diff options
Diffstat (limited to 'Build/source/utils/asymptote/cudareflect/tinyexr/examples/exr2fptiff/tiny_dng_writer.h')
-rw-r--r-- | Build/source/utils/asymptote/cudareflect/tinyexr/examples/exr2fptiff/tiny_dng_writer.h | 1356 |
1 files changed, 1356 insertions, 0 deletions
diff --git a/Build/source/utils/asymptote/cudareflect/tinyexr/examples/exr2fptiff/tiny_dng_writer.h b/Build/source/utils/asymptote/cudareflect/tinyexr/examples/exr2fptiff/tiny_dng_writer.h new file mode 100644 index 00000000000..6156821f3c8 --- /dev/null +++ b/Build/source/utils/asymptote/cudareflect/tinyexr/examples/exr2fptiff/tiny_dng_writer.h @@ -0,0 +1,1356 @@ +// +// 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 |