#include "TIFF.h" #include #include #include #include #include #include #include static const std::map tagDescriptions = { {NEW_SUBFILE_TYPE, "New subfile type"}, {IMAGE_WIDTH, "Image width"}, {IMAGE_LENGTH, "Image height"}, {BITS_PER_SAMPLE, "Bits per sample"}, {COMPRESSION, "Compression"}, {PHOTOMETRIC_INTERPRETATION, "Photometric interpretation"}, {IMAGE_DESCRIPTION, "Image description"}, {MANUFACTURER, "Manufacturer"}, {MODEL, "Model"}, {STRIP_OFFSETS, "Strip offsets"}, {ORIENTATION, "Orientation"}, {SAMPLES_PER_PIXEL, "Samples per pixel"}, {ROWS_PER_STRIP, "Rows per strip"}, {STRIP_BYTE_COUNTS, "Strip byte counts"}, {X_RESOLUTION, "X resolution"}, {Y_RESOLUTION, "Y resolution"}, {PLANAR_CONFIGURATION, "Planar configuration"}, {RESOLUTION_UNIT, "Resolution unit"}, {SOFTWARE, "Software"}, {DATE_TIME, "Date/Time"}, {XML_PACKET, "XML Packet"}, {CFA_REPEAT_PATTERN_DIM, "CFA repeat pattern"}, {CFA_PATTERN, "CFA pattern"}, {EXPOSURE_TIME, "Exposure time (seconds)"}, {F_NUMBER, "F-Number (relative aperture)"}, {EXIF_IFD, "EXIF IFD offset"}, {ISO_SPEED_RATINGS, "ISO"}, {DATE_TIME_ORIGINAL, "Date/time original"}, {FOCAL_LENGTH, "Focal length (mm)"}, {TIFF_EP_STANDARD_ID, "TIFF/EP standard ID"}, {COPYRIGHT_NOTICE, "Copyright notice"}, {DNG_VERSION, "DNG version"}, {DNG_BACKWARD_VERSION, "DNG backward version"}, {UNIQUE_CAMERA_MODEL, "Unique camera model"}, {CFA_PLANE_COLOR, "CFA plane color"}, {CFA_LAYOUT, "CFA spatial layout"}, {BLACK_LEVEL_REPEAT_DIM, "Black level repeat pattern size"}, {BLACK_LEVEL, "Black level"}, {WHITE_LEVEL, "White level"}, {DEFAULT_SCALE, "Default scale"}, {DEFAULT_CROP_ORIGIN, "Default crop origin"}, {DEFAULT_CROP_SIZE, "Default crop size"}, {COLOR_MATRIX_1, "Color matrix 1"}, {COLOR_MATRIX_2, "Color matrix 2"}, {CAMERA_CALIBRATION_1, "Camera calibration 1"}, {CAMERA_CALIBRATION_2, "Camera calibration 2"}, {AS_SHOT_NEUTRAL, "White balance at time of capture"}, {BASELINE_EXPOSURE, "Baseline exposure (EV units)"}, {CALIBRATION_ILLUMINANT_1, "Calibration illuminant 1"}, {CALIBRATION_ILLUMINANT_2, "Calibration illuminant 2"}, {ACTIVE_AREA, "Active area of sensor (pixels)"}, {FORWARD_MATRIX_1, "Forward matrix 1"}, {FORWARD_MATRIX_2, "Forward matrix 2"}, {OPCODE_LIST_1, "Opcode list 1"}, {OPCODE_LIST_2, "Opcode list 2"}, {OPCODE_LIST_3, "Opcode list 3"}, {NOISE_PROFILE, "Noise profile"}, {GPS_INFO, "GPS info (offset to IFD)"} }; std::ostream &operator<<(std::ostream &stream, Compression compression) { switch (compression) { case NO_COMPRESSION: stream << "no compression"; break; case HUFFMAN: stream << "1-Dimensional modified huffman run length encoding"; break; case PACK_BITS: stream << "PackBits"; break; default: stream << "undefined"; break; } return stream; } std::ostream& operator<<(std::ostream& stream, PhotometricInterpretation pr) { switch(pr) { case WHITE_IS_ZERO: stream << "white is zero"; break; case BLACK_IS_ZERO: stream << "black is zero"; break; case RGB: stream << "RGB"; break; case PALETTE_COLOR: stream << "palette color"; break; case TRANSPARENCY_MASK: stream << "transparency mask"; break; case YCBCR: stream << "YCbCr (YUV)"; break; case COLOR_FILTER_ARRAY: stream << "CFA (color filter array)"; break; case LINEAR_RAW: stream << "linear raw"; break; default: stream << "undefined"; break; } return stream; } template std::ostream& operator<<(std::ostream& stream, const RationalType& rational) { stream << 1.0*rational.numerator/rational.denominator; return stream; } std::ostream& operator<<(std::ostream& stream, ResolutionUnit unit) { switch(unit) { case NO_UNIT: stream << "no unit"; break; case INCH: stream << "inch"; break; case CENTIMETER: stream << "centimeter"; break; default: stream << "undefined"; break; } return stream; } std::ostream& operator<<(std::ostream& stream, Orientation orientation) { switch (orientation) { case NORMAL: stream << "normal"; break; case FLIP_HORIZONTAL: stream << "flip horizontal"; break; case FLIP_BOTH: stream << "flip both"; break; case FLIP_VERTICAL: stream << "flip vertical"; break; case NORMAL_ROTATED: stream << "normal (rotated)"; break; case FLIP_HORIZONTAL_ROTATED: stream << "flip horizontal (rotated)"; break; case FLIP_BOTH_ROTATED: stream << "flip both (rotated)"; break; case FLIP_VERTICAL_ROTATED: stream << "flip vertical (rotated)"; break; } return stream; } std::ostream& operator<<(std::ostream& stream, PlanarConfiguration conf) { switch (conf) { case CHUNKY: stream << "chunky"; break; case PLANAR: stream << "planar"; break; } return stream; } std::ostream& operator<<(std::ostream& stream, CFALayout layout) { switch (layout) { case RECTANGULAR: stream << "rectangular"; break; case STAGGERED_LAYOUT_A: stream << "even columns are offset down by 1/2 row"; break; case STAGGERED_LAYOUT_B: stream << "even columns are offset up by 1/2 row"; break; case STAGGERED_LAYOUT_C: stream << "even rows are offset right by 1/2 column"; break; case STAGGERED_LAYOUT_D: stream << "even rows are offset left by 1/2 column"; break; case STAGGERED_LAYOUT_E: stream << "even rows are offset up by 1/2 row, even columns are offset left by 1/2 column"; break; case STAGGERED_LAYOUT_F: stream << "even rows are offset up by 1/2 row, even columns are offset right by 1/2 column"; break; case STAGGERED_LAYOUT_G: stream << "even rows are offset down by 1/2 row, even columns are offset left by 1/2 column"; break; case STAGGERED_LAYOUT_H: stream << "even rows are offset down by 1/2 row, even columns are offset right by 1/2 column"; break; } return stream; } std::ostream& operator<<(std::ostream& stream, uint8_t value) { stream << (int)value; return stream; } template std::ostream& operator<<(std::ostream& stream, const std::vector& vector) { size_t vSize = vector.size(); if(vSize > 10) { stream << " (" + std::to_string(vector.size()) + ")"; } else { std::stringstream ss; ss << std::setw(0) << "["; for(size_t i = 0; i < vSize; ++i) { ss << vector[i]; if(i < vSize - 1) { ss << ", "; } } ss << "]"; stream << ss.str(); } return stream; } std::ostream& operator<<(std::ostream& stream, const std::vector& vector) { stream << std::to_string(vector[0]) + "x" + std::to_string(vector[1]); return stream; } std::ostream& operator<<(std::ostream& stream, const std::vector& vector) { std::string pattern; for(auto filter: vector) { switch (filter) { case RED: pattern += "R"; break; case GREEN: pattern += "G"; break; case BLUE: pattern += "B"; break; case CYAN: pattern += "C"; break; case MAGENTA: pattern += "M"; break; case YELLOW: pattern += "Y"; break; case WHITE: pattern += "W"; break; } } stream << pattern; return stream; } template void ImageFileDirectory::parseValueImpl(char *filePtr, IfdEntry *entry) { size_t sizeOfValue = sizeOfType(entry->type)*entry->numberOfValues; bool isArray = entry->numberOfValues > 1; T* dataPtr = reinterpret_cast(sizeOfValue <= 4 ? (char*)&entry->valueOrOffset : filePtr + entry->valueOrOffset); EntryValueType value; if (isArray) { value = std::vector(dataPtr, dataPtr + entry->numberOfValues); } else { value = *dataPtr; } _entriesMap.emplace(entry->tag, value); } template void ImageFileDirectory::parseValue(char *filePtr, IfdEntry *entry) { switch (entry->tag) { case COMPRESSION: parseValueImpl(filePtr, entry); break; case PHOTOMETRIC_INTERPRETATION: parseValueImpl(filePtr, entry); break; case RESOLUTION_UNIT: parseValueImpl(filePtr, entry); break; case ORIENTATION: parseValueImpl(filePtr, entry); break; case PLANAR_CONFIGURATION: parseValueImpl(filePtr, entry); break; case CFA_REPEAT_PATTERN_DIM: parseValueImpl(filePtr, entry); break; case CFA_PATTERN: parseValueImpl(filePtr, entry); break; case CFA_LAYOUT: parseValueImpl(filePtr, entry); break; default: parseValueImpl(filePtr, entry); break; } } ImageFileDirectory::ImageFileDirectory(char* ptr, char* filePtr) { _entriesCount = *(uint16_t*)ptr; _entries = (IfdEntry*)(ptr + 2); _nextIfdOffset = *(uint32_t*)(ptr + 2 + _entriesCount * sizeof(IfdEntry)); for(size_t i = 0; i < _entriesCount; ++i) { auto entry = _entries[i]; switch (entry.type) { case ASCII: { auto str = entry.numberOfValues > 1 ? std::string(filePtr + entry.valueOrOffset) : std::string(); _entriesMap.emplace(entry.tag, EntryValueType(str)); break; } case BYTE: parseValue(filePtr, &entry); break; case SHORT: parseValue(filePtr, &entry); break; case LONG: parseValue(filePtr, &entry); break; case RATIONAL: parseValue(filePtr, &entry); break; case SBYTE: parseValue(filePtr, &entry); break; case SSHORT: parseValue(filePtr, &entry); break; case SLONG: parseValue(filePtr, &entry); break; case SRATIONAL: parseValue(filePtr, &entry); break; case UNDEFINED: parseValue(filePtr, &entry); break; case FLOAT: parseValue(filePtr, &entry); break; case DOUBLE: parseValue(filePtr, &entry); break; } } } uint32_t ImageFileDirectory::nextOffset() const { return _nextIfdOffset; } size_t ImageFileDirectory::sizeOfType(FieldType type) const { switch (type) { case BYTE: return 1; case ASCII: return 1; case SHORT: return 2; case LONG: return 4; case RATIONAL: return 8; case SBYTE: return 1; case UNDEFINED: return 1; case SSHORT: return 2; case SLONG: return 4; case SRATIONAL: return 8; case FLOAT: return 4; case DOUBLE: return 8; } return 0; } std::optional ImageFileDirectory::operator[](TiffTag tag) const { auto iter = _entriesMap.find(tag); if(iter != _entriesMap.end()) return std::optional(iter->second); else return std::nullopt; } void ImageFileDirectory::dumpInfo() const { std::cout << "=== IFD ====================================================================================================================================" << std::endl; std::cout << "| Tag | Description | Value |" << std::endl; std::cout << "---------------------------------------------------------------------------------------------------------------------------------------------" << std::endl; std::ios state(nullptr); state.copyfmt(std::cout); for(auto const& [key, val]: _entriesMap) { std::visit([key](auto&& arg) { auto iter = tagDescriptions.find(key); std::string description = iter != tagDescriptions.end() ? iter->second : ""; std::cout << "| " << std::left << std::setw(5) << key << " | " << std::setw(32) << description << " | " << std::setw(94) << arg << " |" << std::endl; }, val); } std::cout.copyfmt(state); } TIFF::TIFF(std::string fileName) { std::ifstream stream(fileName, std::ios::binary | std::ios::ate); std::streamsize size = stream.tellg(); stream.seekg(0, std::ios::beg); _data = std::make_unique(size); if(!stream.read((char*)_data.get(), size)) { throw std::runtime_error("error opening TIFF file: " + fileName); } auto header = (TIFFHeader*)_data.get(); if(header->magic != TIFF_MAGIC) { throw std::runtime_error("wrong magic number"); } uint32_t ifdOffset = header->firstIfdOffset; while(ifdOffset != 0) { auto ifd = ImageFileDirectory(_data.get() + ifdOffset, _data.get()); _ifds.push_back(ifd); ifdOffset = ifd.nextOffset(); } } std::vector TIFF::ifds() const { return _ifds; }