//
|
|
// TinyDNGWriter, single header only DNG writer in C++11.
|
|
//
|
|
|
|
/*
|
|
The MIT License (MIT)
|
|
|
|
Copyright (c) 2016 - 2020 Syoyo Fujita.
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in
|
|
all copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
THE SOFTWARE.
|
|
*/
|
|
|
|
#ifndef TINY_DNG_WRITER_H_
|
|
#define TINY_DNG_WRITER_H_
|
|
|
|
#include <sstream>
|
|
#include <vector>
|
|
|
|
namespace tinydngwriter {
|
|
|
|
typedef enum {
|
|
TIFFTAG_SUB_FILETYPE = 254,
|
|
TIFFTAG_IMAGE_WIDTH = 256,
|
|
TIFFTAG_IMAGE_LENGTH = 257,
|
|
TIFFTAG_BITS_PER_SAMPLE = 258,
|
|
TIFFTAG_COMPRESSION = 259,
|
|
TIFFTAG_PHOTOMETRIC = 262,
|
|
TIFFTAG_IMAGEDESCRIPTION = 270,
|
|
TIFFTAG_STRIP_OFFSET = 273,
|
|
TIFFTAG_SAMPLES_PER_PIXEL = 277,
|
|
TIFFTAG_ROWS_PER_STRIP = 278,
|
|
TIFFTAG_STRIP_BYTE_COUNTS = 279,
|
|
TIFFTAG_PLANAR_CONFIG = 284,
|
|
TIFFTAG_ORIENTATION = 274,
|
|
|
|
TIFFTAG_XRESOLUTION = 282, // rational
|
|
TIFFTAG_YRESOLUTION = 283, // rational
|
|
TIFFTAG_RESOLUTION_UNIT = 296,
|
|
|
|
TIFFTAG_SAMPLEFORMAT = 339,
|
|
|
|
// DNG extension
|
|
TIFFTAG_CFA_REPEAT_PATTERN_DIM = 33421,
|
|
TIFFTAG_CFA_PATTERN = 33422,
|
|
|
|
TIFFTAG_DNG_VERSION = 50706,
|
|
TIFFTAG_DNG_BACKWARD_VERSION = 50707,
|
|
TIFFTAG_CHRROMA_BLUR_RADIUS = 50703,
|
|
TIFFTAG_BLACK_LEVEL = 50714,
|
|
TIFFTAG_WHITE_LEVEL = 50717,
|
|
TIFFTAG_COLOR_MATRIX1 = 50721,
|
|
TIFFTAG_COLOR_MATRIX2 = 50722,
|
|
TIFFTAG_EXTRA_CAMERA_PROFILES = 50933,
|
|
TIFFTAG_PROFILE_NAME = 50936,
|
|
TIFFTAG_AS_SHOT_PROFILE_NAME = 50934,
|
|
TIFFTAG_DEFAULT_BLACK_RENDER = 51110,
|
|
TIFFTAG_ACTIVE_AREA = 50829,
|
|
TIFFTAG_FORWARD_MATRIX1 = 50964,
|
|
TIFFTAG_FORWARD_MATRIX2 = 50965
|
|
} Tag;
|
|
|
|
// SUBFILETYPE(bit field)
|
|
static const int FILETYPE_REDUCEDIMAGE = 1;
|
|
static const int FILETYPE_PAGE = 2;
|
|
static const int FILETYPE_MASK = 4;
|
|
|
|
// PLANARCONFIG
|
|
static const int PLANARCONFIG_CONTIG = 1;
|
|
static const int PLANARCONFIG_SEPARATE = 2;
|
|
|
|
// COMPRESSION
|
|
// TODO(syoyo) more compressin types.
|
|
static const int COMPRESSION_NONE = 1;
|
|
|
|
// ORIENTATION
|
|
static const int ORIENTATION_TOPLEFT = 1;
|
|
static const int ORIENTATION_TOPRIGHT = 2;
|
|
static const int ORIENTATION_BOTRIGHT = 3;
|
|
static const int ORIENTATION_BOTLEFT = 4;
|
|
static const int ORIENTATION_LEFTTOP = 5;
|
|
static const int ORIENTATION_RIGHTTOP = 6;
|
|
static const int ORIENTATION_RIGHTBOT = 7;
|
|
static const int ORIENTATION_LEFTBOT = 8;
|
|
|
|
// RESOLUTIONUNIT
|
|
static const int RESUNIT_NONE = 1;
|
|
static const int RESUNIT_INCH = 2;
|
|
static const int RESUNIT_CENTIMETER = 2;
|
|
|
|
// PHOTOMETRIC
|
|
// TODO(syoyo): more photometric types.
|
|
static const int PHOTOMETRIC_WHITE_IS_ZERO = 0; // For bilevel and grayscale
|
|
static const int PHOTOMETRIC_BLACK_IS_ZERO = 1; // For bilevel and grayscale
|
|
static const int PHOTOMETRIC_RGB = 2; // Default
|
|
static const int PHOTOMETRIC_CFA = 32893; // DNG ext
|
|
static const int PHOTOMETRIC_LINEARRAW = 34892; // DNG ext
|
|
|
|
// Sample format
|
|
static const int SAMPLEFORMAT_UINT = 1; // Default
|
|
static const int SAMPLEFORMAT_INT = 2;
|
|
static const int SAMPLEFORMAT_IEEEFP = 3; // floating point
|
|
|
|
struct IFDTag {
|
|
unsigned short tag;
|
|
unsigned short type;
|
|
unsigned int count;
|
|
unsigned int offset_or_value;
|
|
};
|
|
// 12 bytes.
|
|
|
|
class DNGImage {
|
|
public:
|
|
DNGImage();
|
|
~DNGImage() {}
|
|
|
|
///
|
|
/// Optional: Explicitly specify endian.
|
|
/// Must be called before calling other Set methods.
|
|
///
|
|
void SetBigEndian(bool big_endian);
|
|
|
|
///
|
|
/// Default = 0
|
|
///
|
|
bool SetSubfileType(bool reduced_image = false, bool page = false,
|
|
bool mask = false);
|
|
|
|
bool SetImageWidth(unsigned int value);
|
|
bool SetImageLength(unsigned int value);
|
|
bool SetRowsPerStrip(unsigned int value);
|
|
bool SetSamplesPerPixel(unsigned short value);
|
|
// Set bits for each samples
|
|
bool SetBitsPerSample(const unsigned int num_samples,
|
|
const unsigned short *values);
|
|
bool SetPhotometric(unsigned short value);
|
|
bool SetPlanarConfig(unsigned short value);
|
|
bool SetOrientation(unsigned short value);
|
|
bool SetCompression(unsigned short value);
|
|
bool SetSampleFormat(const unsigned int num_samples,
|
|
const unsigned short *values);
|
|
bool SetXResolution(double value);
|
|
bool SetYResolution(double value);
|
|
bool SetResolutionUnit(const unsigned short value);
|
|
|
|
bool SetImageDescription(const std::string &ascii);
|
|
|
|
bool SetActiveArea(const unsigned int values[4]);
|
|
|
|
bool SetChromaBlurRadius(double value);
|
|
|
|
/// Specify black level per sample.
|
|
bool SetBlackLevelRational(unsigned int num_samples, const double *values);
|
|
|
|
/// Specify white level per sample.
|
|
bool SetWhiteLevelRational(unsigned int num_samples, const double *values);
|
|
|
|
/// Set image data
|
|
bool SetImageData(const unsigned char *data, const size_t data_len);
|
|
|
|
/// Set custom field
|
|
bool SetCustomFieldLong(const unsigned short tag, const int value);
|
|
bool SetCustomFieldULong(const unsigned short tag, const unsigned int value);
|
|
|
|
size_t GetDataSize() const { return data_os_.str().length(); }
|
|
|
|
size_t GetStripOffset() const { return data_strip_offset_; }
|
|
size_t GetStripBytes() const { return data_strip_bytes_; }
|
|
|
|
/// Write aux IFD data and strip image data to stream
|
|
bool WriteDataToStream(std::ostream *ofs, std::string *err) const;
|
|
|
|
///
|
|
/// Write IFD to stream.
|
|
///
|
|
/// @param[in] data_base_offset : Byte offset to data
|
|
/// @param[in] strip_offset : Byte offset to image strip data
|
|
///
|
|
/// TODO(syoyo): Support multiple strips
|
|
///
|
|
bool WriteIFDToStream(const unsigned int data_base_offset,
|
|
const unsigned int strip_offset, std::ostream *ofs,
|
|
std::string *err) const;
|
|
|
|
std::string Error() const { return err_; }
|
|
|
|
private:
|
|
std::ostringstream data_os_;
|
|
bool swap_endian_;
|
|
bool dng_big_endian_;
|
|
unsigned short num_fields_;
|
|
unsigned int samples_per_pixels_;
|
|
unsigned short bits_per_sample_;
|
|
|
|
// TODO(syoyo): Support multiple strips
|
|
size_t data_strip_offset_{0};
|
|
size_t data_strip_bytes_{0};
|
|
|
|
std::string err_; // Error message
|
|
|
|
std::vector<IFDTag> ifd_tags_;
|
|
};
|
|
|
|
class DNGWriter {
|
|
public:
|
|
// TODO(syoyo): Use same endian setting with DNGImage.
|
|
DNGWriter(bool big_endian);
|
|
~DNGWriter() {}
|
|
|
|
///
|
|
/// Add DNGImage.
|
|
/// It just retains the pointer of the image, thus
|
|
/// application must not free resources until `WriteToFile` has been called.
|
|
///
|
|
bool AddImage(const DNGImage *image) {
|
|
images_.push_back(image);
|
|
|
|
return true;
|
|
}
|
|
|
|
/// Write DNG to a file.
|
|
/// Return error string to `err` when Write() returns false.
|
|
/// Returns true upon success.
|
|
bool WriteToFile(const char *filename, std::string *err) const;
|
|
|
|
private:
|
|
bool swap_endian_;
|
|
bool dng_big_endian_; // Endianness of DNG file.
|
|
|
|
std::vector<const DNGImage *> images_;
|
|
};
|
|
|
|
} // namespace tinydngwriter
|
|
|
|
#endif // TINY_DNG_WRITER_H_
|
|
|
|
#ifdef TINY_DNG_WRITER_IMPLEMENTATION
|
|
|
|
//
|
|
// TIFF format resources.
|
|
//
|
|
// http://c0de517e.blogspot.jp/2013/07/tiny-hdr-writer.html
|
|
// http://paulbourke.net/dataformats/tiff/ and
|
|
// http://partners.adobe.com/public/developer/en/tiff/TIFF6.pdf
|
|
//
|
|
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <cfloat>
|
|
#include <cmath>
|
|
#include <cstdint>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <sstream>
|
|
|
|
namespace tinydngwriter {
|
|
|
|
#ifdef __clang__
|
|
#pragma clang diagnostic push
|
|
#if __has_warning("-Wzero-as-null-pointer-constant")
|
|
#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant"
|
|
#endif
|
|
#endif
|
|
|
|
//
|
|
// TinyDNGWriter stores IFD table in the end of file so that offset to
|
|
// image data can be easily computed.
|
|
//
|
|
// +----------------------+
|
|
// | header |
|
|
// +----------------------+
|
|
// | |
|
|
// | image & meta 0 |
|
|
// | |
|
|
// +----------------------+
|
|
// | |
|
|
// | image & meta 1 |
|
|
// | |
|
|
// +----------------------+
|
|
// ...
|
|
// +----------------------+
|
|
// | |
|
|
// | image & meta N |
|
|
// | |
|
|
// +----------------------+
|
|
// | |
|
|
// | IFD 0 |
|
|
// | |
|
|
// +----------------------+
|
|
// | |
|
|
// | IFD 1 |
|
|
// | |
|
|
// +----------------------+
|
|
// ...
|
|
// +----------------------+
|
|
// | |
|
|
// | IFD 2 |
|
|
// | |
|
|
// +----------------------+
|
|
//
|
|
|
|
// From tiff.h
|
|
typedef enum {
|
|
TIFF_NOTYPE = 0, /* placeholder */
|
|
TIFF_BYTE = 1, /* 8-bit unsigned integer */
|
|
TIFF_ASCII = 2, /* 8-bit bytes w/ last byte null */
|
|
TIFF_SHORT = 3, /* 16-bit unsigned integer */
|
|
TIFF_LONG = 4, /* 32-bit unsigned integer */
|
|
TIFF_RATIONAL = 5, /* 64-bit unsigned fraction */
|
|
TIFF_SBYTE = 6, /* !8-bit signed integer */
|
|
TIFF_UNDEFINED = 7, /* !8-bit untyped data */
|
|
TIFF_SSHORT = 8, /* !16-bit signed integer */
|
|
TIFF_SLONG = 9, /* !32-bit signed integer */
|
|
TIFF_SRATIONAL = 10, /* !64-bit signed fraction */
|
|
TIFF_FLOAT = 11, /* !32-bit IEEE floating point */
|
|
TIFF_DOUBLE = 12, /* !64-bit IEEE floating point */
|
|
TIFF_IFD = 13, /* %32-bit unsigned integer (offset) */
|
|
TIFF_LONG8 = 16, /* BigTIFF 64-bit unsigned integer */
|
|
TIFF_SLONG8 = 17, /* BigTIFF 64-bit signed integer */
|
|
TIFF_IFD8 = 18 /* BigTIFF 64-bit unsigned integer (offset) */
|
|
} DataType;
|
|
|
|
const static int kHeaderSize = 8; // TIFF header size.
|
|
|
|
// floating point to integer rational value conversion
|
|
// https://stackoverflow.com/questions/51142275/exact-value-of-a-floating-point-number-as-a-rational
|
|
//
|
|
// Return error flag
|
|
static int DoubleToRational(double x, double *numerator, double *denominator) {
|
|
if (!std::isfinite(x)) {
|
|
*numerator = *denominator = 0.0;
|
|
if (x > 0.0) *numerator = 1.0;
|
|
if (x < 0.0) *numerator = -1.0;
|
|
return 1;
|
|
}
|
|
|
|
// TIFF Rational use two uint32's, so reduce the bits
|
|
int bdigits = FLT_MANT_DIG;
|
|
int expo;
|
|
*denominator = 1.0;
|
|
*numerator = std::frexp(x, &expo) * std::pow(2.0, bdigits);
|
|
expo -= bdigits;
|
|
if (expo > 0) {
|
|
*numerator *= std::pow(2.0, expo);
|
|
} else if (expo < 0) {
|
|
expo = -expo;
|
|
if (expo >= FLT_MAX_EXP - 1) {
|
|
*numerator /= std::pow(2.0, expo - (FLT_MAX_EXP - 1));
|
|
*denominator *= std::pow(2.0, FLT_MAX_EXP - 1);
|
|
return fabs(*numerator) < 1.0;
|
|
} else {
|
|
*denominator *= std::pow(2.0, expo);
|
|
}
|
|
}
|
|
|
|
while ((std::fabs(*numerator) > 0.0) &&
|
|
(std::fabs(std::fmod(*numerator, 2)) <
|
|
std::numeric_limits<double>::epsilon()) &&
|
|
(std::fabs(std::fmod(*denominator, 2)) <
|
|
std::numeric_limits<double>::epsilon())) {
|
|
*numerator /= 2.0;
|
|
*denominator /= 2.0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static inline bool IsBigEndian() {
|
|
unsigned int i = 0x01020304;
|
|
char c[4];
|
|
memcpy(c, &i, 4);
|
|
return (c[0] == 1);
|
|
}
|
|
|
|
static void swap2(unsigned short *val) {
|
|
unsigned short tmp = *val;
|
|
unsigned char *dst = reinterpret_cast<unsigned char *>(val);
|
|
unsigned char *src = reinterpret_cast<unsigned char *>(&tmp);
|
|
|
|
dst[0] = src[1];
|
|
dst[1] = src[0];
|
|
}
|
|
|
|
static void swap4(unsigned int *val) {
|
|
unsigned int tmp = *val;
|
|
unsigned char *dst = reinterpret_cast<unsigned char *>(val);
|
|
unsigned char *src = reinterpret_cast<unsigned char *>(&tmp);
|
|
|
|
dst[0] = src[3];
|
|
dst[1] = src[2];
|
|
dst[2] = src[1];
|
|
dst[3] = src[0];
|
|
}
|
|
|
|
static void swap8(uint64_t *val) {
|
|
uint64_t tmp = *val;
|
|
unsigned char *dst = reinterpret_cast<unsigned char *>(val);
|
|
unsigned char *src = reinterpret_cast<unsigned char *>(&tmp);
|
|
|
|
dst[0] = src[7];
|
|
dst[1] = src[6];
|
|
dst[2] = src[5];
|
|
dst[3] = src[4];
|
|
dst[4] = src[3];
|
|
dst[5] = src[2];
|
|
dst[6] = src[1];
|
|
dst[7] = src[0];
|
|
}
|
|
|
|
static void Write1(const unsigned char c, std::ostringstream *out) {
|
|
unsigned char value = c;
|
|
out->write(reinterpret_cast<const char *>(&value), 1);
|
|
}
|
|
|
|
static void Write2(const unsigned short c, std::ostringstream *out,
|
|
const bool swap_endian) {
|
|
unsigned short value = c;
|
|
if (swap_endian) {
|
|
swap2(&value);
|
|
}
|
|
|
|
out->write(reinterpret_cast<const char *>(&value), 2);
|
|
}
|
|
|
|
static void Write4(const unsigned int c, std::ostringstream *out,
|
|
const bool swap_endian) {
|
|
unsigned int value = c;
|
|
if (swap_endian) {
|
|
swap4(&value);
|
|
}
|
|
|
|
out->write(reinterpret_cast<const char *>(&value), 4);
|
|
}
|
|
|
|
static bool WriteTIFFTag(const unsigned short tag, const unsigned short type,
|
|
const unsigned int count, const unsigned char *data,
|
|
std::vector<IFDTag> *tags_out,
|
|
std::ostringstream *data_out) {
|
|
assert(sizeof(IFDTag) ==
|
|
12); // FIXME(syoyo): Use static_assert for C++11 compiler
|
|
|
|
IFDTag ifd;
|
|
ifd.tag = tag;
|
|
ifd.type = type;
|
|
ifd.count = count;
|
|
|
|
size_t typesize_table[] = {1, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8, 4};
|
|
|
|
size_t len = count * (typesize_table[(type) < 14 ? (type) : 0]);
|
|
if (len > 4) {
|
|
assert(data_out);
|
|
if (!data_out) {
|
|
return false;
|
|
}
|
|
|
|
// Store offset value.
|
|
|
|
unsigned int offset =
|
|
static_cast<unsigned int>(data_out->tellp()) + kHeaderSize;
|
|
ifd.offset_or_value = offset;
|
|
|
|
data_out->write(reinterpret_cast<const char *>(data),
|
|
static_cast<std::streamsize>(len));
|
|
|
|
} else {
|
|
ifd.offset_or_value = 0;
|
|
|
|
// less than 4 bytes = store data itself.
|
|
if (len == 1) {
|
|
unsigned char value = *(data);
|
|
memcpy(&(ifd.offset_or_value), &value, sizeof(unsigned char));
|
|
} else if (len == 2) {
|
|
unsigned short value = *(reinterpret_cast<const unsigned short *>(data));
|
|
memcpy(&(ifd.offset_or_value), &value, sizeof(unsigned short));
|
|
} else if (len == 4) {
|
|
unsigned int value = *(reinterpret_cast<const unsigned int *>(data));
|
|
ifd.offset_or_value = value;
|
|
} else {
|
|
assert(0);
|
|
}
|
|
}
|
|
|
|
tags_out->push_back(ifd);
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool WriteTIFFVersionHeader(std::ostringstream *out, bool big_endian) {
|
|
// TODO(syoyo): Support BigTIFF?
|
|
|
|
// 4d 4d = Big endian. 49 49 = Little endian.
|
|
if (big_endian) {
|
|
Write1(0x4d, out);
|
|
Write1(0x4d, out);
|
|
Write1(0x0, out);
|
|
Write1(0x2a, out); // Tiff version ID
|
|
} else {
|
|
Write1(0x49, out);
|
|
Write1(0x49, out);
|
|
Write1(0x2a, out); // Tiff version ID
|
|
Write1(0x0, out);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
DNGImage::DNGImage()
|
|
: dng_big_endian_(true),
|
|
num_fields_(0),
|
|
samples_per_pixels_(0),
|
|
bits_per_sample_(0),
|
|
data_strip_offset_{0},
|
|
data_strip_bytes_{0} {
|
|
swap_endian_ = (IsBigEndian() != dng_big_endian_);
|
|
}
|
|
|
|
void DNGImage::SetBigEndian(bool big_endian) {
|
|
dng_big_endian_ = big_endian;
|
|
swap_endian_ = (IsBigEndian() != dng_big_endian_);
|
|
}
|
|
|
|
bool DNGImage::SetSubfileType(bool reduced_image, bool page, bool mask) {
|
|
unsigned int count = 1;
|
|
|
|
unsigned int bits = 0;
|
|
if (reduced_image) {
|
|
bits |= FILETYPE_REDUCEDIMAGE;
|
|
}
|
|
if (page) {
|
|
bits |= FILETYPE_PAGE;
|
|
}
|
|
if (mask) {
|
|
bits |= FILETYPE_MASK;
|
|
}
|
|
|
|
bool ret = WriteTIFFTag(
|
|
static_cast<unsigned short>(TIFFTAG_SUB_FILETYPE), TIFF_LONG, count,
|
|
reinterpret_cast<const unsigned char *>(&bits), &ifd_tags_, &data_os_);
|
|
|
|
if (!ret) {
|
|
return false;
|
|
}
|
|
|
|
num_fields_++;
|
|
return true;
|
|
}
|
|
|
|
bool DNGImage::SetImageWidth(const unsigned int width) {
|
|
unsigned int count = 1;
|
|
|
|
unsigned int data = width;
|
|
bool ret = WriteTIFFTag(
|
|
static_cast<unsigned short>(TIFFTAG_IMAGE_WIDTH), TIFF_LONG, count,
|
|
reinterpret_cast<const unsigned char *>(&data), &ifd_tags_, &data_os_);
|
|
|
|
if (!ret) {
|
|
return false;
|
|
}
|
|
|
|
num_fields_++;
|
|
return true;
|
|
}
|
|
|
|
bool DNGImage::SetImageLength(const unsigned int length) {
|
|
unsigned int count = 1;
|
|
|
|
const unsigned int data = length;
|
|
bool ret = WriteTIFFTag(
|
|
static_cast<unsigned short>(TIFFTAG_IMAGE_LENGTH), TIFF_LONG, count,
|
|
reinterpret_cast<const unsigned char *>(&data), &ifd_tags_, &data_os_);
|
|
|
|
if (!ret) {
|
|
return false;
|
|
}
|
|
|
|
num_fields_++;
|
|
return true;
|
|
}
|
|
|
|
bool DNGImage::SetRowsPerStrip(const unsigned int rows) {
|
|
if (rows == 0) {
|
|
return false;
|
|
}
|
|
|
|
unsigned int count = 1;
|
|
|
|
const unsigned int data = rows;
|
|
bool ret = WriteTIFFTag(
|
|
static_cast<unsigned short>(TIFFTAG_ROWS_PER_STRIP), TIFF_LONG, count,
|
|
reinterpret_cast<const unsigned char *>(&data), &ifd_tags_, &data_os_);
|
|
|
|
if (!ret) {
|
|
return false;
|
|
}
|
|
|
|
num_fields_++;
|
|
return true;
|
|
}
|
|
|
|
bool DNGImage::SetSamplesPerPixel(const unsigned short value) {
|
|
if (value > 4) {
|
|
return false;
|
|
}
|
|
|
|
unsigned int count = 1;
|
|
|
|
const unsigned short data = value;
|
|
bool ret = WriteTIFFTag(
|
|
static_cast<unsigned short>(TIFFTAG_SAMPLES_PER_PIXEL), TIFF_SHORT, count,
|
|
reinterpret_cast<const unsigned char *>(&data), &ifd_tags_, &data_os_);
|
|
|
|
if (!ret) {
|
|
return false;
|
|
}
|
|
|
|
samples_per_pixels_ = value; // Store SPP for later use.
|
|
|
|
num_fields_++;
|
|
return true;
|
|
}
|
|
|
|
bool DNGImage::SetBitsPerSample(const unsigned int num_samples,
|
|
const unsigned short *values) {
|
|
// `SetSamplesPerPixel()` must be called in advance and SPP shoud be equal to
|
|
// `num_samples`.
|
|
if ((num_samples > 0) && (num_samples == samples_per_pixels_)) {
|
|
// OK
|
|
} else {
|
|
err_ += "SetSamplesPerPixel() must be called before SetBitsPerSample().\n";
|
|
return false;
|
|
}
|
|
|
|
unsigned short bps = values[0];
|
|
|
|
std::vector<unsigned short> vs(num_samples);
|
|
for (size_t i = 0; i < vs.size(); i++) {
|
|
// FIXME(syoyo): Currently bps must be same for all samples
|
|
if (bps != values[i]) {
|
|
err_ += "BitsPerSample must be same among samples at the moment.\n";
|
|
return false;
|
|
}
|
|
|
|
vs[i] = values[i];
|
|
|
|
// TODO(syoyo): Swap values when writing IFD tag, not here.
|
|
if (swap_endian_) {
|
|
swap2(&vs[i]);
|
|
}
|
|
}
|
|
|
|
unsigned int count = num_samples;
|
|
|
|
bool ret = WriteTIFFTag(static_cast<unsigned short>(TIFFTAG_BITS_PER_SAMPLE),
|
|
TIFF_SHORT, count,
|
|
reinterpret_cast<const unsigned char *>(vs.data()),
|
|
&ifd_tags_, &data_os_);
|
|
|
|
if (!ret) {
|
|
return false;
|
|
}
|
|
|
|
// Store BPS for later use.
|
|
bits_per_sample_ = bps;
|
|
|
|
num_fields_++;
|
|
return true;
|
|
}
|
|
|
|
bool DNGImage::SetPhotometric(const unsigned short value) {
|
|
if ((value == PHOTOMETRIC_LINEARRAW) || (value == PHOTOMETRIC_RGB) ||
|
|
(value == PHOTOMETRIC_WHITE_IS_ZERO) ||
|
|
(value == PHOTOMETRIC_BLACK_IS_ZERO)) {
|
|
// OK
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
unsigned int count = 1;
|
|
|
|
const unsigned short data = value;
|
|
bool ret = WriteTIFFTag(
|
|
static_cast<unsigned short>(TIFFTAG_PHOTOMETRIC), TIFF_SHORT, count,
|
|
reinterpret_cast<const unsigned char *>(&data), &ifd_tags_, &data_os_);
|
|
|
|
if (!ret) {
|
|
return false;
|
|
}
|
|
|
|
num_fields_++;
|
|
return true;
|
|
}
|
|
|
|
bool DNGImage::SetPlanarConfig(const unsigned short value) {
|
|
unsigned int count = 1;
|
|
|
|
if ((value == PLANARCONFIG_CONTIG) || (value == PLANARCONFIG_SEPARATE)) {
|
|
// OK
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
const unsigned short data = value;
|
|
bool ret = WriteTIFFTag(
|
|
static_cast<unsigned short>(TIFFTAG_PLANAR_CONFIG), TIFF_SHORT, count,
|
|
reinterpret_cast<const unsigned char *>(&data), &ifd_tags_, &data_os_);
|
|
|
|
if (!ret) {
|
|
return false;
|
|
}
|
|
|
|
num_fields_++;
|
|
return true;
|
|
}
|
|
|
|
bool DNGImage::SetCompression(const unsigned short value) {
|
|
unsigned int count = 1;
|
|
|
|
if ((value == COMPRESSION_NONE)) {
|
|
// OK
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
const unsigned short data = value;
|
|
bool ret = WriteTIFFTag(
|
|
static_cast<unsigned short>(TIFFTAG_COMPRESSION), TIFF_SHORT, count,
|
|
reinterpret_cast<const unsigned char *>(&data), &ifd_tags_, &data_os_);
|
|
|
|
if (!ret) {
|
|
return false;
|
|
}
|
|
|
|
num_fields_++;
|
|
return true;
|
|
}
|
|
|
|
bool DNGImage::SetSampleFormat(const unsigned int num_samples,
|
|
const unsigned short *values) {
|
|
// `SetSamplesPerPixel()` must be called in advance
|
|
if ((num_samples > 0) && (num_samples == samples_per_pixels_)) {
|
|
// OK
|
|
} else {
|
|
err_ += "SetSamplesPerPixel() must be called before SetSampleFormat().\n";
|
|
return false;
|
|
}
|
|
|
|
unsigned short format = values[0];
|
|
|
|
std::vector<unsigned short> vs(num_samples);
|
|
for (size_t i = 0; i < vs.size(); i++) {
|
|
// FIXME(syoyo): Currently format must be same for all samples
|
|
if (format != values[i]) {
|
|
err_ += "SampleFormat must be same among samples at the moment.\n";
|
|
return false;
|
|
}
|
|
|
|
if ((format == SAMPLEFORMAT_UINT) || (format == SAMPLEFORMAT_INT) ||
|
|
(format == SAMPLEFORMAT_IEEEFP)) {
|
|
// OK
|
|
} else {
|
|
err_ += "Invalid format value specified for SetSampleFormat().\n";
|
|
return false;
|
|
}
|
|
|
|
vs[i] = values[i];
|
|
|
|
// TODO(syoyo): Swap values when writing IFD tag, not here.
|
|
if (swap_endian_) {
|
|
swap2(&vs[i]);
|
|
}
|
|
}
|
|
|
|
unsigned int count = num_samples;
|
|
|
|
bool ret = WriteTIFFTag(static_cast<unsigned short>(TIFFTAG_SAMPLEFORMAT),
|
|
TIFF_SHORT, count,
|
|
reinterpret_cast<const unsigned char *>(vs.data()),
|
|
&ifd_tags_, &data_os_);
|
|
|
|
if (!ret) {
|
|
return false;
|
|
}
|
|
|
|
num_fields_++;
|
|
return true;
|
|
}
|
|
|
|
bool DNGImage::SetOrientation(const unsigned short value) {
|
|
unsigned int count = 1;
|
|
|
|
if ((value == ORIENTATION_TOPLEFT) || (value == ORIENTATION_TOPRIGHT) ||
|
|
(value == ORIENTATION_BOTRIGHT) || (value == ORIENTATION_BOTLEFT) ||
|
|
(value == ORIENTATION_LEFTTOP) || (value == ORIENTATION_RIGHTTOP) ||
|
|
(value == ORIENTATION_RIGHTBOT) || (value == ORIENTATION_LEFTBOT)) {
|
|
// OK
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
const unsigned int data = value;
|
|
bool ret = WriteTIFFTag(
|
|
static_cast<unsigned short>(TIFFTAG_ORIENTATION), TIFF_SHORT, count,
|
|
reinterpret_cast<const unsigned char *>(&data), &ifd_tags_, &data_os_);
|
|
|
|
if (!ret) {
|
|
return false;
|
|
}
|
|
|
|
num_fields_++;
|
|
return true;
|
|
}
|
|
|
|
bool DNGImage::SetBlackLevelRational(unsigned int num_samples,
|
|
const double *values) {
|
|
// `SetSamplesPerPixel()` must be called in advance and SPP shoud be equal to
|
|
// `num_samples`.
|
|
if ((num_samples > 0) && (num_samples == samples_per_pixels_)) {
|
|
// OK
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
std::vector<unsigned int> vs(num_samples * 2);
|
|
for (size_t i = 0; i < vs.size(); i++) {
|
|
double numerator, denominator;
|
|
if (DoubleToRational(values[i], &numerator, &denominator) != 0) {
|
|
// Couldn't represent fp value as integer rational value.
|
|
return false;
|
|
}
|
|
|
|
vs[2 * i + 0] = static_cast<unsigned int>(numerator);
|
|
vs[2 * i + 1] = static_cast<unsigned int>(denominator);
|
|
|
|
// TODO(syoyo): Swap rational value(8 bytes) when writing IFD tag, not here.
|
|
if (swap_endian_) {
|
|
swap4(&vs[2 * i + 0]);
|
|
swap4(&vs[2 * i + 1]);
|
|
}
|
|
}
|
|
|
|
unsigned int count = num_samples;
|
|
|
|
bool ret = WriteTIFFTag(static_cast<unsigned short>(TIFFTAG_BLACK_LEVEL),
|
|
TIFF_RATIONAL, count,
|
|
reinterpret_cast<const unsigned char *>(vs.data()),
|
|
&ifd_tags_, &data_os_);
|
|
|
|
if (!ret) {
|
|
return false;
|
|
}
|
|
|
|
num_fields_++;
|
|
return true;
|
|
}
|
|
|
|
bool DNGImage::SetWhiteLevelRational(unsigned int num_samples,
|
|
const double *values) {
|
|
// `SetSamplesPerPixel()` must be called in advance and SPP shoud be equal to
|
|
// `num_samples`.
|
|
if ((num_samples > 0) && (num_samples == samples_per_pixels_)) {
|
|
// OK
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
std::vector<unsigned int> vs(num_samples * 2);
|
|
for (size_t i = 0; i < vs.size(); i++) {
|
|
double numerator, denominator;
|
|
if (DoubleToRational(values[i], &numerator, &denominator) != 0) {
|
|
// Couldn't represent fp value as integer rational value.
|
|
return false;
|
|
}
|
|
|
|
vs[2 * i + 0] = static_cast<unsigned int>(numerator);
|
|
vs[2 * i + 1] = static_cast<unsigned int>(denominator);
|
|
|
|
// TODO(syoyo): Swap rational value(8 bytes) when writing IFD tag, not here.
|
|
if (swap_endian_) {
|
|
swap4(&vs[2 * i + 0]);
|
|
swap4(&vs[2 * i + 1]);
|
|
}
|
|
}
|
|
|
|
unsigned int count = num_samples;
|
|
|
|
bool ret = WriteTIFFTag(static_cast<unsigned short>(TIFFTAG_WHITE_LEVEL),
|
|
TIFF_RATIONAL, count,
|
|
reinterpret_cast<const unsigned char *>(vs.data()),
|
|
&ifd_tags_, &data_os_);
|
|
|
|
if (!ret) {
|
|
return false;
|
|
}
|
|
|
|
num_fields_++;
|
|
return true;
|
|
}
|
|
|
|
bool DNGImage::SetXResolution(const double value) {
|
|
double numerator, denominator;
|
|
if (DoubleToRational(value, &numerator, &denominator) != 0) {
|
|
// Couldn't represent fp value as integer rational value.
|
|
return false;
|
|
}
|
|
|
|
unsigned int data[2];
|
|
data[0] = static_cast<unsigned int>(numerator);
|
|
data[1] = static_cast<unsigned int>(denominator);
|
|
|
|
// TODO(syoyo): Swap rational value(8 bytes) when writing IFD tag, not here.
|
|
if (swap_endian_) {
|
|
swap4(&data[0]);
|
|
swap4(&data[1]);
|
|
}
|
|
|
|
bool ret = WriteTIFFTag(
|
|
static_cast<unsigned short>(TIFFTAG_XRESOLUTION), TIFF_RATIONAL, 1,
|
|
reinterpret_cast<const unsigned char *>(data), &ifd_tags_, &data_os_);
|
|
|
|
if (!ret) {
|
|
return false;
|
|
}
|
|
|
|
num_fields_++;
|
|
return true;
|
|
}
|
|
|
|
bool DNGImage::SetYResolution(const double value) {
|
|
double numerator, denominator;
|
|
if (DoubleToRational(value, &numerator, &denominator) != 0) {
|
|
// Couldn't represent fp value as integer rational value.
|
|
return false;
|
|
}
|
|
|
|
unsigned int data[2];
|
|
data[0] = static_cast<unsigned int>(numerator);
|
|
data[1] = static_cast<unsigned int>(denominator);
|
|
|
|
// TODO(syoyo): Swap rational value(8 bytes) when writing IFD tag, not here.
|
|
if (swap_endian_) {
|
|
swap4(&data[0]);
|
|
swap4(&data[1]);
|
|
}
|
|
|
|
bool ret = WriteTIFFTag(
|
|
static_cast<unsigned short>(TIFFTAG_YRESOLUTION), TIFF_RATIONAL, 1,
|
|
reinterpret_cast<const unsigned char *>(data), &ifd_tags_, &data_os_);
|
|
|
|
if (!ret) {
|
|
return false;
|
|
}
|
|
|
|
num_fields_++;
|
|
return true;
|
|
}
|
|
|
|
bool DNGImage::SetResolutionUnit(const unsigned short value) {
|
|
unsigned int count = 1;
|
|
|
|
if ((value == RESUNIT_NONE) || (value == RESUNIT_INCH) ||
|
|
(value == RESUNIT_CENTIMETER)) {
|
|
// OK
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
const unsigned short data = value;
|
|
bool ret = WriteTIFFTag(
|
|
static_cast<unsigned short>(TIFFTAG_RESOLUTION_UNIT), TIFF_SHORT, count,
|
|
reinterpret_cast<const unsigned char *>(&data), &ifd_tags_, &data_os_);
|
|
|
|
if (!ret) {
|
|
return false;
|
|
}
|
|
|
|
num_fields_++;
|
|
return true;
|
|
}
|
|
|
|
bool DNGImage::SetImageDescription(const std::string &ascii) {
|
|
unsigned int count =
|
|
static_cast<unsigned int>(ascii.length() + 1); // +1 for '\0'
|
|
|
|
if (count < 2) {
|
|
// empty string
|
|
return false;
|
|
}
|
|
|
|
if (count > (1024 * 1024)) {
|
|
// too large
|
|
return false;
|
|
}
|
|
|
|
bool ret = WriteTIFFTag(static_cast<unsigned short>(TIFFTAG_IMAGEDESCRIPTION),
|
|
TIFF_ASCII, count,
|
|
reinterpret_cast<const unsigned char *>(ascii.data()),
|
|
&ifd_tags_, &data_os_);
|
|
|
|
if (!ret) {
|
|
return false;
|
|
}
|
|
|
|
num_fields_++;
|
|
return true;
|
|
}
|
|
|
|
bool DNGImage::SetActiveArea(const unsigned int values[4]) {
|
|
unsigned int count = 4;
|
|
|
|
const unsigned int *data = values;
|
|
bool ret = WriteTIFFTag(
|
|
static_cast<unsigned short>(TIFFTAG_ACTIVE_AREA), TIFF_LONG, count,
|
|
reinterpret_cast<const unsigned char *>(data), &ifd_tags_, &data_os_);
|
|
|
|
if (!ret) {
|
|
return false;
|
|
}
|
|
|
|
num_fields_++;
|
|
return true;
|
|
}
|
|
|
|
bool DNGImage::SetImageData(const unsigned char *data, const size_t data_len) {
|
|
if ((data == NULL) || (data_len < 1)) {
|
|
return false;
|
|
}
|
|
|
|
data_strip_offset_ = size_t(data_os_.tellp());
|
|
data_strip_bytes_ = data_len;
|
|
|
|
data_os_.write(reinterpret_cast<const char *>(data),
|
|
static_cast<std::streamsize>(data_len));
|
|
|
|
// NOTE: STRIP_OFFSET tag will be written at `WriteIFDToStream()`.
|
|
|
|
{
|
|
unsigned int count = 1;
|
|
unsigned int bytes = static_cast<unsigned int>(data_len);
|
|
|
|
bool ret = WriteTIFFTag(
|
|
static_cast<unsigned short>(TIFFTAG_STRIP_BYTE_COUNTS), TIFF_LONG,
|
|
count, reinterpret_cast<const unsigned char *>(&bytes), &ifd_tags_,
|
|
NULL);
|
|
|
|
if (!ret) {
|
|
return false;
|
|
}
|
|
|
|
num_fields_++;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DNGImage::SetCustomFieldLong(const unsigned short tag, const int value) {
|
|
unsigned int count = 1;
|
|
|
|
// TODO(syoyo): Check if `tag` value does not conflict with existing TIFF tag
|
|
// value.
|
|
|
|
bool ret = WriteTIFFTag(tag, TIFF_SLONG, count,
|
|
reinterpret_cast<const unsigned char *>(&value),
|
|
&ifd_tags_, &data_os_);
|
|
|
|
if (!ret) {
|
|
return false;
|
|
}
|
|
|
|
num_fields_++;
|
|
return true;
|
|
}
|
|
|
|
bool DNGImage::SetCustomFieldULong(const unsigned short tag,
|
|
const unsigned int value) {
|
|
unsigned int count = 1;
|
|
|
|
// TODO(syoyo): Check if `tag` value does not conflict with existing TIFF tag
|
|
// value.
|
|
|
|
bool ret = WriteTIFFTag(tag, TIFF_LONG, count,
|
|
reinterpret_cast<const unsigned char *>(&value),
|
|
&ifd_tags_, &data_os_);
|
|
|
|
if (!ret) {
|
|
return false;
|
|
}
|
|
|
|
num_fields_++;
|
|
return true;
|
|
}
|
|
|
|
static bool IFDComparator(const IFDTag &a, const IFDTag &b) {
|
|
return (a.tag < b.tag);
|
|
}
|
|
|
|
bool DNGImage::WriteDataToStream(std::ostream *ofs, std::string *err) const {
|
|
if ((data_os_.str().length() == 0)) {
|
|
if (err) {
|
|
(*err) += "Empty IFD data and image data.\n";
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if ((bits_per_sample_ == 0) || (samples_per_pixels_ == 0)) {
|
|
if (err) {
|
|
(*err) += "Both BitsPerSample and SamplesPerPixels must be set.\n";
|
|
}
|
|
return false;
|
|
}
|
|
|
|
std::vector<uint8_t> data(data_os_.str().length());
|
|
memcpy(data.data(), data_os_.str().data(), data.size());
|
|
|
|
if (data_strip_bytes_ == 0) {
|
|
// May ok?.
|
|
} else {
|
|
// FIXME(syoyo): Assume all channels use sample bps
|
|
|
|
// We may need to swap endian for pixel data.
|
|
if (swap_endian_) {
|
|
if (bits_per_sample_ == 16) {
|
|
size_t n = data_strip_bytes_ / sizeof(uint16_t);
|
|
uint16_t *ptr =
|
|
reinterpret_cast<uint16_t *>(data.data() + data_strip_offset_);
|
|
|
|
for (size_t i = 0; i < n; i++) {
|
|
swap2(&ptr[i]);
|
|
}
|
|
|
|
} else if (bits_per_sample_ == 32) {
|
|
size_t n = data_strip_bytes_ / sizeof(uint32_t);
|
|
uint32_t *ptr =
|
|
reinterpret_cast<uint32_t *>(data.data() + data_strip_offset_);
|
|
|
|
for (size_t i = 0; i < n; i++) {
|
|
swap4(&ptr[i]);
|
|
}
|
|
|
|
} else if (bits_per_sample_ == 64) {
|
|
size_t n = data_strip_bytes_ / sizeof(uint64_t);
|
|
uint64_t *ptr =
|
|
reinterpret_cast<uint64_t *>(data.data() + data_strip_offset_);
|
|
|
|
for (size_t i = 0; i < n; i++) {
|
|
swap8(&ptr[i]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ofs->write(reinterpret_cast<const char *>(data.data()),
|
|
static_cast<std::streamsize>(data.size()));
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DNGImage::WriteIFDToStream(const unsigned int data_base_offset,
|
|
const unsigned int strip_offset,
|
|
std::ostream *ofs, std::string *err) const {
|
|
if ((num_fields_ == 0) || (ifd_tags_.size() < 1)) {
|
|
if (err) {
|
|
(*err) += "No TIFF Tags.\n";
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// add STRIP_OFFSET tag and sort IFD tags.
|
|
std::vector<IFDTag> tags = ifd_tags_;
|
|
{
|
|
// For STRIP_OFFSET we need the actual offset value to data(image),
|
|
// thus write STRIP_OFFSET here.
|
|
unsigned int offset = strip_offset + kHeaderSize;
|
|
IFDTag ifd;
|
|
ifd.tag = TIFFTAG_STRIP_OFFSET;
|
|
ifd.type = TIFF_LONG;
|
|
ifd.count = 1;
|
|
ifd.offset_or_value = offset;
|
|
tags.push_back(ifd);
|
|
}
|
|
|
|
// TIFF expects IFD tags are sorted.
|
|
std::sort(tags.begin(), tags.end(), IFDComparator);
|
|
|
|
std::ostringstream ifd_os;
|
|
|
|
unsigned short num_fields = static_cast<unsigned short>(tags.size());
|
|
|
|
Write2(num_fields, &ifd_os, swap_endian_);
|
|
|
|
{
|
|
size_t typesize_table[] = {1, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8, 4};
|
|
|
|
for (size_t i = 0; i < tags.size(); i++) {
|
|
const IFDTag &ifd = tags[i];
|
|
Write2(ifd.tag, &ifd_os, swap_endian_);
|
|
Write2(ifd.type, &ifd_os, swap_endian_);
|
|
Write4(ifd.count, &ifd_os, swap_endian_);
|
|
|
|
size_t len =
|
|
ifd.count * (typesize_table[(ifd.type) < 14 ? (ifd.type) : 0]);
|
|
if (len > 4) {
|
|
// Store offset value.
|
|
unsigned int ifd_offt = ifd.offset_or_value + data_base_offset;
|
|
Write4(ifd_offt, &ifd_os, swap_endian_);
|
|
} else {
|
|
// less than 4 bytes = store data itself.
|
|
|
|
if (len == 1) {
|
|
const unsigned char value =
|
|
*(reinterpret_cast<const unsigned char *>(&ifd.offset_or_value));
|
|
Write1(value, &ifd_os);
|
|
unsigned char pad = 0;
|
|
Write1(pad, &ifd_os);
|
|
Write1(pad, &ifd_os);
|
|
Write1(pad, &ifd_os);
|
|
} else if (len == 2) {
|
|
const unsigned short value =
|
|
*(reinterpret_cast<const unsigned short *>(&ifd.offset_or_value));
|
|
Write2(value, &ifd_os, swap_endian_);
|
|
const unsigned short pad = 0;
|
|
Write2(pad, &ifd_os, swap_endian_);
|
|
} else if (len == 4) {
|
|
const unsigned int value =
|
|
*(reinterpret_cast<const unsigned int *>(&ifd.offset_or_value));
|
|
Write4(value, &ifd_os, swap_endian_);
|
|
} else {
|
|
assert(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
ofs->write(ifd_os.str().c_str(),
|
|
static_cast<std::streamsize>(ifd_os.str().length()));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// -------------------------------------------
|
|
|
|
DNGWriter::DNGWriter(bool big_endian) : dng_big_endian_(big_endian) {
|
|
swap_endian_ = (IsBigEndian() != dng_big_endian_);
|
|
}
|
|
|
|
bool DNGWriter::WriteToFile(const char *filename, std::string *err) const {
|
|
std::ofstream ofs(filename, std::ostream::binary);
|
|
|
|
if (!ofs) {
|
|
if (err) {
|
|
(*err) = "Failed to open file.\n";
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
std::ostringstream header;
|
|
bool ret = WriteTIFFVersionHeader(&header, dng_big_endian_);
|
|
if (!ret) {
|
|
return false;
|
|
}
|
|
|
|
if (images_.size() == 0) {
|
|
if (err) {
|
|
(*err) = "No image added for writing.\n";
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// 1. Compute offset and data size(exclude TIFF header bytes)
|
|
size_t data_len = 0;
|
|
size_t strip_offset = 0;
|
|
std::vector<size_t> data_offset_table;
|
|
std::vector<size_t> strip_offset_table;
|
|
for (size_t i = 0; i < images_.size(); i++) {
|
|
strip_offset = data_len + images_[i]->GetStripOffset();
|
|
data_offset_table.push_back(data_len);
|
|
strip_offset_table.push_back(strip_offset);
|
|
data_len += images_[i]->GetDataSize();
|
|
}
|
|
|
|
// 2. Write offset to ifd table.
|
|
const unsigned int ifd_offset =
|
|
kHeaderSize + static_cast<unsigned int>(data_len);
|
|
Write4(ifd_offset, &header, swap_endian_);
|
|
|
|
assert(header.str().length() == 8);
|
|
|
|
// std::cout << "ifd_offset " << ifd_offset << std::endl;
|
|
// std::cout << "data_len " << data_os_.str().length() << std::endl;
|
|
// std::cout << "ifd_len " << ifd_os_.str().length() << std::endl;
|
|
// std::cout << "swap endian " << swap_endian_ << std::endl;
|
|
|
|
// 3. Write header
|
|
ofs.write(header.str().c_str(),
|
|
static_cast<std::streamsize>(header.str().length()));
|
|
|
|
// 4. Write image and meta data
|
|
// TODO(syoyo): Write IFD first, then image/meta data
|
|
for (size_t i = 0; i < images_.size(); i++) {
|
|
bool ok = images_[i]->WriteDataToStream(&ofs, err);
|
|
if (!ok) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// 5. Write IFD entries;
|
|
for (size_t i = 0; i < images_.size(); i++) {
|
|
bool ok = images_[i]->WriteIFDToStream(
|
|
static_cast<unsigned int>(data_offset_table[i]),
|
|
static_cast<unsigned int>(strip_offset_table[i]), &ofs, err);
|
|
if (!ok) {
|
|
return false;
|
|
}
|
|
|
|
unsigned int next_ifd_offset =
|
|
static_cast<unsigned int>(ofs.tellp()) + sizeof(unsigned int);
|
|
|
|
if (i == (images_.size() - 1)) {
|
|
// Write zero as IFD offset(= end of data)
|
|
next_ifd_offset = 0;
|
|
}
|
|
|
|
if (swap_endian_) {
|
|
swap4(&next_ifd_offset);
|
|
}
|
|
|
|
ofs.write(reinterpret_cast<const char *>(&next_ifd_offset), 4);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
#ifdef __clang__
|
|
#pragma clang diagnostic pop
|
|
#endif
|
|
|
|
} // namespace tinydngwriter
|
|
|
|
#endif // TINY_DNG_WRITER_IMPLEMENTATION
|