From fdbab550ad997053a8dfefb4e527dbd6ceb701ec Mon Sep 17 00:00:00 2001 From: shun-iwasawa Date: Oct 18 2018 02:56:20 +0000 Subject: load jpeg dpi from exif, save to jfif (#2322) --- diff --git a/toonz/sources/common/tiio/tiio_jpg.cpp b/toonz/sources/common/tiio/tiio_jpg.cpp index ec3917c..ee31928 100644 --- a/toonz/sources/common/tiio/tiio_jpg.cpp +++ b/toonz/sources/common/tiio/tiio_jpg.cpp @@ -9,6 +9,7 @@ //#include "../compatibility/tnz4.h" #include "tiio_jpg.h" +#include "tiio_jpg_exif.h" #include "tproperty.h" #include "tpixel.h" @@ -76,10 +77,36 @@ void JpgReader::open(FILE *file) { m_cinfo.err->error_exit = tnz_error_exit; jpeg_create_decompress(&m_cinfo); + m_chan = file; jpeg_stdio_src(&m_cinfo, m_chan); + jpeg_save_markers(&m_cinfo, JPEG_APP0 + 1, 0xffff); // EXIF bool ret = jpeg_read_header(&m_cinfo, TRUE); - ret = ret && jpeg_start_decompress(&m_cinfo); + + bool resolutionFoundInExif = false; + jpeg_saved_marker_ptr mark; + for (mark = m_cinfo.marker_list; NULL != mark; mark = mark->next) { + switch (mark->marker) { + case JPEG_APP0 + 1: // EXIF + JpgExifReader exifReader; + exifReader.process_EXIF(mark->data - 2, mark->data_length); + if (exifReader.containsResolution()) { + int resUnit = exifReader.getResolutionUnit(); + resolutionFoundInExif = true; + if (resUnit == 1 || resUnit == 2) { // no unit(1) or inch(2) + m_info.m_dpix = (double)exifReader.getXResolution(); + m_info.m_dpiy = (double)exifReader.getYResolution(); + } else if (resUnit == 3) { // centimeter(3); + m_info.m_dpix = (double)exifReader.getXResolution() * 2.54; + m_info.m_dpiy = (double)exifReader.getYResolution() * 2.54; + } else // ignore millimeter(4) and micrometer(5) cases for now + resolutionFoundInExif = false; + } + break; + } + } + + ret = ret && jpeg_start_decompress(&m_cinfo); if (!ret) return; int row_stride = m_cinfo.output_width * m_cinfo.output_components; @@ -91,6 +118,17 @@ void JpgReader::open(FILE *file) { m_info.m_samplePerPixel = 3; m_info.m_valid = true; m_isOpen = true; + + if (!resolutionFoundInExif && (m_cinfo.saw_JFIF_marker != 0) && + (m_cinfo.X_density != 1) && (m_cinfo.Y_density != 1)) { + if (m_cinfo.density_unit == 1) { + m_info.m_dpix = (double)m_cinfo.X_density; + m_info.m_dpiy = (double)m_cinfo.Y_density; + } else if (m_cinfo.density_unit == 2) { + m_info.m_dpix = (double)m_cinfo.X_density * 2.54; + m_info.m_dpiy = (double)m_cinfo.Y_density * 2.54; + } + } } void JpgReader::readLine(char *buffer, int x0, int x1, int shrink) { @@ -164,6 +202,16 @@ public: m_cinfo.in_color_space = JCS_RGB; jpeg_set_defaults(&m_cinfo); + + // save dpi always in JFIF header, instead of EXIF + m_cinfo.write_JFIF_header = 1; + m_cinfo.JFIF_major_version = 1; + m_cinfo.JFIF_minor_version = 2; + m_cinfo.X_density = (UINT16)info.m_dpix; + m_cinfo.Y_density = (UINT16)info.m_dpiy; + m_cinfo.density_unit = 1; // dot per inch + m_cinfo.write_Adobe_marker = 0; + if (!m_properties) m_properties = new Tiio::JpgWriterProperties(); jpeg_set_quality( diff --git a/toonz/sources/common/tiio/tiio_jpg_exif.cpp b/toonz/sources/common/tiio/tiio_jpg_exif.cpp new file mode 100644 index 0000000..c7613dd --- /dev/null +++ b/toonz/sources/common/tiio/tiio_jpg_exif.cpp @@ -0,0 +1,1057 @@ +/*------------------------------------------------------------- +tiio_jpg_exif.cpp +Based on source code of a public domain software "Exif Jpeg header manipulation +tool (jhead)" by Matthias Wandel. +For now it is used only for obtaining resolution values. +http://www.sentex.net/~mwandel/jhead/ +-------------------------------------------------------------*/ +#include "tiio_jpg_exif.h" +#include +#include + +// for debug +#define ShowTags 0 +#define DumpExifMap 0 + +namespace { + +typedef unsigned char uchar; +const int BytesPerFormat[] = {0, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8}; +//-------------------------------------------------------------------------- +// Describes tag values +const int TAG_X_RESOLUTION = 0x011A; +const int TAG_Y_RESOLUTION = 0x011B; +const int TAG_RESOLUTION_UNIT = 0x0128; +#ifdef ReadAllTags +const int TAG_INTEROP_INDEX = 0x0001; +const int TAG_INTEROP_VERSION = 0x0002; +const int TAG_IMAGE_WIDTH = 0x0100; +const int TAG_IMAGE_LENGTH = 0x0101; +const int TAG_BITS_PER_SAMPLE = 0x0102; +const int TAG_COMPRESSION = 0x0103; +const int TAG_PHOTOMETRIC_INTERP = 0x0106; +const int TAG_FILL_ORDER = 0x010A; +const int TAG_DOCUMENT_NAME = 0x010D; +const int TAG_IMAGE_DESCRIPTION = 0x010E; +const int TAG_MAKE = 0x010F; +const int TAG_MODEL = 0x0110; +const int TAG_SRIP_OFFSET = 0x0111; +const int TAG_ORIENTATION = 0x0112; +const int TAG_SAMPLES_PER_PIXEL = 0x0115; +const int TAG_ROWS_PER_STRIP = 0x0116; +const int TAG_STRIP_BYTE_COUNTS = 0x0117; +const int TAG_PLANAR_CONFIGURATION = 0x011C; +const int TAG_TRANSFER_FUNCTION = 0x012D; +const int TAG_SOFTWARE = 0x0131; +const int TAG_DATETIME = 0x0132; +const int TAG_ARTIST = 0x013B; +const int TAG_WHITE_POINT = 0x013E; +const int TAG_PRIMARY_CHROMATICITIES = 0x013F; +const int TAG_TRANSFER_RANGE = 0x0156; +const int TAG_JPEG_PROC = 0x0200; +const int TAG_THUMBNAIL_OFFSET = 0x0201; +const int TAG_THUMBNAIL_LENGTH = 0x0202; +const int TAG_Y_CB_CR_COEFFICIENTS = 0x0211; +const int TAG_Y_CB_CR_SUB_SAMPLING = 0x0212; +const int TAG_Y_CB_CR_POSITIONING = 0x0213; +const int TAG_REFERENCE_BLACK_WHITE = 0x0214; +const int TAG_RELATED_IMAGE_WIDTH = 0x1001; +const int TAG_RELATED_IMAGE_LENGTH = 0x1002; +const int TAG_CFA_REPEAT_PATTERN_DIM = 0x828D; +const int TAG_CFA_PATTERN1 = 0x828E; +const int TAG_BATTERY_LEVEL = 0x828F; +const int TAG_COPYRIGHT = 0x8298; +const int TAG_EXPOSURETIME = 0x829A; +const int TAG_FNUMBER = 0x829D; +const int TAG_IPTC_NAA = 0x83BB; +const int TAG_EXIF_OFFSET = 0x8769; +const int TAG_INTER_COLOR_PROFILE = 0x8773; +const int TAG_EXPOSURE_PROGRAM = 0x8822; +const int TAG_SPECTRAL_SENSITIVITY = 0x8824; +const int TAG_GPSINFO = 0x8825; +const int TAG_ISO_EQUIVALENT = 0x8827; +const int TAG_OECF = 0x8828; +const int TAG_EXIF_VERSION = 0x9000; +const int TAG_DATETIME_ORIGINAL = 0x9003; +const int TAG_DATETIME_DIGITIZED = 0x9004; +const int TAG_COMPONENTS_CONFIG = 0x9101; +const int TAG_CPRS_BITS_PER_PIXEL = 0x9102; +const int TAG_SHUTTERSPEED = 0x9201; +const int TAG_APERTURE = 0x9202; +const int TAG_BRIGHTNESS_VALUE = 0x9203; +const int TAG_EXPOSURE_BIAS = 0x9204; +const int TAG_MAXAPERTURE = 0x9205; +const int TAG_SUBJECT_DISTANCE = 0x9206; +const int TAG_METERING_MODE = 0x9207; +const int TAG_LIGHT_SOURCE = 0x9208; +const int TAG_FLASH = 0x9209; +const int TAG_FOCALLENGTH = 0x920A; +const int TAG_SUBJECTAREA = 0x9214; +const int TAG_MAKER_NOTE = 0x927C; +const int TAG_USERCOMMENT = 0x9286; +const int TAG_SUBSEC_TIME = 0x9290; +const int TAG_SUBSEC_TIME_ORIG = 0x9291; +const int TAG_SUBSEC_TIME_DIG = 0x9292; + +const int TAG_WINXP_TITLE = 0x9c9b; // Windows XP - not part of exif standard. +const int TAG_WINXP_COMMENT = + 0x9c9c; // Windows XP - not part of exif standard. +const int TAG_WINXP_AUTHOR = 0x9c9d; // Windows XP - not part of exif standard. +const int TAG_WINXP_KEYWORDS = + 0x9c9e; // Windows XP - not part of exif standard. +const int TAG_WINXP_SUBJECT = + 0x9c9f; // Windows XP - not part of exif standard. + +const int TAG_FLASH_PIX_VERSION = 0xA000; +const int TAG_COLOR_SPACE = 0xA001; +const int TAG_PIXEL_X_DIMENSION = 0xA002; +const int TAG_PIXEL_Y_DIMENSION = 0xA003; +const int TAG_RELATED_AUDIO_FILE = 0xA004; +const int TAG_INTEROP_OFFSET = 0xA005; +const int TAG_FLASH_ENERGY = 0xA20B; +const int TAG_SPATIAL_FREQ_RESP = 0xA20C; +const int TAG_FOCAL_PLANE_XRES = 0xA20E; +const int TAG_FOCAL_PLANE_YRES = 0xA20F; +const int TAG_FOCAL_PLANE_UNITS = 0xA210; +const int TAG_SUBJECT_LOCATION = 0xA214; +const int TAG_EXPOSURE_INDEX = 0xA215; +const int TAG_SENSING_METHOD = 0xA217; +const int TAG_FILE_SOURCE = 0xA300; +const int TAG_SCENE_TYPE = 0xA301; +const int TAG_CFA_PATTERN = 0xA302; +const int TAG_CUSTOM_RENDERED = 0xA401; +const int TAG_EXPOSURE_MODE = 0xA402; +const int TAG_WHITEBALANCE = 0xA403; +const int TAG_DIGITALZOOMRATIO = 0xA404; +const int TAG_FOCALLENGTH_35MM = 0xA405; +const int TAG_SCENE_CAPTURE_TYPE = 0xA406; +const int TAG_GAIN_CONTROL = 0xA407; +const int TAG_CONTRAST = 0xA408; +const int TAG_SATURATION = 0xA409; +const int TAG_SHARPNESS = 0xA40A; +const int TAG_DISTANCE_RANGE = 0xA40C; +const int TAG_IMAGE_UNIQUE_ID = 0xA420; +#endif + +typedef struct { + unsigned short Tag; + char *Desc; +} TagTable_t; + +const TagTable_t TagTable[] = { + {TAG_X_RESOLUTION, "XResolution"}, + {TAG_Y_RESOLUTION, "YResolution"}, + {TAG_RESOLUTION_UNIT, "ResolutionUnit"}, +#ifdef ReadAllTags + {TAG_INTEROP_INDEX, "InteropIndex"}, + {TAG_INTEROP_VERSION, "InteropVersion"}, + {TAG_IMAGE_WIDTH, "ImageWidth"}, + {TAG_IMAGE_LENGTH, "ImageLength"}, + {TAG_BITS_PER_SAMPLE, "BitsPerSample"}, + {TAG_COMPRESSION, "Compression"}, + {TAG_PHOTOMETRIC_INTERP, "PhotometricInterpretation"}, + {TAG_FILL_ORDER, "FillOrder"}, + {TAG_DOCUMENT_NAME, "DocumentName"}, + {TAG_IMAGE_DESCRIPTION, "ImageDescription"}, + {TAG_MAKE, "Make"}, + {TAG_MODEL, "Model"}, + {TAG_SRIP_OFFSET, "StripOffsets"}, + {TAG_ORIENTATION, "Orientation"}, + {TAG_SAMPLES_PER_PIXEL, "SamplesPerPixel"}, + {TAG_ROWS_PER_STRIP, "RowsPerStrip"}, + {TAG_STRIP_BYTE_COUNTS, "StripByteCounts"}, + {TAG_PLANAR_CONFIGURATION, "PlanarConfiguration"}, + {TAG_TRANSFER_FUNCTION, "TransferFunction"}, + {TAG_SOFTWARE, "Software"}, + {TAG_DATETIME, "DateTime"}, + {TAG_ARTIST, "Artist"}, + {TAG_WHITE_POINT, "WhitePoint"}, + {TAG_PRIMARY_CHROMATICITIES, "PrimaryChromaticities"}, + {TAG_TRANSFER_RANGE, "TransferRange"}, + {TAG_JPEG_PROC, "JPEGProc"}, + {TAG_THUMBNAIL_OFFSET, "ThumbnailOffset"}, + {TAG_THUMBNAIL_LENGTH, "ThumbnailLength"}, + {TAG_Y_CB_CR_COEFFICIENTS, "YCbCrCoefficients"}, + {TAG_Y_CB_CR_SUB_SAMPLING, "YCbCrSubSampling"}, + {TAG_Y_CB_CR_POSITIONING, "YCbCrPositioning"}, + {TAG_REFERENCE_BLACK_WHITE, "ReferenceBlackWhite"}, + {TAG_RELATED_IMAGE_WIDTH, "RelatedImageWidth"}, + {TAG_RELATED_IMAGE_LENGTH, "RelatedImageLength"}, + {TAG_CFA_REPEAT_PATTERN_DIM, "CFARepeatPatternDim"}, + {TAG_CFA_PATTERN1, "CFAPattern"}, + {TAG_BATTERY_LEVEL, "BatteryLevel"}, + {TAG_COPYRIGHT, "Copyright"}, + {TAG_EXPOSURETIME, "ExposureTime"}, + {TAG_FNUMBER, "FNumber"}, + {TAG_IPTC_NAA, "IPTC/NAA"}, + {TAG_EXIF_OFFSET, "ExifOffset"}, + {TAG_INTER_COLOR_PROFILE, "InterColorProfile"}, + {TAG_EXPOSURE_PROGRAM, "ExposureProgram"}, + {TAG_SPECTRAL_SENSITIVITY, "SpectralSensitivity"}, + {TAG_GPSINFO, "GPS Dir offset"}, + {TAG_ISO_EQUIVALENT, "ISOSpeedRatings"}, + {TAG_OECF, "OECF"}, + {TAG_EXIF_VERSION, "ExifVersion"}, + {TAG_DATETIME_ORIGINAL, "DateTimeOriginal"}, + {TAG_DATETIME_DIGITIZED, "DateTimeDigitized"}, + {TAG_COMPONENTS_CONFIG, "ComponentsConfiguration"}, + {TAG_CPRS_BITS_PER_PIXEL, "CompressedBitsPerPixel"}, + {TAG_SHUTTERSPEED, "ShutterSpeedValue"}, + {TAG_APERTURE, "ApertureValue"}, + {TAG_BRIGHTNESS_VALUE, "BrightnessValue"}, + {TAG_EXPOSURE_BIAS, "ExposureBiasValue"}, + {TAG_MAXAPERTURE, "MaxApertureValue"}, + {TAG_SUBJECT_DISTANCE, "SubjectDistance"}, + {TAG_METERING_MODE, "MeteringMode"}, + {TAG_LIGHT_SOURCE, "LightSource"}, + {TAG_FLASH, "Flash"}, + {TAG_FOCALLENGTH, "FocalLength"}, + {TAG_MAKER_NOTE, "MakerNote"}, + {TAG_USERCOMMENT, "UserComment"}, + {TAG_SUBSEC_TIME, "SubSecTime"}, + {TAG_SUBSEC_TIME_ORIG, "SubSecTimeOriginal"}, + {TAG_SUBSEC_TIME_DIG, "SubSecTimeDigitized"}, + {TAG_WINXP_TITLE, "Windows-XP Title"}, + {TAG_WINXP_COMMENT, "Windows-XP comment"}, + {TAG_WINXP_AUTHOR, "Windows-XP author"}, + {TAG_WINXP_KEYWORDS, "Windows-XP keywords"}, + {TAG_WINXP_SUBJECT, "Windows-XP subject"}, + {TAG_FLASH_PIX_VERSION, "FlashPixVersion"}, + {TAG_COLOR_SPACE, "ColorSpace"}, + {TAG_PIXEL_X_DIMENSION, "ExifImageWidth"}, + {TAG_PIXEL_Y_DIMENSION, "ExifImageLength"}, + {TAG_RELATED_AUDIO_FILE, "RelatedAudioFile"}, + {TAG_INTEROP_OFFSET, "InteroperabilityOffset"}, + {TAG_FLASH_ENERGY, "FlashEnergy"}, + {TAG_SPATIAL_FREQ_RESP, "SpatialFrequencyResponse"}, + {TAG_FOCAL_PLANE_XRES, "FocalPlaneXResolution"}, + {TAG_FOCAL_PLANE_YRES, "FocalPlaneYResolution"}, + {TAG_FOCAL_PLANE_UNITS, "FocalPlaneResolutionUnit"}, + {TAG_SUBJECT_LOCATION, "SubjectLocation"}, + {TAG_EXPOSURE_INDEX, "ExposureIndex"}, + {TAG_SENSING_METHOD, "SensingMethod"}, + {TAG_FILE_SOURCE, "FileSource"}, + {TAG_SCENE_TYPE, "SceneType"}, + {TAG_CFA_PATTERN, "CFA Pattern"}, + {TAG_CUSTOM_RENDERED, "CustomRendered"}, + {TAG_EXPOSURE_MODE, "ExposureMode"}, + {TAG_WHITEBALANCE, "WhiteBalance"}, + {TAG_DIGITALZOOMRATIO, "DigitalZoomRatio"}, + {TAG_FOCALLENGTH_35MM, "FocalLengthIn35mmFilm"}, + {TAG_SUBJECTAREA, "SubjectArea"}, + {TAG_SCENE_CAPTURE_TYPE, "SceneCaptureType"}, + {TAG_GAIN_CONTROL, "GainControl"}, + {TAG_CONTRAST, "Contrast"}, + {TAG_SATURATION, "Saturation"}, + {TAG_SHARPNESS, "Sharpness"}, + {TAG_DISTANCE_RANGE, "SubjectDistanceRange"}, + {TAG_IMAGE_UNIQUE_ID, "ImageUniqueId"}, +#endif +}; + +const int TAG_TABLE_SIZE = (sizeof(TagTable) / sizeof(TagTable_t)); + +const int TRUE = 1; +const int FALSE = 0; +} + +//-------------------------------------------------------------------------- +// Convert a 16 bit unsigned value from file's native byte order +//-------------------------------------------------------------------------- +int JpgExifReader::Get16u(void *Short) { + if (MotorolaOrder) { + return (((uchar *)Short)[0] << 8) | ((uchar *)Short)[1]; + } else { + return (((uchar *)Short)[1] << 8) | ((uchar *)Short)[0]; + } +} + +//-------------------------------------------------------------------------- +// Convert a 32 bit signed value from file's native byte order +//-------------------------------------------------------------------------- +int JpgExifReader::Get32s(void *Long) { + if (MotorolaOrder) { + return (((char *)Long)[0] << 24) | (((uchar *)Long)[1] << 16) | + (((uchar *)Long)[2] << 8) | (((uchar *)Long)[3] << 0); + } else { + return (((char *)Long)[3] << 24) | (((uchar *)Long)[2] << 16) | + (((uchar *)Long)[1] << 8) | (((uchar *)Long)[0] << 0); + } +} + +//-------------------------------------------------------------------------- +// Convert a 32 bit unsigned value from file's native byte order +//-------------------------------------------------------------------------- +unsigned JpgExifReader::Get32u(void *Long) { + return (unsigned)Get32s(Long) & 0xffffffff; +} + +//-------------------------------------------------------------------------- +// Display a number as one of its many formats +//-------------------------------------------------------------------------- +void JpgExifReader::PrintFormatNumber(void *ValuePtr, int Format, + int ByteCount) { + int s, n; + + for (n = 0; n < 16; n++) { + switch (Format) { + case FMT_SBYTE: + case FMT_BYTE: + printf("%02x", *(uchar *)ValuePtr); + s = 1; + break; + case FMT_USHORT: + printf("%d", Get16u(ValuePtr)); + s = 2; + break; + case FMT_ULONG: + case FMT_SLONG: + printf("%d", Get32s(ValuePtr)); + s = 4; + break; + case FMT_SSHORT: + printf("%hd", (signed short)Get16u(ValuePtr)); + s = 2; + break; + case FMT_URATIONAL: + printf("%u/%u", Get32s(ValuePtr), Get32s(4 + (char *)ValuePtr)); + s = 8; + break; + + case FMT_SRATIONAL: + printf("%d/%d", Get32s(ValuePtr), Get32s(4 + (char *)ValuePtr)); + s = 8; + break; + + case FMT_SINGLE: + printf("%f", (double)*(float *)ValuePtr); + s = 8; + break; + case FMT_DOUBLE: + printf("%f", *(double *)ValuePtr); + s = 8; + break; + default: + printf("Unknown format %d:", Format); + return; + } + ByteCount -= s; + if (ByteCount <= 0) break; + printf(", "); + ValuePtr = (void *)((char *)ValuePtr + s); + } + if (n >= 16) printf("..."); +} + +//-------------------------------------------------------------------------- +// Evaluate number, be it int, rational, or float from directory. +//-------------------------------------------------------------------------- +double JpgExifReader::ConvertAnyFormat(void *ValuePtr, int Format) { + double Value; + Value = 0; + + switch (Format) { + case FMT_SBYTE: + Value = *(signed char *)ValuePtr; + break; + case FMT_BYTE: + Value = *(uchar *)ValuePtr; + break; + + case FMT_USHORT: + Value = Get16u(ValuePtr); + break; + case FMT_ULONG: + Value = Get32u(ValuePtr); + break; + + case FMT_URATIONAL: + case FMT_SRATIONAL: { + int Num, Den; + Num = Get32s(ValuePtr); + Den = Get32s(4 + (char *)ValuePtr); + if (Den == 0) { + Value = 0; + } else { + if (Format == FMT_SRATIONAL) { + Value = (double)Num / Den; + } else { + Value = (double)(unsigned)Num / (double)(unsigned)Den; + } + } + break; + } + + case FMT_SSHORT: + Value = (signed short)Get16u(ValuePtr); + break; + case FMT_SLONG: + Value = Get32s(ValuePtr); + break; + + // Not sure if this is correct (never seen float used in Exif format) + case FMT_SINGLE: + Value = (double)*(float *)ValuePtr; + break; + case FMT_DOUBLE: + Value = *(double *)ValuePtr; + break; + + default: + std::cout << "Illegal format code " << Format << " in Exif header" + << std::endl; + } + return Value; +} + +//-------------------------------------------------------------------------- + +void JpgExifReader::process_EXIF(unsigned char *ExifSection, + unsigned int length) { + unsigned int FirstOffset; + +#ifdef ReadAllTags + FocalplaneXRes = 0; + FocalplaneUnits = 0; + ExifImageWidth = 0; + NumOrientations = 0; + DirWithThumbnailPtrs = NULL; +#endif + + if (ShowTags) printf("Exif header %u bytes long\n", length); + + { // Check the EXIF header component + static uchar ExifHeader[] = "Exif\0\0"; + if (memcmp(ExifSection + 2, ExifHeader, 6)) { + std::cout << "Incorrect Exif header" << std::endl; + return; + } + } + + if (memcmp(ExifSection + 8, "II", 2) == 0) { + if (ShowTags) printf("Exif section in Intel order\n"); + MotorolaOrder = 0; + } else { + if (memcmp(ExifSection + 8, "MM", 2) == 0) { + if (ShowTags) printf("Exif section in Motorola order\n"); + MotorolaOrder = 1; + } else { + std::cout << "Invalid Exif alignment marker." << std::endl; + return; + } + } + + // Check the next value for correctness. + if (Get16u(ExifSection + 10) != 0x2a) { + std::cout << "Invalid Exif start (1)" << std::endl; + return; + } + + FirstOffset = Get32u(ExifSection + 12); + if (FirstOffset < 8 || FirstOffset > 16) { + if (FirstOffset < 16 || FirstOffset > length - 16) { + std::cout << "invalid offset for first Exif IFD value" << std::endl; + return; + } + // Usually set to 8, but other values valid too. + std::cout << "Suspicious offset of first Exif IFD value" << std::endl; + } + + // First directory starts 16 bytes in. All offset are relative to 8 bytes in. + ProcessExifDir(ExifSection + 8 + FirstOffset, ExifSection + 8, length - 8, 0); + +#ifdef ReadAllTags + ImageInfo.ThumbnailAtEnd = + ImageInfo.ThumbnailOffset >= ImageInfo.LargestExifOffset ? TRUE : FALSE; + + if (DumpExifMap) { + unsigned a, b; + printf("Map: %05d- End of exif\n", length - 8); + for (a = 0; a < length - 8; a += 10) { + printf("Map: %05d ", a); + for (b = 0; b < 10; b++) printf(" %02x", *(ExifSection + 8 + a + b)); + printf("\n"); + } + } + + // Compute the CCD width, in millimeters. + if (FocalplaneXRes != 0 && ExifImageWidth != 0) { + // Note: With some cameras, its not possible to compute this correctly + // because + // they don't adjust the indicated focal plane resolution units when using + // less + // than maximum resolution, so the CCDWidth value comes out too small. + // Nothing + // that Jhad can do about it - its a camera problem. + ImageInfo.CCDWidth = + (float)(ExifImageWidth * FocalplaneUnits / FocalplaneXRes); + + if (ImageInfo.FocalLength && ImageInfo.FocalLength35mmEquiv == 0) { + // Compute 35 mm equivalent focal length based on sensor geometry if we + // haven't + // already got it explicitly from a tag. + ImageInfo.FocalLength35mmEquiv = + (int)(ImageInfo.FocalLength / ImageInfo.CCDWidth * 36 + 0.5); + } + } +#endif +} + +//-------------------------------------------------------------------------- +// Process one of the nested EXIF directories. +//-------------------------------------------------------------------------- +void JpgExifReader::ProcessExifDir(unsigned char *DirStart, + unsigned char *OffsetBase, + unsigned ExifLength, int NestingLevel) { + int de; + int a; + int NumDirEntries; + unsigned ThumbnailOffset = 0; + unsigned ThumbnailSize = 0; + char IndentString[25]; + + if (NestingLevel > 4) { + std::cout << "Maximum Exif directory nesting exceeded (corrupt Exif header)" + << std::endl; + return; + } + + memset(IndentString, ' ', 25); + IndentString[NestingLevel * 4] = '\0'; + + NumDirEntries = Get16u(DirStart); +#define DIR_ENTRY_ADDR(Start, Entry) (Start + 2 + 12 * (Entry)) + + { + unsigned char *DirEnd; + DirEnd = DIR_ENTRY_ADDR(DirStart, NumDirEntries); + if (DirEnd + 4 > (OffsetBase + ExifLength)) { + if (DirEnd + 2 == OffsetBase + ExifLength || + DirEnd == OffsetBase + ExifLength) { + // Version 1.3 of jhead would truncate a bit too much. + // This also caught later on as well. + } else { + std::cout << "Illegally sized Exif subdirectory (" << NumDirEntries + << "entries)" << std::endl; + return; + } + } + if (DumpExifMap) { + printf("Map: %05u-%05u: Directory\n", (int)(DirStart - OffsetBase), + (int)(DirEnd + 4 - OffsetBase)); + } + } + + if (ShowTags) { + printf("(dir has %d entries)\n", NumDirEntries); + } + + for (de = 0; de < NumDirEntries; de++) { + int Tag, Format, Components; + unsigned char *ValuePtr; + int ByteCount; + unsigned char *DirEntry; + DirEntry = DIR_ENTRY_ADDR(DirStart, de); + + Tag = Get16u(DirEntry); + Format = Get16u(DirEntry + 2); + Components = Get32u(DirEntry + 4); + + if ((Format - 1) >= NUM_FORMATS) { + // (-1) catches illegal zero case as unsigned underflows to positive + // large. + std::cout << "Illegal number format " << Format << " for tag " << Tag + << " in Exif" << std::endl; + continue; + } + + if ((unsigned)Components > 0x10000) { + std::cout << "Too many components " << Components << " for tag " << Tag + << " in Exif"; + continue; + } + + ByteCount = Components * BytesPerFormat[Format]; + + if (ByteCount > 4) { + unsigned OffsetVal; + OffsetVal = Get32u(DirEntry + 8); + // If its bigger than 4 bytes, the dir entry contains an offset. + if (OffsetVal + ByteCount > ExifLength) { + // Bogus pointer offset and / or bytecount value + std::cout << "Illegal value pointer for tag " << Tag << " in Exif"; + continue; + } + ValuePtr = OffsetBase + OffsetVal; + +#ifdef ReadAllTags + if (OffsetVal > ImageInfo.LargestExifOffset) { + ImageInfo.LargestExifOffset = OffsetVal; + } +#endif + + if (DumpExifMap) { + printf("Map: %05u-%05u: Data for tag %04x\n", OffsetVal, + OffsetVal + ByteCount, Tag); + } + } else { + // 4 bytes or less and value is in the dir entry itself + ValuePtr = DirEntry + 8; + } + +#ifdef ReadAllTags + if (Tag == TAG_MAKER_NOTE) { + if (ShowTags) { + printf("%s Maker note: ", IndentString); + } + // ProcessMakerNote(ValuePtr, ByteCount, OffsetBase, ExifLength); + continue; + } + + if (ShowTags) { + // Show tag name + for (a = 0;; a++) { + if (a >= TAG_TABLE_SIZE) { + printf("%s Unknown Tag %04x Value = ", IndentString, Tag); + break; + } + if (TagTable[a].Tag == Tag) { + printf("%s %s = ", IndentString, TagTable[a].Desc); + break; + } + } + + // Show tag value. + switch (Format) { + case FMT_BYTE: + if (ByteCount > 1) { + printf("%.*ls\n", ByteCount / 2, (wchar_t *)ValuePtr); + } else { + PrintFormatNumber(ValuePtr, Format, ByteCount); + printf("\n"); + } + break; + + case FMT_UNDEFINED: + // Undefined is typically an ascii string. + + case FMT_STRING: + // String arrays printed without function call (different from int + // arrays) + { + int NoPrint = 0; + printf("\""); + for (a = 0; a < ByteCount; a++) { + if (ValuePtr[a] >= 32) { + putchar(ValuePtr[a]); + NoPrint = 0; + } else { + // Avoiding indicating too many unprintable characters of + // proprietary + // bits of binary information this program may not know how to + // parse. + if (!NoPrint && a != ByteCount - 1) { + putchar('?'); + NoPrint = 1; + } + } + } + printf("\"\n"); + } + break; + + default: + // Handle arrays of numbers later (will there ever be?) + PrintFormatNumber(ValuePtr, Format, ByteCount); + printf("\n"); + } + } +#endif + // Extract useful components of tag + switch (Tag) { +#ifdef ReadAllTags + case TAG_MAKE: + strncpy(ImageInfo.CameraMake, (char *)ValuePtr, + ByteCount < 31 ? ByteCount : 31); + break; + + case TAG_MODEL: + strncpy(ImageInfo.CameraModel, (char *)ValuePtr, + ByteCount < 39 ? ByteCount : 39); + break; + + case TAG_DATETIME_ORIGINAL: + // If we get a DATETIME_ORIGINAL, we use that one. + strncpy(ImageInfo.DateTime, (char *)ValuePtr, 19); + // Fallthru... + + case TAG_DATETIME_DIGITIZED: + case TAG_DATETIME: + if (!isdigit(static_cast(ImageInfo.DateTime[0]))) { + // If we don't already have a DATETIME_ORIGINAL, use whatever + // time fields we may have. + strncpy(ImageInfo.DateTime, (char *)ValuePtr, 19); + } + + if (ImageInfo.numDateTimeTags >= MAX_DATE_COPIES) { + std::cout << "More than " << MAX_DATE_COPIES + << " date fields in Exif. This is nuts" << std::endl; + break; + } + ImageInfo.DateTimeOffsets[ImageInfo.numDateTimeTags++] = + (char *)ValuePtr - (char *)OffsetBase; + break; + + case TAG_WINXP_COMMENT: + if (ImageInfo.Comments[0]) { // We already have a jpeg comment. + // Already have a comment (probably windows comment), skip this one. + if (ShowTags) + printf("Windows XP commend and other comment in header\n"); + break; // Already have a windows comment, skip this one. + } + + if (ByteCount > 1) { + if (ByteCount > MAX_COMMENT_SIZE) ByteCount = MAX_COMMENT_SIZE; + memcpy(ImageInfo.Comments, ValuePtr, ByteCount); + ImageInfo.CommentWidthchars = ByteCount / 2; + } + break; + + case TAG_USERCOMMENT: + if (ImageInfo.Comments[0]) { // We already have a jpeg comment. + // Already have a comment (probably windows comment), skip this one. + if (ShowTags) printf("Multiple comments in exif header\n"); + break; // Already have a windows comment, skip this one. + } + + // Comment is often padded with trailing spaces. Remove these first. + for (a = ByteCount;;) { + a--; + if ((ValuePtr)[a] == ' ') { + (ValuePtr)[a] = '\0'; + } else { + break; + } + if (a == 0) break; + } + + // Copy the comment + { + int msiz = ExifLength - (ValuePtr - OffsetBase); + if (msiz > ByteCount) msiz = ByteCount; + if (msiz > MAX_COMMENT_SIZE - 1) msiz = MAX_COMMENT_SIZE - 1; + if (msiz > 5 && memcmp(ValuePtr, "ASCII", 5) == 0) { + for (a = 5; a < 10 && a < msiz; a++) { + int c = (ValuePtr)[a]; + if (c != '\0' && c != ' ') { + strncpy(ImageInfo.Comments, (char *)ValuePtr + a, msiz - a); + break; + } + } + } else { + strncpy(ImageInfo.Comments, (char *)ValuePtr, msiz); + } + } + break; + + case TAG_FNUMBER: + // Simplest way of expressing aperture, so I trust it the most. + // (overwrite previously computd value if there is one) + ImageInfo.ApertureFNumber = (float)ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_APERTURE: + case TAG_MAXAPERTURE: + // More relevant info always comes earlier, so only use this field if we + // don't + // have appropriate aperture information yet. + if (ImageInfo.ApertureFNumber == 0) { + ImageInfo.ApertureFNumber = + (float)exp(ConvertAnyFormat(ValuePtr, Format) * log(2) * 0.5); + } + break; + + case TAG_FOCALLENGTH: + // Nice digital cameras actually save the focal length as a function + // of how farthey are zoomed in. + ImageInfo.FocalLength = (float)ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_SUBJECT_DISTANCE: + // Inidcates the distacne the autofocus camera is focused to. + // Tends to be less accurate as distance increases. + ImageInfo.Distance = (float)ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_EXPOSURETIME: + // Simplest way of expressing exposure time, so I trust it most. + // (overwrite previously computd value if there is one) + ImageInfo.ExposureTime = (float)ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_SHUTTERSPEED: + // More complicated way of expressing exposure time, so only use + // this value if we don't already have it from somewhere else. + if (ImageInfo.ExposureTime == 0) { + ImageInfo.ExposureTime = + (float)(1 / exp(ConvertAnyFormat(ValuePtr, Format) * log(2))); + } + break; + + case TAG_FLASH: + ImageInfo.FlashUsed = (int)ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_ORIENTATION: + if (NumOrientations >= 2) { + // Can have another orientation tag for the thumbnail, but if there's + // a third one, things are strange. + std::cout << "More than two orientation in Exif" << std::endl; + break; + } + OrientationPtr[NumOrientations] = ValuePtr; + OrientationNumFormat[NumOrientations] = Format; + if (NumOrientations == 0) { + ImageInfo.Orientation = (int)ConvertAnyFormat(ValuePtr, Format); + } + if (ImageInfo.Orientation < 0 || ImageInfo.Orientation > 8) { + std::cout << "Undefined rotation value " << ImageInfo.Orientation + << " in Exif" << std::endl; + } + NumOrientations += 1; + break; + + case TAG_PIXEL_Y_DIMENSION: + case TAG_PIXEL_X_DIMENSION: + // Use largest of height and width to deal with images that have been + // rotated to portrait format. + a = (int)ConvertAnyFormat(ValuePtr, Format); + if (ExifImageWidth < a) ExifImageWidth = a; + break; + + case TAG_FOCAL_PLANE_XRES: + FocalplaneXRes = ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_FOCAL_PLANE_UNITS: + switch ((int)ConvertAnyFormat(ValuePtr, Format)) { + case 1: + FocalplaneUnits = 25.4; + break; // inch + case 2: + // According to the information I was using, 2 means meters. + // But looking at the Cannon powershot's files, inches is the only + // sensible value. + FocalplaneUnits = 25.4; + break; + + case 3: + FocalplaneUnits = 10; + break; // centimeter + case 4: + FocalplaneUnits = 1; + break; // millimeter + case 5: + FocalplaneUnits = .001; + break; // micrometer + } + break; + + case TAG_EXPOSURE_BIAS: + ImageInfo.ExposureBias = (float)ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_WHITEBALANCE: + ImageInfo.Whitebalance = (int)ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_LIGHT_SOURCE: + ImageInfo.LightSource = (int)ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_METERING_MODE: + ImageInfo.MeteringMode = (int)ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_EXPOSURE_PROGRAM: + ImageInfo.ExposureProgram = (int)ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_EXPOSURE_INDEX: + if (ImageInfo.ISOequivalent == 0) { + // Exposure index and ISO equivalent are often used interchangeably, + // so we will do the same in jhead. + // http://photography.about.com/library/glossary/bldef_ei.htm + ImageInfo.ISOequivalent = (int)ConvertAnyFormat(ValuePtr, Format); + } + break; + + case TAG_EXPOSURE_MODE: + ImageInfo.ExposureMode = (int)ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_ISO_EQUIVALENT: + ImageInfo.ISOequivalent = (int)ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_DIGITALZOOMRATIO: + ImageInfo.DigitalZoomRatio = (float)ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_THUMBNAIL_OFFSET: + ThumbnailOffset = (unsigned)ConvertAnyFormat(ValuePtr, Format); + DirWithThumbnailPtrs = DirStart; + break; + + case TAG_THUMBNAIL_LENGTH: + ThumbnailSize = (unsigned)ConvertAnyFormat(ValuePtr, Format); + ImageInfo.ThumbnailSizeOffset = ValuePtr - OffsetBase; + break; + + case TAG_EXIF_OFFSET: + if (ShowTags) printf("%s Exif Dir:", IndentString); + + case TAG_INTEROP_OFFSET: + if (Tag == TAG_INTEROP_OFFSET && ShowTags) + printf("%s Interop Dir:", IndentString); + { + unsigned char *SubdirStart; + SubdirStart = OffsetBase + Get32u(ValuePtr); + if (SubdirStart < OffsetBase || SubdirStart > OffsetBase + ExifLength) { + std::cout << "Illegal Exif or interop ofset directory link" + << std::endl; + } else { + ProcessExifDir(SubdirStart, OffsetBase, ExifLength, NestingLevel + 1); + } + continue; + } + break; + + case TAG_GPSINFO: + if (ShowTags) printf("%s GPS info dir:", IndentString); + { + unsigned char *SubdirStart; + SubdirStart = OffsetBase + Get32u(ValuePtr); + if (SubdirStart < OffsetBase || SubdirStart > OffsetBase + ExifLength) { + std::cout << "Illegal GPS directory link in Exif" << std::endl; + } else { + // ProcessGpsInfo(SubdirStart, OffsetBase, ExifLength); + } + continue; + } + break; + + case TAG_FOCALLENGTH_35MM: + // The focal length equivalent 35 mm is a 2.2 tag (defined as of April + // 2002) + // if its present, use it to compute equivalent focal length instead of + // computing it from sensor geometry and actual focal length. + ImageInfo.FocalLength35mmEquiv = + (unsigned)ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_DISTANCE_RANGE: + // Three possible standard values: + // 1 = macro, 2 = close, 3 = distant + ImageInfo.DistanceRange = (int)ConvertAnyFormat(ValuePtr, Format); + break; + +#endif + + case TAG_X_RESOLUTION: + if (NestingLevel == + 0) { // Only use the values from the top level directory + ImageInfo.xResolution = (float)ConvertAnyFormat(ValuePtr, Format); + // if yResolution has not been set, use the value of xResolution + if (ImageInfo.yResolution == 0.0) + ImageInfo.yResolution = ImageInfo.xResolution; + } + break; + + case TAG_Y_RESOLUTION: + if (NestingLevel == + 0) { // Only use the values from the top level directory + ImageInfo.yResolution = (float)ConvertAnyFormat(ValuePtr, Format); + // if xResolution has not been set, use the value of yResolution + if (ImageInfo.xResolution == 0.0) + ImageInfo.xResolution = ImageInfo.yResolution; + } + break; + + case TAG_RESOLUTION_UNIT: + if (NestingLevel == + 0) { // Only use the values from the top level directory + ImageInfo.ResolutionUnit = (int)ConvertAnyFormat(ValuePtr, Format); + } + break; + } + } + +#ifdef ReadAllTags + { + // In addition to linking to subdirectories via exif tags, + // there's also a potential link to another directory at the end of each + // directory. this has got to be the result of a committee! + unsigned char *SubdirStart; + unsigned Offset; + + if (DIR_ENTRY_ADDR(DirStart, NumDirEntries) + 4 <= + OffsetBase + ExifLength) { + Offset = Get32u(DirStart + 2 + 12 * NumDirEntries); + if (Offset) { + SubdirStart = OffsetBase + Offset; + if (SubdirStart > OffsetBase + ExifLength || SubdirStart < OffsetBase) { + if (SubdirStart > OffsetBase && + SubdirStart < OffsetBase + ExifLength + 20) { + // Jhead 1.3 or earlier would crop the whole directory! + // As Jhead produces this form of format incorrectness, + // I'll just let it pass silently + if (ShowTags) + printf("Thumbnail removed with Jhead 1.3 or earlier\n"); + } else { + std::cout << "Illegal subdirectory link in Exif header" + << std::endl; + } + } else { + if (SubdirStart <= OffsetBase + ExifLength) { + if (ShowTags) printf("%s Continued directory ", IndentString); + ProcessExifDir(SubdirStart, OffsetBase, ExifLength, + NestingLevel + 1); + } + } + if (Offset > ImageInfo.LargestExifOffset) { + ImageInfo.LargestExifOffset = Offset; + } + } + } else { + // The exif header ends before the last next directory pointer. + } + } + + if (ThumbnailOffset) { + ImageInfo.ThumbnailAtEnd = FALSE; + + if (DumpExifMap) { + printf("Map: %05d-%05d: Thumbnail\n", ThumbnailOffset, + ThumbnailOffset + ThumbnailSize); + } + + if (ThumbnailOffset <= ExifLength) { + if (ThumbnailSize > ExifLength - ThumbnailOffset) { + // If thumbnail extends past exif header, only save the part that + // actually exists. Canon's EOS viewer utility will do this - the + // thumbnail extracts ok with this hack. + ThumbnailSize = ExifLength - ThumbnailOffset; + if (ShowTags) printf("Thumbnail incorrectly placed in header\n"); + } + // The thumbnail pointer appears to be valid. Store it. + ImageInfo.ThumbnailOffset = ThumbnailOffset; + ImageInfo.ThumbnailSize = ThumbnailSize; + + if (ShowTags) { + printf("Thumbnail size: %u bytes\n", ThumbnailSize); + } + } + } +#endif +} diff --git a/toonz/sources/include/tiio_jpg_exif.h b/toonz/sources/include/tiio_jpg_exif.h new file mode 100644 index 0000000..1d619d8 --- /dev/null +++ b/toonz/sources/include/tiio_jpg_exif.h @@ -0,0 +1,139 @@ +/*------------------------------------------------------------- +tiio_jpg_exif.h +Based on source code of a public domain software "Exif Jpeg header manipulation +tool (jhead)" by Matthias Wandel. +For now it is used only for obtaining resolution values. +http://www.sentex.net/~mwandel/jhead/ +-------------------------------------------------------------*/ +#pragma once +#ifndef TIIO_JPG_EXIF_H +#define TIIO_JPG_EXIF_H + +#include + +// undefining ReadAllTags will load only resolution infomation +#define ReadAllTags +#undef ReadAllTags + +#define MAX_COMMENT_SIZE 16000 +#define MAX_DATE_COPIES 10 + +#ifdef _WIN32 +#define PATH_MAX _MAX_PATH +#define SLASH '\\' +#else +#ifndef PATH_MAX +#define PATH_MAX 1024 +#endif +#define SLASH '/' +#endif + +class JpgExifReader { + enum FORMATS { + FMT_BYTE = 1, + FMT_STRING, + FMT_USHORT, + FMT_ULONG, + FMT_URATIONAL, + FMT_SBYTE, + FMT_UNDEFINED, + FMT_SSHORT, + FMT_SLONG, + FMT_SRATIONAL, + FMT_SINGLE, + FMT_DOUBLE + }; + + int NUM_FORMATS = FMT_DOUBLE; + + struct ExifImageInfo { + float xResolution = 0.0; + float yResolution = 0.0; + int ResolutionUnit; +#ifdef ReadAllTags + char FileName[PATH_MAX + 1]; + time_t FileDateTime; + + unsigned FileSize; + char CameraMake[32]; + char CameraModel[40]; + char DateTime[20]; + unsigned Height, Width; + int Orientation; + int IsColor; + int Process; + int FlashUsed; + float FocalLength; + float ExposureTime; + float ApertureFNumber; + float Distance; + float CCDWidth; + float ExposureBias; + float DigitalZoomRatio; + int FocalLength35mmEquiv; // Exif 2.2 tag - usually not present. + int Whitebalance; + int MeteringMode; + int ExposureProgram; + int ExposureMode; + int ISOequivalent; + int LightSource; + int DistanceRange; + + char Comments[MAX_COMMENT_SIZE]; + int CommentWidthchars; // If nonzero, widechar comment, indicates number of + // chars. + + unsigned ThumbnailOffset; // Exif offset to thumbnail + unsigned ThumbnailSize; // Size of thumbnail. + unsigned LargestExifOffset; // Last exif data referenced (to check if + // thumbnail is at end) + + char ThumbnailAtEnd; // Exif header ends with the thumbnail + // (we can only modify the thumbnail if its at the end) + int ThumbnailSizeOffset; + + int DateTimeOffsets[MAX_DATE_COPIES]; + int numDateTimeTags = 0; + + int GpsInfoPresent; + char GpsLat[31]; + char GpsLong[31]; + char GpsAlt[20]; + + int QualityGuess; +#endif + } ImageInfo; + +#ifdef ReadAllTags + unsigned char* DirWithThumbnailPtrs; + double FocalplaneXRes; + double FocalplaneUnits; + int ExifImageWidth; + + // for fixing the rotation. + void* OrientationPtr[2]; + int OrientationNumFormat[2]; + int NumOrientations = 0; +#endif + + int MotorolaOrder = 0; + + int Get16u(void* Short); + unsigned Get32u(void* Long); + int Get32s(void* Long); + void PrintFormatNumber(void* ValuePtr, int Format, int ByteCount); + double ConvertAnyFormat(void* ValuePtr, int Format); + +public: + void process_EXIF(unsigned char* ExifSection, unsigned int length); + void ProcessExifDir(unsigned char* DirStart, unsigned char* OffsetBase, + unsigned ExifLength, int NestingLevel); + + // obtaining resolution info + bool containsResolution() { return ImageInfo.xResolution != 0.0; } + float getXResolution() { return ImageInfo.xResolution; } + float getYResolution() { return ImageInfo.yResolution; } + int getResolutionUnit() { return ImageInfo.ResolutionUnit; } +}; + +#endif \ No newline at end of file diff --git a/toonz/sources/tnzcore/CMakeLists.txt b/toonz/sources/tnzcore/CMakeLists.txt index 16603f9..658c70e 100644 --- a/toonz/sources/tnzcore/CMakeLists.txt +++ b/toonz/sources/tnzcore/CMakeLists.txt @@ -112,6 +112,7 @@ set(HEADERS ${MOC_HEADERS} ../include/tiio_bmp.h ../include/tiio_jpg.h ../include/tiio_jpg_util.h + ../include/tiio_jpg_exif.h ../include/tiio_std.h ../include/ttoonzimage.h ../include/tipc.h @@ -226,6 +227,7 @@ set(SOURCES ../common/tiio/tiio_bmp.cpp ../common/tiio/tiio_jpg.cpp ../common/tiio/tiio_jpg_util.cpp + ../common/tiio/tiio_jpg_exif.cpp ../common/tiio/tiio_std.cpp ../common/tiio/movsettings.cpp ../common/tiio/compatibility/tfile_io.c