335 lines
12 KiB
C++
335 lines
12 KiB
C++
#include "TIFF.h"
|
|
#include <iostream>
|
|
#include <fstream>
|
|
#include <iomanip>
|
|
#include <algorithm>
|
|
#include <numeric>
|
|
#include <sstream>
|
|
#include <type_traits>
|
|
|
|
static const std::map<TiffTag, std::string> 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<typename T>
|
|
std::ostream& operator<<(std::ostream& stream, const RationalType<T>& 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<typename T>
|
|
std::ostream& operator<<(std::ostream& stream, const std::vector<T>& vector) {
|
|
size_t vSize = vector.size();
|
|
if(vSize > 10) {
|
|
stream << "<vector> (" + 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<CFAPatternDim>& vector) {
|
|
stream << std::to_string(vector[0]) + "x" + std::to_string(vector[1]);
|
|
return stream;
|
|
}
|
|
|
|
std::ostream& operator<<(std::ostream& stream, const std::vector<CFAPattern>& 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<typename T>
|
|
void ImageFileDirectory::parseValueImpl(char *filePtr, IfdEntry *entry) {
|
|
size_t sizeOfValue = sizeOfType(entry->type)*entry->numberOfValues;
|
|
bool isArray = entry->numberOfValues > 1;
|
|
T* dataPtr = reinterpret_cast<T*>(sizeOfValue <= 4 ? (char*)&entry->valueOrOffset : filePtr + entry->valueOrOffset);
|
|
|
|
EntryValueType value;
|
|
if (isArray) {
|
|
value = std::vector<T>(dataPtr, dataPtr + entry->numberOfValues);
|
|
} else {
|
|
value = *dataPtr;
|
|
}
|
|
|
|
_entriesMap.emplace(entry->tag, value);
|
|
}
|
|
|
|
template<typename T>
|
|
void ImageFileDirectory::parseValue(char *filePtr, IfdEntry *entry) {
|
|
switch (entry->tag) {
|
|
case COMPRESSION: parseValueImpl<Compression>(filePtr, entry); break;
|
|
case PHOTOMETRIC_INTERPRETATION: parseValueImpl<PhotometricInterpretation>(filePtr, entry); break;
|
|
case RESOLUTION_UNIT: parseValueImpl<ResolutionUnit>(filePtr, entry); break;
|
|
case ORIENTATION: parseValueImpl<Orientation>(filePtr, entry); break;
|
|
case PLANAR_CONFIGURATION: parseValueImpl<PlanarConfiguration>(filePtr, entry); break;
|
|
case CFA_REPEAT_PATTERN_DIM: parseValueImpl<CFAPatternDim>(filePtr, entry); break;
|
|
case CFA_PATTERN: parseValueImpl<CFAPattern>(filePtr, entry); break;
|
|
case CFA_LAYOUT: parseValueImpl<CFALayout>(filePtr, entry); break;
|
|
default: parseValueImpl<T>(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<uint8_t>(filePtr, &entry); break;
|
|
case SHORT: parseValue<uint16_t>(filePtr, &entry); break;
|
|
case LONG: parseValue<uint32_t>(filePtr, &entry); break;
|
|
case RATIONAL: parseValue<Rational>(filePtr, &entry); break;
|
|
case SBYTE: parseValue<int8_t>(filePtr, &entry); break;
|
|
case SSHORT: parseValue<int16_t>(filePtr, &entry); break;
|
|
case SLONG: parseValue<int32_t>(filePtr, &entry); break;
|
|
case SRATIONAL: parseValue<SRational>(filePtr, &entry); break;
|
|
case UNDEFINED: parseValue<uint8_t>(filePtr, &entry); break;
|
|
case FLOAT: parseValue<float>(filePtr, &entry); break;
|
|
case DOUBLE: parseValue<double>(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::EntryValueType> ImageFileDirectory::operator[](TiffTag tag) const {
|
|
auto iter = _entriesMap.find(tag);
|
|
if(iter != _entriesMap.end())
|
|
return std::optional<ImageFileDirectory::EntryValueType>(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<char[]>(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<ImageFileDirectory> TIFF::ifds() const {
|
|
return _ifds;
|
|
}
|