#include "TIFF.h" #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"} }; 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; } template std::ostream& operator<<(std::ostream& stream, const std::vector& vector) { std::ios_base::fmtflags f(stream.flags()); if(vector.size() > 10) { stream << " (" + std::to_string(vector.size()) + ")"; } else { stream << std::setw(0) << "[" << vector[0]; std::accumulate(std::next(vector.begin()), vector.end(), std::string(), [&stream](std::string a, T elem){ stream << ", " << elem; }); stream << "]"; } stream.flags(f); 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; 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: _entriesMap.emplace(entry.tag, EntryValueType(std::string(filePtr + entry.valueOrOffset))); 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(29) << description << " | " << std::setw(80) << 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; }