|
|
- //
- // 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
|