diff --git a/modules/imgcodecs/include/opencv2/imgcodecs.hpp b/modules/imgcodecs/include/opencv2/imgcodecs.hpp index 039482017fc0..59d5197a6953 100644 --- a/modules/imgcodecs/include/opencv2/imgcodecs.hpp +++ b/modules/imgcodecs/include/opencv2/imgcodecs.hpp @@ -44,6 +44,7 @@ #define OPENCV_IMGCODECS_HPP #include "opencv2/core.hpp" +#include /** @defgroup imgcodecs Image file reading and writing @@ -252,6 +253,10 @@ enum ImwriteGIFCompressionFlags { IMWRITE_GIF_COLORTABLE_SIZE_256 = 8 }; +//! @} imgcodecs_flags + +//! @addtogroup imgcodecs_metadata +//! @{ enum ImageMetadataType { IMAGE_METADATA_UNKNOWN = -1, // Used when metadata type is unrecognized or not set @@ -263,7 +268,271 @@ enum ImageMetadataType IMAGE_METADATA_MAX = 2 // Highest valid index (usually used for bounds checking) }; -//! @} imgcodecs_flags +enum ExifTagType +{ + TAG_TYPE_NOTYPE = 0, // Invalid or undefined type + TAG_TYPE_BYTE = 1, // 8-bit unsigned integer + TAG_TYPE_ASCII = 2, // 8-bit ASCII string, null-terminated + TAG_TYPE_SHORT = 3, // 16-bit unsigned integer + TAG_TYPE_LONG = 4, // 32-bit unsigned integer + TAG_TYPE_RATIONAL = 5, // Two LONGs: numerator and denominator (64-bit unsigned fraction) + TAG_TYPE_SBYTE = 6, // 8-bit signed integer + TAG_TYPE_UNDEFINED = 7, // 8-bit untyped data + TAG_TYPE_SSHORT = 8, // 16-bit signed integer + TAG_TYPE_SLONG = 9, // 32-bit signed integer + TAG_TYPE_SRATIONAL = 10, // Two SLONGs: signed 64-bit fraction + TAG_TYPE_FLOAT = 11, // IEEE 32-bit float + TAG_TYPE_DOUBLE = 12, // IEEE 64-bit float + TAG_TYPE_IFD = 13, // 32-bit offset to IFD + TAG_TYPE_LONG8 = 16, // BigTIFF: 64-bit unsigned integer + TAG_TYPE_SLONG8 = 17, // BigTIFF: 64-bit signed integer + TAG_TYPE_IFD8 = 18 // BigTIFF: 64-bit offset to IFD +}; + +/** + * @brief Picture orientation which may be taken from EXIF + * Orientation usually matters when the picture is taken by + * smartphone or other camera with orientation sensor support + * Corresponds to EXIF 2.3 Specification + */ +enum ImageOrientation +{ + IMAGE_ORIENTATION_TL = 1, ///< Horizontal (normal) + IMAGE_ORIENTATION_TR = 2, ///< Mirrored horizontal + IMAGE_ORIENTATION_BR = 3, ///< Rotate 180 + IMAGE_ORIENTATION_BL = 4, ///< Mirrored vertical + IMAGE_ORIENTATION_LT = 5, ///< Mirrored horizontal & rotate 270 CW + IMAGE_ORIENTATION_RT = 6, ///< Rotate 90 CW + IMAGE_ORIENTATION_RB = 7, ///< Mirrored horizontal & rotate 90 CW + IMAGE_ORIENTATION_LB = 8 ///< Rotate 270 CW +}; + +/** + * @brief Base Exif tags used by IFD0 (main image) + */ +enum ExifTagId +{ + TAG_EMPTY = 0, + TAG_SUB_FILETYPE = 254, + TAG_IMAGE_WIDTH = 256, + TAG_IMAGE_LENGTH = 257, + TAG_BITS_PER_SAMPLE = 258, + TAG_COMPRESSION = 259, + TAG_PHOTOMETRIC = 262, + TAG_THRESHOLDING = 263, + TAG_CELLWIDTH = 264, + TAG_CELLLENGTH = 265, + TAG_FILLORDER = 266, + TAG_DOCUMENTNAME = 269, + TAG_IMAGEDESCRIPTION = 270, + TAG_MAKE = 271, + TAG_MODEL = 272, + TAG_STRIP_OFFSET = 273, + TAG_ORIENTATION = 274, + TAG_SAMPLES_PER_PIXEL = 277, + TAG_ROWS_PER_STRIP = 278, + TAG_STRIP_BYTE_COUNTS = 279, + + TAG_XRESOLUTION = 282, + TAG_YRESOLUTION = 283, + TAG_PLANAR_CONFIG = 284, + TAG_PAGENAME = 285, + TAG_XPOSITION = 286, + TAG_YPOSITION = 287, + TAG_GRAYRESPONSEUNIT = 290, + TAG_GRAYRESPONSECURVE = 291, + TAG_T4OPTIONS = 292, + TAG_T6OPTIONS = 293, + TAG_RESOLUTION_UNIT = 296, + TAG_PAGENUMBER = 297, + TAG_TRANSFERFUNCTION = 301, + TAG_SOFTWARE = 305, + TAG_MODIFYDATE = 306, + TAG_ARTIST = 315, + TAG_HOST_COMPUTER = 316, + + TAG_SAMPLEFORMAT = 339, + TAG_JPGFROMRAWSTART = 513, + TAG_JPGFROMRAWLENGTH = 514, + + TAG_YCBCRSUBSAMPLING = 530, + TAG_YCBCRPOSITIONING = 531, + TAG_REFERENCEBLACKWHITE = 532, + + // DNG extension + TAG_CFA_REPEAT_PATTERN_DIM = 33421, + TAG_CFA_PATTERN = 33422, + + TAG_COPYRIGHT = 33432, + TAG_EXPOSURE_TIME = 33434, + TAG_FNUMBER = 33437, + + TAG_EXIF_OFFSET = 34665, + TAG_EXPOSUREPROGRAM = 34850, + TAG_GPSINFO = 34853, + TAG_ISOSPEED = 34855, + + TAG_EXIF_VERSION = 36864, + TAG_DATETIME_ORIGINAL = 36867, + TAG_DATETIME_CREATE = 36868, + + TAG_OFFSETTIME = 36880, + TAG_OFFSETTIME_ORIGINAL = 36881, + TAG_OFFSETTIME_DIGITIZED = 36882, + + TAG_COMPONENTSCONFIGURATION = 37121, + + TAG_SHUTTER_SPEED = 37377, + TAG_APERTURE_VALUE = 37378, + TAG_BRIGHTNESS_VALUE = 37379, + TAG_EXPOSUREBIASVALUE = 37380, + TAG_MAXAPERTUREVALUE = 37381, + TAG_SUBJECTDISTANCE = 37382, + TAG_METERINGMODE = 37383, + TAG_LIGHTSOURCE = 37384, + TAG_FLASH = 37385, + TAG_FOCALLENGTH = 37386, + + TAG_SUBJECT_AREA = 37396, + + TAG_EP_STANDARD_ID = 37398, + + TAG_MAKERNOTE = 37500, + TAG_USERCOMMENT = 37510, + + TAG_SUBSECTIME = 37520, + + TAG_SUBSECTIME_ORIGINAL = 37521, + TAG_SUBSECTIME_DIGITIZED = 37522, + + TAG_FLASHPIXVERSION = 40960, + TAG_COLORSPACE = 40961, + TAG_EXIF_IMAGE_WIDTH = 40962, + TAG_EXIF_IMAGE_HEIGHT = 40963, + + TAG_FOCALPLANEXRESOLUTION = 41486, + TAG_FOCALPLANEYRESOLUTION = 41487, + TAG_FOCALPLANERESOLUTIONUNIT = 41488, + + TAG_SCENE_TYPE = 41729, + TAG_CUSTOMRENDERED = 41985, + TAG_EXPOSUREMODE = 41986, + TAG_WHITE_BALANCE = 41987, + TAG_SCENECAPTURETYPE = 41990, + + TAG_BODYSERIALNUMBER = 42033, + TAG_LENSSPECIFICATION = 42034, + TAG_LENSMAKE = 42035, + TAG_LENSMODEL = 42036, + + TAG_DNG_VERSION = 50706, + TAG_DNG_BACKWARD_VERSION = 50707, + TAG_UNIQUE_CAMERA_MODEL = 50708, + TAG_CHROMA_BLUR_RADIUS = 50703, + TAG_CFA_PLANECOLOR = 50710, + TAG_CFA_LAYOUT = 50711, + TAG_BLACK_LEVEL_REPEAT_DIM = 50713, + TAG_BLACK_LEVEL = 50714, + TAG_WHITE_LEVEL = 50717, + TAG_DEFAULT_SCALE = 50718, + TAG_DEFAULT_CROP_ORIGIN = 50719, + TAG_DEFAULT_CROP_SIZE = 50720, + TAG_COLOR_MATRIX1 = 50721, + TAG_COLOR_MATRIX2 = 50722, + TAG_CAMERA_CALIBRATION1 = 50723, + TAG_CAMERA_CALIBRATION2 = 50724, + TAG_ANALOG_BALANCE = 50727, + TAG_AS_SHOT_NEUTRAL = 50728, + TAG_AS_SHOT_WHITE_XY = 50729, + TAG_BASELINE_EXPOSURE = 50730, + TAG_CALIBRATION_ILLUMINANT1 = 50778, + TAG_CALIBRATION_ILLUMINANT2 = 50779, + TAG_EXTRA_CAMERA_PROFILES = 50933, + TAG_PROFILE_NAME = 50936, + TAG_AS_SHOT_PROFILE_NAME = 50934, + TAG_PREVIEW_COLORSPACE = 50970, + TAG_OPCODE_LIST2 = 51009, + TAG_NOISE_PROFILE = 51041, + TAG_DEFAULT_BLACK_RENDER = 51110, + TAG_ACTIVE_AREA = 50829, + TAG_FORWARD_MATRIX1 = 50964, + TAG_FORWARD_MATRIX2 = 50965, + TAG_INVALID_TAG = 65535 +}; + +struct CV_EXPORTS_W_SIMPLE urational64_t +{ + CV_PROP_RW int num = 0; + CV_PROP_RW int denom = 1; +}; + +struct CV_EXPORTS_W_SIMPLE srational64_t +{ + CV_PROP_RW int num = 0; + CV_PROP_RW int denom = 1; +}; + +struct CV_EXPORTS_W_SIMPLE ExifEntry +{ +public: + ExifEntry() + : tagId(TAG_EMPTY), type(TAG_TYPE_NOTYPE), count(1), + value_u32(0), value_str(), value_raw(), value_srational() {} + ~ExifEntry() = default; + + CV_PROP_RW int tagId; + CV_PROP_RW int type; + CV_PROP_RW int count; + + CV_WRAP int getValueAsInt() const { return value_u32; } + CV_WRAP std::string getValueAsString() const { return value_str; } + CV_WRAP std::vector getValueAsRaw() const { return value_raw; } + CV_WRAP std::vector getValueAsRational() const { return value_srational; } + + CV_WRAP void setValueAsString(const std::string& value) { + value_str = value; + count = static_cast(value.size()); + } + + CV_WRAP void setValueAsInt(int value) { value_u32 = value; } + CV_WRAP void setValueAsRaw(const std::vector& value) { value_raw = value; } + CV_WRAP void setValueAsRational(const std::vector& value) { value_srational = value; } + + CV_WRAP bool empty() const { return tagId == TAG_EMPTY; } + CV_WRAP std::string getTagIdAsString() const; + CV_WRAP std::string getTagTypeAsString() const; + std::ostream& dump(std::ostream& strm) const; + +private: + int value_u32; + std::string value_str; + std::vector value_raw; + std::vector value_srational; +}; + +/** @brief Decodes EXIF metadata from binary data into structured ExifEntry entries. + +This function parses raw EXIF binary data and extracts metadata tags as structured `ExifEntry` objects. +The extracted entries are organized as a vector of IFD (Image File Directory) blocks, where each IFD is a vector of `ExifEntry`. + +@param data The input binary EXIF data buffer. +@param exif_entries Output vector of IFD blocks. Each IFD block is a vector of `ExifEntry` objects containing decoded tag information (tag ID, type, count, and value). +@return Returns `true` if decoding was successful, `false` otherwise. + */ +CV_EXPORTS_W bool decodeExif(const std::vector& data, CV_OUT std::vector< std::vector >& exif_entries); + +/** @brief Encodes structured ExifEntry metadata into binary EXIF data. + +This function serializes a collection of ExifEntry objects into a binary EXIF data block. +The input entries are expected to be organized as a vector of IFD blocks, matching the EXIF file structure (e.g., primary IFD, Exif IFD, GPS IFD). + +@param exif_entries Input vector of IFD blocks. Each IFD block is a vector of ExifEntry objects containing tag metadata to be encoded. +@param data Output buffer where the encoded EXIF binary data will be stored. +@return Returns `true` if decoding was successful, `false` otherwise. + */ +CV_EXPORTS_W bool encodeExif(const std::vector>& exif_entries, CV_OUT std::vector& data); + +//! @} imgcodecs_metadata /** @brief Represents an animation with multiple frames. The `Animation` struct is designed to store and manage data for animated sequences such as those from animated formats (e.g., GIF, AVIF, APNG, WebP). diff --git a/modules/imgcodecs/misc/python/pyopencv_imgcodecs.hpp b/modules/imgcodecs/misc/python/pyopencv_imgcodecs.hpp new file mode 100644 index 000000000000..f9a01e126da5 --- /dev/null +++ b/modules/imgcodecs/misc/python/pyopencv_imgcodecs.hpp @@ -0,0 +1,6 @@ +#ifdef HAVE_OPENCV_IMGCODECS + +typedef std::vector vector_srational64_t; +typedef std::vector > vector_vector_ExifEntry; + +#endif diff --git a/modules/imgcodecs/src/exif.cpp b/modules/imgcodecs/src/exif.cpp index 5484031c50c1..63f80988b070 100644 --- a/modules/imgcodecs/src/exif.cpp +++ b/modules/imgcodecs/src/exif.cpp @@ -1,48 +1,12 @@ -/*M/////////////////////////////////////////////////////////////////////////////////////// -// -// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. -// -// By downloading, copying, installing or using the software you agree to this license. -// If you do not agree to this license, do not download, install, -// copy or use the software. -// -// -// License Agreement -// For Open Source Computer Vision Library -// -// Copyright (C) 2000-2008, Intel Corporation, all rights reserved. -// Copyright (C) 2009, Willow Garage Inc., all rights reserved. -// Third party copyrights are property of their respective owners. -// -// Redistribution and use in source and binary forms, with or without modification, -// are permitted provided that the following conditions are met: -// -// * Redistribution's of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// * Redistribution's in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// * The name of the copyright holders may not be used to endorse or promote products -// derived from this software without specific prior written permission. -// -// This software is provided by the copyright holders and contributors "as is" and -// any express or implied warranties, including, but not limited to, the implied -// warranties of merchantability and fitness for a particular purpose are disclaimed. -// In no event shall the Intel Corporation or contributors be liable for any direct, -// indirect, incidental, special, exemplary, or consequential damages -// (including, but not limited to, procurement of substitute goods or services; -// loss of use, data, or profits; or business interruption) however caused -// and on any theory of liability, whether in contract, strict liability, -// or tort (including negligence or otherwise) arising in any way out of -// the use of this software, even if advised of the possibility of such damage. -// -//M*/ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html #include "precomp.hpp" #include "exif.hpp" #include "opencv2/core/utils/logger.hpp" +#include +#include namespace { @@ -54,6 +18,236 @@ namespace { namespace cv { +static uint32_t getExifTagTypeSize(uint16_t type) +{ + return + type == TAG_TYPE_NOTYPE ? 0 : + type == TAG_TYPE_BYTE ? 1 : + type == TAG_TYPE_ASCII ? 1 : + type == TAG_TYPE_SHORT ? 2 : + type == TAG_TYPE_LONG ? 4 : + type == TAG_TYPE_RATIONAL ? 8 : + type == TAG_TYPE_SBYTE ? 1 : + type == TAG_TYPE_UNDEFINED ? 1 : + type == TAG_TYPE_SSHORT ? 2 : + type == TAG_TYPE_SLONG ? 4 : + type == TAG_TYPE_SRATIONAL ? 8 : + type == TAG_TYPE_FLOAT ? 4 : + type == TAG_TYPE_DOUBLE ? 8 : + type == TAG_TYPE_IFD ? 0 : + type == TAG_TYPE_LONG8 ? 8 : + type == TAG_TYPE_SLONG8 ? 8 : + type == TAG_TYPE_IFD8 ? 0 : 0; +} + +static uint32_t exifEntryValuetoUInt32(ExifEntry entry) +{ + switch (entry.type) + { + case TAG_TYPE_BYTE: + case TAG_TYPE_UNDEFINED: + return (entry.count > 0) ? entry.getValueAsInt() : 0; + case TAG_TYPE_SBYTE: + return (entry.count > 0) ? static_cast(entry.getValueAsInt()) : 0; + case TAG_TYPE_SHORT: + return (entry.count > 0) ? entry.getValueAsInt() : 0; + case TAG_TYPE_SSHORT: + return (entry.count > 0) ? static_cast(entry.getValueAsInt()) : 0; + case TAG_TYPE_LONG: + case TAG_TYPE_SLONG: + return (entry.count > 0) ? entry.getValueAsInt() : 0; + case TAG_TYPE_ASCII: + return 0; + case TAG_TYPE_RATIONAL: + case TAG_TYPE_SRATIONAL: + return 0; + default: + return 0; + } +} + +static std::vector exifEntryValuetoBytes(const ExifEntry& entry) +{ + std::vector bytes; + switch (entry.type) + { + case TAG_TYPE_ASCII: + { + std::string str = entry.getValueAsString(); + size_t len = str.size(); + + // Insert string data + bytes.insert(bytes.end(), + reinterpret_cast(str.data()), + reinterpret_cast(str.data()) + len); + + // Pad with nulls if needed + if (len < static_cast(entry.count)) + bytes.insert(bytes.end(), entry.count - len, 0x00); + } + break; + case TAG_TYPE_UNDEFINED: + { + return entry.getValueAsRaw(); + } + break; + case TAG_TYPE_SRATIONAL: + case TAG_TYPE_RATIONAL: + { + std::vector srational_vec = entry.getValueAsRational(); + for (size_t i = 0; i < srational_vec.size(); ++i) + { + uint32_t numerator = srational_vec[i].num; + uint32_t denominator = srational_vec[i].denom; + bytes.insert(bytes.end(), reinterpret_cast(&numerator), reinterpret_cast(&numerator) + 4); + bytes.insert(bytes.end(), reinterpret_cast(&denominator), reinterpret_cast(&denominator) + 4); + } + } + break; + default: + // other types are handled inline in toUInt32() + break; + } + + return bytes; +} + +bool encodeExif(const std::vector>& exif_entries, std::vector& data) +{ + data.clear(); + + if (exif_entries.empty() || exif_entries[0].empty()) + return false; + + // TIFF Header + size_t tiffHeaderOffset = data.size(); + data.push_back('I'); data.push_back('I'); // Little Endian + data.push_back(0x2A); data.push_back(0x00); // 0x002A + uint32_t firstIFDOffset = 8; // IFD starts after TIFF header + data.insert(data.end(), (uchar*)&firstIFDOffset, (uchar*)&firstIFDOffset + 4); + + + for (const auto& ifdEntries : exif_entries) + { + // Temporary buffer for Value Area (big data blocks) + std::vector valueArea; + + uint16_t entryCount = static_cast(ifdEntries.size()); + + // Entry count + data.insert(data.end(), (uchar*)&entryCount, (uchar*)&entryCount + 2); + + // Placeholders for Entries + size_t entryStart = data.size(); + data.resize(data.size() + entryCount * 12); + + // Next IFD Offset (set to 0) + uint32_t nextIFDOffset = 0; + data.insert(data.end(), (uchar*)&nextIFDOffset, (uchar*)&nextIFDOffset + 4); + + // Process Entries + size_t entryOffset = entryStart; + for (const auto& entry : ifdEntries) + { + uint16_t tagId = (uint16_t)entry.tagId; + uint16_t type = (uint16_t)entry.type; + uint32_t count = entry.count; + + // Write Tag ID + std::memcpy(&data[entryOffset], &tagId, 2); entryOffset += 2; + // Write Type + std::memcpy(&data[entryOffset], &type, 2); entryOffset += 2; + // Write Count + std::memcpy(&data[entryOffset], &count, 4); entryOffset += 4; + + uint32_t valueSize = count * getExifTagTypeSize(type); + if (valueSize <= 4) + { + if (entry.getValueAsString().size()) + std::memcpy(&data[entryOffset], entry.getValueAsString().c_str(), 4); + else + { + uint32_t val = entry.getValueAsInt(); + std::memcpy(&data[entryOffset], &val, 4); + } + } + else + { + uint32_t offset = static_cast(tiffHeaderOffset + data.size() + valueArea.size()); + std::memcpy(&data[entryOffset], &offset, 4); + std::vector valData = exifEntryValuetoBytes(entry); + + valueArea.insert(valueArea.end(), valData.begin(), valData.end()); + + if (valData.size() % 2 != 0) // Align to 2 bytes + valueArea.push_back(0x00); + } + entryOffset += 4; + + } + + // Append Value Area after IFDs + data.insert(data.end(), valueArea.begin(), valueArea.end()); + } + + + return true; +} + + +bool decodeExif(const std::vector& data, std::vector< std::vector >& exif_entries) +{ + ExifReader reader; + return reader.parseExif(data.data(), data.size(), exif_entries); +} + +template void dumpScalar(std::ostream& strm, _Tp v) +{ + strm << v; +} + +template<> void dumpScalar(std::ostream& strm, int64_t v) +{ + strm << v; +} + +template<> void dumpScalar(std::ostream& strm, double v) +{ + strm << cv::format("%.8g", v); +} + +template<> void dumpScalar(std::ostream& strm, srational64_t v) +{ + strm << v.num << "/" << v.denom << cv::format(" (%.4f)", (double)v.num / v.denom); +} + +template<> void dumpScalar(std::ostream& strm, urational64_t v) +{ + strm << v.num << "/" << v.denom; + if (v.denom != 0) + strm << cv::format(" (%.4f)", static_cast(v.num) / v.denom); + else + strm << " (NaN)"; +} + +template void dumpVector(std::ostream& strm, const std::vector<_Tp>& v) +{ + size_t i, nvalues = v.size(); + if (nvalues > 1) + strm << '['; + for (i = 0; i < nvalues; i++) { + if (i > 0) + strm << ", "; + if (i >= 3 && i + 6 < nvalues) { + strm << "... "; + i = nvalues - 3; + } + dumpScalar(strm, v[i]); + } + if (nvalues > 1) + strm << ']'; +} + static std::string HexStringToBytes(const char* hexstring, size_t expected_length); // Converts the NULL terminated 'hexstring' which contains 2-byte character @@ -91,16 +285,10 @@ static std::string HexStringToBytes(const char* hexstring, return raw_data; } -ExifEntry_t::ExifEntry_t() : - field_float(0), field_double(0), field_u32(0), field_s32(0), - tag(INVALID_TAG), field_u16(0), field_s16(0), field_u8(0), field_s8(0) -{ -} - /** * @brief ExifReader constructor */ -ExifReader::ExifReader() : m_format(NONE) +ExifReader::ExifReader() : m_format(ExifEndianness::NONE) { } @@ -120,10 +308,10 @@ ExifReader::~ExifReader() * @return ExifEntru_t structure. Caller has to know what tag it calls in order to extract proper field from the structure ExifEntry_t * */ -ExifEntry_t ExifReader::getTag(const ExifTagName tag) const +ExifEntry ExifReader::getEntrybyTagId(const ExifTagId tag) const { - ExifEntry_t entry; - std::map::const_iterator it = m_exif.find(tag); + ExifEntry entry; + std::map::const_iterator it = m_exif.find(tag); if( it != m_exif.end() ) { @@ -176,7 +364,7 @@ bool ExifReader::processRawProfile(const char* profile, size_t profile_len) { * @return true if parsing was successful * false in case of unsuccessful parsing */ -bool ExifReader::parseExif(unsigned char* data, const size_t size) +bool ExifReader::parseExif(const unsigned char* data, const size_t size) { // Populate m_data, then call parseExif() (private) if( data && size > 0 ) @@ -218,46 +406,128 @@ void ExifReader::parseExif() } uint32_t offset = getStartOffset(); - size_t numEntry = getNumDirEntry( offset ); offset += 2; //go to start of tag fields for( size_t entry = 0; entry < numEntry; entry++ ) { - ExifEntry_t exifEntry = parseExifEntry( offset ); - m_exif.insert( std::make_pair( exifEntry.tag, exifEntry ) ); + ExifEntry exifEntry = parseExifEntry( offset ); + m_exif.insert( std::make_pair( exifEntry.tagId, exifEntry ) ); offset += tiffFieldSize; } } +// Helper: endian-aware offset extraction +uint32_t ExifReader::extractIFDOffset(const ExifEntry& entry) const +{ + uint32_t offsetVal = 0; + + // If the type is LONG or SLONG, the offset is directly the 32-bit value + if (entry.type == TAG_TYPE_LONG || entry.type == TAG_TYPE_SLONG) + { + offsetVal = static_cast(entry.getValueAsInt()); + } + else + { + // For other types, you may store offsets in a different union member + // or directly in a separate struct field when parsing + // This fallback assumes the offset fits in uint32_t + offsetVal = static_cast(exifEntryValuetoUInt32(entry)); + } + + // Apply endian conversion if needed +#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + uint8_t tmp[4]; + std::memcpy(tmp, &offsetVal, 4); + std::reverse(tmp, tmp + 4); + std::memcpy(&offsetVal, tmp, 4); +#endif + + return offsetVal; +} + +bool ExifReader::parseExif(const unsigned char* data, const size_t size, std::vector< std::vector >& exif_entries_vec) +{ + if (data && size > 0) + { + m_data.assign(data, data + size); + } + else + { + return false; + } + + m_format = getFormat(); + + if (!checkTagMark()) + { + return false; + } + + std::vector ifd_offsets; + ifd_offsets.push_back(getStartOffset()); + size_t current_ifd = 0; + while (current_ifd < ifd_offsets.size()) + { + uint32_t offset = ifd_offsets[current_ifd]; + + size_t numEntry = getNumDirEntry(offset); + offset += 2; //go to start of tag fields + + std::vector exif_entries; + for (size_t i = 0; i < numEntry; ++i) + { + ExifEntry exifEntry = parseExifEntry(offset); + exif_entries.push_back(exifEntry); + if (exifEntry.tagId == 0x8769 || exifEntry.tagId == 0x8825) // Exif or GPS IFD pointer + { + uint32_t sub_ifd_offset = exifEntry.getValueAsInt(); + if (sub_ifd_offset < m_data.size()) + ifd_offsets.push_back(sub_ifd_offset); + } + offset += tiffFieldSize; + } + exif_entries_vec.push_back(exif_entries); + // Handle IFD1 (Next IFD offset at the end of current IFD) + if (offset + 4 <= m_data.size()) + { + uint32_t next_ifd_offset = getU32(offset); + if (next_ifd_offset != 0 && next_ifd_offset < m_data.size()) + ifd_offsets.push_back(next_ifd_offset); + } + current_ifd++; + } + return true; +} + /** * @brief Get endianness of exif information * This is internal function and is not exposed to client * * @return INTEL, MOTO or NONE */ -Endianness_t ExifReader::getFormat() const +int ExifReader::getFormat() const { if (m_data.size() < 1) - return NONE; + return ExifEndianness::NONE; if( m_data.size() > 1 && m_data[0] != m_data[1] ) { - return NONE; + return ExifEndianness::NONE; } if( m_data[0] == 'I' ) { - return INTEL; + return ExifEndianness::INTEL; } if( m_data[0] == 'M' ) { - return MOTO; + return ExifEndianness::MOTO; } - return NONE; + return ExifEndianness::NONE; } /** @@ -314,80 +584,53 @@ size_t ExifReader::getNumDirEntry(const size_t offsetNumDir) const * Details can be found here: http://www.media.mit.edu/pia/Research/deepview/exif.html * * @param [in] offset Offset to entry in bytes inside raw exif data - * @return ExifEntry_t structure which corresponds to particular entry + * @return ExifEntry structure which corresponds to particular entry * */ -ExifEntry_t ExifReader::parseExifEntry(const size_t offset) +ExifEntry ExifReader::parseExifEntry(const size_t offset) { - ExifEntry_t entry; - uint16_t tagNum = getExifTag( offset ); - entry.tag = tagNum; + ExifEntry exifentry; + exifentry.tagId = ExifTagId(getU16(offset)); + exifentry.type = ExifTagType(getU16(offset + 2)); + exifentry.count = getU32(offset + 4); - switch( tagNum ) + switch (exifentry.type) { - case IMAGE_DESCRIPTION: - entry.field_str = getString( offset ); - break; - case MAKE: - entry.field_str = getString( offset ); - break; - case MODEL: - entry.field_str = getString( offset ); - break; - case ORIENTATION: - entry.field_u16 = getOrientation( offset ); - break; - case XRESOLUTION: - entry.field_u_rational = getResolution( offset ); - break; - case YRESOLUTION: - entry.field_u_rational = getResolution( offset ); - break; - case RESOLUTION_UNIT: - entry.field_u16 = getResolutionUnit( offset ); - break; - case SOFTWARE: - entry.field_str = getString( offset ); - break; - case DATE_TIME: - entry.field_str = getString( offset ); - break; - case WHITE_POINT: - entry.field_u_rational = getWhitePoint( offset ); - break; - case PRIMARY_CHROMATICIES: - entry.field_u_rational = getPrimaryChromaticies( offset ); - break; - case Y_CB_CR_COEFFICIENTS: - entry.field_u_rational = getYCbCrCoeffs( offset ); - break; - case Y_CB_CR_POSITIONING: - entry.field_u16 = getYCbCrPos( offset ); - break; - case REFERENCE_BLACK_WHITE: - entry.field_u_rational = getRefBW( offset ); - break; - case COPYRIGHT: - entry.field_str = getString( offset ); - break; - case EXIF_OFFSET: - break; - default: - entry.tag = INVALID_TAG; - break; + case TAG_TYPE_BYTE: + case TAG_TYPE_SBYTE: + exifentry.setValueAsInt(m_data[offset + 8]); + break; + case TAG_TYPE_ASCII: + exifentry.setValueAsString(getString(offset)); + break; + + case TAG_TYPE_SHORT: + exifentry.setValueAsInt(getU16(offset + 8)); + break; + + case TAG_TYPE_UNDEFINED: + case TAG_TYPE_LONG: + exifentry.setValueAsInt(getU32(offset + 8)); + break; + + case TAG_TYPE_SSHORT: + exifentry.setValueAsInt((int16_t)getU16(offset + 8)); + break; + + case TAG_TYPE_SLONG: + exifentry.setValueAsInt((int32_t)getU32(offset + 8)); + break; + + case TAG_TYPE_RATIONAL: + case TAG_TYPE_SRATIONAL: + exifentry.setValueAsRational(getSRational(offset)); + break; + default: + CV_LOG_WARNING(NULL, "Undefined ExifTagValue type " << (int)exifentry.type); + break; } - return entry; -} -/** - * @brief Get tag number from raw exif data - * This is internal function and is not exposed to client - * @param [in] offset Offset to entry in bytes inside raw exif data - * @return tag number - */ -uint16_t ExifReader::getExifTag(const size_t offset) const -{ - return getU16( offset ); + return exifentry; } /** @@ -399,7 +642,7 @@ uint16_t ExifReader::getExifTag(const size_t offset) const std::string ExifReader::getString(const size_t offset) const { size_t size = getU32( offset + 4 ); - size_t dataOffset = 8; // position of data in the field + size_t dataOffset = offset + 8; // position of data in the field if( size > maxDataSize ) { dataOffset = getU32( offset + 8 ); @@ -409,7 +652,6 @@ std::string ExifReader::getString(const size_t offset) const } std::vector::const_iterator it = m_data.begin() + dataOffset; std::string result( it, it + size ); //copy vector content into result - return result; } @@ -424,7 +666,7 @@ uint16_t ExifReader::getU16(const size_t offset) const if (offset + 1 >= m_data.size()) throw ExifParsingError(); - if( m_format == INTEL ) + if( m_format == ExifEndianness::INTEL ) { return m_data[offset] + ( m_data[offset + 1] << 8 ); } @@ -442,7 +684,7 @@ uint32_t ExifReader::getU32(const size_t offset) const if (offset + 3 >= m_data.size()) throw ExifParsingError(); - if( m_format == INTEL ) + if( m_format == ExifEndianness::INTEL ) { return m_data[offset] + ( m_data[offset + 1] << 8 ) + @@ -465,143 +707,242 @@ uint32_t ExifReader::getU32(const size_t offset) const * "rational" means a fractional value, it contains 2 signed/unsigned long integer value, * and the first represents the numerator, the second, the denominator. */ -u_rational_t ExifReader::getURational(const size_t offset) const -{ - uint32_t numerator = getU32( offset ); - uint32_t denominator = getU32( offset + 4 ); - - return std::make_pair( numerator, denominator ); - -} - -/** - * @brief Get orientation information from raw exif data - * This is internal function and is not exposed to client - * @param [in] offset Offset to entry in bytes inside raw exif data - * @return orientation number - */ -uint16_t ExifReader::getOrientation(const size_t offset) const -{ - return getU16( offset + 8 ); -} - -/** - * @brief Get resolution information from raw exif data - * This is internal function and is not exposed to client - * @param [in] offset Offset to entry in bytes inside raw exif data - * @return resolution value - */ -std::vector ExifReader::getResolution(const size_t offset) const +std::vector ExifReader::getURational(const size_t offset) const { - std::vector result; - uint32_t rationalOffset = getU32( offset + 8 ); - result.push_back( getURational( rationalOffset ) ); - - return result; -} - -/** - * @brief Get resolution unit from raw exif data - * This is internal function and is not exposed to client - * @param [in] offset Offset to entry in bytes inside raw exif data - * @return resolution unit value - */ -uint16_t ExifReader::getResolutionUnit(const size_t offset) const -{ - return getU16( offset + 8 ); -} - -/** - * @brief Get White Point information from raw exif data - * This is internal function and is not exposed to client - * @param [in] offset Offset to entry in bytes inside raw exif data - * @return White Point value - * - * If the image uses CIE Standard Illumination D65(known as international - * standard of 'daylight'), the values are '3127/10000,3290/10000'. - */ -std::vector ExifReader::getWhitePoint(const size_t offset) const -{ - std::vector result; - uint32_t rationalOffset = getU32( offset + 8 ); - result.push_back( getURational( rationalOffset ) ); - result.push_back( getURational( rationalOffset + 8 ) ); - - return result; -} - -/** - * @brief Get Primary Chromaticies information from raw exif data - * This is internal function and is not exposed to client - * @param [in] offset Offset to entry in bytes inside raw exif data - * @return vector with primary chromaticies values - * - */ -std::vector ExifReader::getPrimaryChromaticies(const size_t offset) const -{ - std::vector result; - uint32_t rationalOffset = getU32( offset + 8 ); - for( size_t i = 0; i < primaryChromaticiesComponents; i++ ) + std::vector result; + size_t dataOffset = getU32(offset + 8); + if (dataOffset > m_data.size() || dataOffset + 8 > m_data.size()) { + throw ExifParsingError(); + } + for (uint32_t count = getU32(offset + 4); count > 0; count--) { - result.push_back( getURational( rationalOffset ) ); - rationalOffset += 8; + urational64_t item; + item.num = getU32(dataOffset); + item.denom = getU32(dataOffset + 4); + result.push_back(item); + dataOffset += 8; } return result; } -/** - * @brief Get YCbCr Coefficients information from raw exif data - * This is internal function and is not exposed to client - * @param [in] offset Offset to entry in bytes inside raw exif data - * @return vector with YCbCr coefficients values - * - */ -std::vector ExifReader::getYCbCrCoeffs(const size_t offset) const +std::vector ExifReader::getSRational(const size_t offset) const { - std::vector result; - uint32_t rationalOffset = getU32( offset + 8 ); - for( size_t i = 0; i < ycbcrCoeffs; i++ ) + std::vector result; + size_t dataOffset = getU32(offset + 8); + if (dataOffset > m_data.size() || dataOffset + 8 > m_data.size()) { + throw ExifParsingError(); + } + for (uint32_t count = getU32(offset + 4); count > 0; count--) { - result.push_back( getURational( rationalOffset ) ); - rationalOffset += 8; + srational64_t item; + item.num = getU32(dataOffset); + item.denom = getU32(dataOffset + 4); + result.push_back(item); + dataOffset += 8; } return result; } -/** - * @brief Get YCbCr Positioning information from raw exif data - * This is internal function and is not exposed to client - * @param [in] offset Offset to entry in bytes inside raw exif data - * @return vector with YCbCr positioning value - * - */ -uint16_t ExifReader::getYCbCrPos(const size_t offset) const +std::string ExifEntry::getTagTypeAsString() const { - return getU16( offset + 8 ); + const char* typestr = + type == TAG_TYPE_NOTYPE ? "NoType" : + type == TAG_TYPE_BYTE ? "Byte" : + type == TAG_TYPE_ASCII ? "ASCII" : + type == TAG_TYPE_SHORT ? "Short" : + type == TAG_TYPE_LONG ? "Long" : + type == TAG_TYPE_RATIONAL ? "Rational" : + type == TAG_TYPE_SBYTE ? "SByte" : + type == TAG_TYPE_UNDEFINED ? "Undefined" : + type == TAG_TYPE_SSHORT ? "SShort" : + type == TAG_TYPE_SLONG ? "SLong" : + type == TAG_TYPE_SRATIONAL ? "SRational" : + type == TAG_TYPE_FLOAT ? "Float" : + type == TAG_TYPE_DOUBLE ? "Double" : + type == TAG_TYPE_IFD ? "IFD" : + type == TAG_TYPE_LONG8 ? "Long8" : + type == TAG_TYPE_SLONG8 ? "SLong8" : + type == TAG_TYPE_IFD8 ? "IFD8" : nullptr; + return typestr ? std::string(typestr) : cv::format("Unkhown type <%d>", (int)type); } -/** - * @brief Get Reference Black&White point information from raw exif data - * This is internal function and is not exposed to client - * @param [in] offset Offset to entry in bytes inside raw exif data - * @return vector with reference BW points - * - * In case of YCbCr format, first 2 show black/white of Y, next 2 are Cb, - * last 2 are Cr. In case of RGB format, first 2 show black/white of R, - * next 2 are G, last 2 are B. - * - */ -std::vector ExifReader::getRefBW(const size_t offset) const +std::string ExifEntry::getTagIdAsString() const +{ + int tag = tagId; + const char* tagstr = + tag == TAG_EMPTY ? "" : + tag == TAG_SUB_FILETYPE ? "Sub File Type" : + tag == TAG_IMAGE_WIDTH ? "Image Width" : + tag == TAG_IMAGE_LENGTH ? "Image Height" : + tag == TAG_BITS_PER_SAMPLE ? "Bits Per Sample" : + tag == TAG_COMPRESSION ? "Compression" : + tag == TAG_PHOTOMETRIC ? "Photometric" : + tag == TAG_IMAGEDESCRIPTION ? "Image Description" : + tag == TAG_MAKE ? "Make" : + tag == TAG_MODEL ? "Model" : + tag == TAG_STRIP_OFFSET ? "Strip Offset" : + tag == TAG_SAMPLES_PER_PIXEL ? "Samples Per Pixel" : + tag == TAG_ROWS_PER_STRIP ? "Rows Per Strip" : + tag == TAG_STRIP_BYTE_COUNTS ? "Strip Byte Counts" : + tag == TAG_PLANAR_CONFIG ? "Planar Config" : + tag == TAG_ORIENTATION ? "Orientation" : + tag == TAG_XRESOLUTION ? "XResolution" : + tag == TAG_YRESOLUTION ? "YResolution" : + tag == TAG_RESOLUTION_UNIT ? "Resolution Unit" : + tag == TAG_SOFTWARE ? "Software" : + tag == TAG_MODIFYDATE ? "Modify Date" : + tag == TAG_HOST_COMPUTER ? "Host Computer" : + + tag == TAG_SAMPLEFORMAT ? "Sample Format" : + tag == TAG_YCBCRPOSITIONING ? "YCbCr Positioning" : + tag == TAG_JPGFROMRAWSTART ? "Jpg From Raw Start " : + tag == TAG_JPGFROMRAWLENGTH ? "Jpg From Raw Length" : + tag == TAG_CFA_REPEAT_PATTERN_DIM ? "CFA Repeat Pattern Dim" : + tag == TAG_CFA_PATTERN ? "CFA Pattern" : + + tag == TAG_COMPONENTSCONFIGURATION ? "Components Configuration" : + + tag == TAG_COPYRIGHT ? "Copyright" : + tag == TAG_EXPOSURE_TIME ? "Exposure Time" : + tag == TAG_FNUMBER ? "FNumber" : + + tag == TAG_EXIF_OFFSET ? "Exif Offset" : + + tag == TAG_EXPOSUREPROGRAM ? "Exposure Program" : + tag == TAG_GPSINFO ? "GPS Info" : + tag == TAG_ISOSPEED ? "ISO Speed" : + + tag == TAG_DATETIME_CREATE ? "Create Date" : + tag == TAG_DATETIME_ORIGINAL ? "DateTime Original" : + + tag == TAG_OFFSETTIME ? "Offset Time" : + tag == TAG_OFFSETTIME_ORIGINAL ? "Offset Time Original" : + tag == TAG_OFFSETTIME_DIGITIZED ? "Offset Time Digitized" : + + tag == TAG_FLASH ? "Flash" : + tag == TAG_FOCALLENGTH ? "Focal Length" : + tag == TAG_EP_STANDARD_ID ? "TIFF/EPStandardID" : + + tag == TAG_SHUTTER_SPEED ? "Shutter Speed" : + tag == TAG_APERTURE_VALUE ? "Aperture Value" : + tag == TAG_BRIGHTNESS_VALUE ? "Brightness Value" : + tag == TAG_EXPOSUREBIASVALUE ? "Exposure Bias Value" : + tag == TAG_MAXAPERTUREVALUE ? "Max Aperture Value" : + tag == TAG_SUBJECTDISTANCE ? "Subject Distance" : + tag == TAG_METERINGMODE ? "Metering Mode" : + tag == TAG_LIGHTSOURCE ? "Light Source" : + tag == TAG_FLASH ? "Flash" : + tag == TAG_SUBJECT_AREA ? "Subject Area" : + tag == TAG_MAKERNOTE ? "Maker Note" : + tag == TAG_USERCOMMENT ? "User Comment" : + + tag == TAG_SUBSECTIME ? "SubSec Time" : + tag == TAG_SUBSECTIME_ORIGINAL ? "SubSec Original Time" : + tag == TAG_SUBSECTIME_DIGITIZED ? "SubSec Digitized Time" : + + tag == TAG_FLASHPIXVERSION ? "Flashpix Version" : + tag == TAG_COLORSPACE ? "ColorSpace" : + tag == TAG_EXIF_IMAGE_WIDTH ? "Exif Image Width" : + tag == TAG_EXIF_IMAGE_HEIGHT ? "Exif Image Height" : + tag == TAG_WHITE_BALANCE ? "White Balance" : + + tag == TAG_EXIF_VERSION ? "Exif Version" : + + tag == TAG_FOCALPLANEXRESOLUTION ? "Foca lPlane XResolution" : + tag == TAG_FOCALPLANEYRESOLUTION ? "Focal Plane YResolution" : + tag == TAG_FOCALPLANERESOLUTIONUNIT ? "Focal Plane Resolution Unit" : + + tag == TAG_SCENE_TYPE ? "Scene Type" : + + tag == TAG_CUSTOMRENDERED ? "Custom Rendered" : + tag == TAG_EXPOSUREMODE ? "Exposure Mode" : + + tag == TAG_BODYSERIALNUMBER ? "Body Serial Number" : + tag == TAG_LENSSPECIFICATION ? "Lens Specification" : + tag == TAG_LENSMODEL ? "Lens Model" : + tag == TAG_SCENECAPTURETYPE ? "Scene Capture Type" : + + tag == TAG_DNG_VERSION ? "DNG Version" : + tag == TAG_DNG_BACKWARD_VERSION ? "DNG Backward Version" : + tag == TAG_UNIQUE_CAMERA_MODEL ? "Unique Camera Model" : + tag == TAG_CHROMA_BLUR_RADIUS ? "ChromaBlurRadius" : + tag == TAG_CFA_PLANECOLOR ? "CFAPlaneColor" : + tag == TAG_CFA_LAYOUT ? "CFALayout" : + tag == TAG_BLACK_LEVEL_REPEAT_DIM ? "Black Level Repeat Dim" : + tag == TAG_BLACK_LEVEL ? "Black Level" : + tag == TAG_WHITE_LEVEL ? "White Level" : + tag == TAG_DEFAULT_SCALE ? "Default Scale" : + tag == TAG_DEFAULT_CROP_ORIGIN ? "Default Crop Origin" : + tag == TAG_DEFAULT_CROP_SIZE ? "Default Crop Size" : + tag == TAG_COLOR_MATRIX1 ? "Color Matrix1" : + tag == TAG_COLOR_MATRIX2 ? "Color Matrix2" : + tag == TAG_CAMERA_CALIBRATION1 ? "Camera Calibration1" : + tag == TAG_CAMERA_CALIBRATION2 ? "Camera Calibration2" : + tag == TAG_ANALOG_BALANCE ? "Analog Balance" : + tag == TAG_AS_SHOT_NEUTRAL ? "AsShotNeutral" : + tag == TAG_AS_SHOT_WHITE_XY ? "AsShotWhiteXY" : + tag == TAG_BASELINE_EXPOSURE ? "Baseline Exposure" : + tag == TAG_CALIBRATION_ILLUMINANT1 ? "Calibration Illuminant1" : + tag == TAG_CALIBRATION_ILLUMINANT2 ? "Calibration Illuminant2" : + tag == TAG_EXTRA_CAMERA_PROFILES ? "Extra Camera Profiles" : + tag == TAG_PROFILE_NAME ? "Profile Name" : + tag == TAG_AS_SHOT_PROFILE_NAME ? "AsShotProfileName" : + tag == TAG_PREVIEW_COLORSPACE ? "PreviewColorspace" : + tag == TAG_OPCODE_LIST2 ? "OpCodeList2" : + tag == TAG_NOISE_PROFILE ? "NoiseProfile" : + tag == TAG_DEFAULT_BLACK_RENDER ? "Black Render" : + tag == TAG_ACTIVE_AREA ? "Active Area" : + tag == TAG_FORWARD_MATRIX1 ? "Forward Matrix1" : + tag == TAG_FORWARD_MATRIX2 ? "Forward Matrix2" : nullptr; + return tagstr ? std::string(tagstr) : cv::format("(%d)", (int)tag); +}; + +std::ostream& ExifEntry::dump(std::ostream& strm) const { - const size_t rationalFieldSize = 8; - std::vector result; - uint32_t rationalOffset = getU32( offset + rationalFieldSize ); - for( size_t i = 0; i < refBWComponents; i++ ) + if (empty()) { + strm << ""; + return strm; + } + + strm << getTagIdAsString() << ": "; + + switch (type) { + case TAG_TYPE_ASCII: + strm << "\"" << getValueAsString() << "\""; + break; + case TAG_TYPE_BYTE: + case TAG_TYPE_SHORT: + case TAG_TYPE_LONG: + strm << getValueAsInt(); + break; + case TAG_TYPE_FLOAT: + + strm << getValueAsInt(); + break; + case TAG_TYPE_DOUBLE: + strm << getValueAsInt(); + break; + case TAG_TYPE_RATIONAL: + case TAG_TYPE_SRATIONAL: + dumpVector(strm, getValueAsRational()); + break; + case TAG_TYPE_UNDEFINED: { - result.push_back( getURational( rationalOffset ) ); - rationalOffset += rationalFieldSize; + strm << "["; + for (size_t i = 0; i < value_raw.size(); ++i) { + if (i) strm << " "; + strm << std::hex << std::setw(2) << std::setfill('0') + << static_cast(value_raw[i]); + } + strm << "]" << std::dec; + break; } - return result; + default: + break; + } + + strm << std::endl; + return strm; } } //namespace cv diff --git a/modules/imgcodecs/src/exif.hpp b/modules/imgcodecs/src/exif.hpp index 137d383ee237..0ae0f4f88c9b 100644 --- a/modules/imgcodecs/src/exif.hpp +++ b/modules/imgcodecs/src/exif.hpp @@ -1,44 +1,6 @@ -/*M/////////////////////////////////////////////////////////////////////////////////////// -// -// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. -// -// By downloading, copying, installing or using the software you agree to this license. -// If you do not agree to this license, do not download, install, -// copy or use the software. -// -// -// License Agreement -// For Open Source Computer Vision Library -// -// Copyright (C) 2000-2008, Intel Corporation, all rights reserved. -// Copyright (C) 2009, Willow Garage Inc., all rights reserved. -// Third party copyrights are property of their respective owners. -// -// Redistribution and use in source and binary forms, with or without modification, -// are permitted provided that the following conditions are met: -// -// * Redistribution's of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// * Redistribution's in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// * The name of the copyright holders may not be used to endorse or promote products -// derived from this software without specific prior written permission. -// -// This software is provided by the copyright holders and contributors "as is" and -// any express or implied warranties, including, but not limited to, the implied -// warranties of merchantability and fitness for a particular purpose are disclaimed. -// In no event shall the Intel Corporation or contributors be liable for any direct, -// indirect, incidental, special, exemplary, or consequential damages -// (including, but not limited to, procurement of substitute goods or services; -// loss of use, data, or profits; or business interruption) however caused -// and on any theory of liability, whether in contract, strict liability, -// or tort (including negligence or otherwise) arising in any way out of -// the use of this software, even if advised of the possibility of such damage. -// -//M*/ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html #ifndef _OPENCV_EXIF_HPP_ @@ -55,99 +17,17 @@ namespace cv { -/** - * @brief Base Exif tags used by IFD0 (main image) - */ -enum ExifTagName -{ - IMAGE_DESCRIPTION = 0x010E, ///< Image Description: ASCII string - MAKE = 0x010F, ///< Description of manufacturer: ASCII string - MODEL = 0x0110, ///< Description of camera model: ASCII string - ORIENTATION = 0x0112, ///< Orientation of the image: unsigned short - XRESOLUTION = 0x011A, ///< Resolution of the image across X axis: unsigned rational - YRESOLUTION = 0x011B, ///< Resolution of the image across Y axis: unsigned rational - RESOLUTION_UNIT = 0x0128, ///< Resolution units. '1' no-unit, '2' inch, '3' centimeter - SOFTWARE = 0x0131, ///< Shows firmware(internal software of digicam) version number - DATE_TIME = 0x0132, ///< Date/Time of image was last modified - WHITE_POINT = 0x013E, ///< Chromaticity of white point of the image - PRIMARY_CHROMATICIES = 0x013F, ///< Chromaticity of the primaries of the image - Y_CB_CR_COEFFICIENTS = 0x0211, ///< constant to translate an image from YCbCr to RGB format - Y_CB_CR_POSITIONING = 0x0213, ///< Chroma sample point of subsampling pixel array - REFERENCE_BLACK_WHITE = 0x0214, ///< Reference value of black point/white point - COPYRIGHT = 0x8298, ///< Copyright information - EXIF_OFFSET = 0x8769, ///< Offset to Exif Sub IFD - INVALID_TAG = 0xFFFF ///< Shows that the tag was not recognized -}; - -enum Endianness_t -{ - INTEL = 0x49, - MOTO = 0x4D, - NONE = 0x00 -}; - -typedef std::pair u_rational_t; - -/** - * @brief Entry which contains possible values for different exif tags - */ -struct ExifEntry_t -{ - ExifEntry_t(); - - std::vector field_u_rational; ///< vector of rational fields - std::string field_str; ///< any kind of textual information - - float field_float; ///< Currently is not used - double field_double; ///< Currently is not used - - uint32_t field_u32; ///< Unsigned 32-bit value - int32_t field_s32; ///< Signed 32-bit value - - uint16_t tag; ///< Tag number - - uint16_t field_u16; ///< Unsigned 16-bit value - int16_t field_s16; ///< Signed 16-bit value - uint8_t field_u8; ///< Unsigned 8-bit value - int8_t field_s8; ///< Signed 8-bit value -}; - -/** - * @brief Picture orientation which may be taken from EXIF - * Orientation usually matters when the picture is taken by - * smartphone or other camera with orientation sensor support - * Corresponds to EXIF 2.3 Specification - */ -enum ImageOrientation -{ - IMAGE_ORIENTATION_TL = 1, ///< Horizontal (normal) - IMAGE_ORIENTATION_TR = 2, ///< Mirrored horizontal - IMAGE_ORIENTATION_BR = 3, ///< Rotate 180 - IMAGE_ORIENTATION_BL = 4, ///< Mirrored vertical - IMAGE_ORIENTATION_LT = 5, ///< Mirrored horizontal & rotate 270 CW - IMAGE_ORIENTATION_RT = 6, ///< Rotate 90 CW - IMAGE_ORIENTATION_RB = 7, ///< Mirrored horizontal & rotate 90 CW - IMAGE_ORIENTATION_LB = 8 ///< Rotate 270 CW -}; - -/** - * @brief Reading exif information from Jpeg file - * - * Usage example for getting the orientation of the image: - * - * @code - * std::ifstream stream(filename,std::ios_base::in | std::ios_base::binary); - * ExifReader reader(stream); - * if( reader.parse() ) - * { - * int orientation = reader.getTag(Orientation).field_u16; - * } - * @endcode - * - */ class ExifReader { public: + + enum ExifEndianness + { + INTEL = 0x49, + MOTO = 0x4D, + NONE = 0x00 + }; + /** * @brief ExifReader constructor. Constructs an object of exif reader */ @@ -166,15 +46,15 @@ class ExifReader * false if parsing error */ - bool parseExif(unsigned char* data, const size_t size); - + bool parseExif(const unsigned char* data, const size_t size); + bool parseExif(const unsigned char* data, const size_t size, std::vector< std::vector >& exif_entries); /** * @brief Get tag info by tag number * * @param [in] tag The tag number * @return ExifEntru_t structure. Caller has to know what tag it calls in order to extract proper field from the structure ExifEntry_t */ - ExifEntry_t getTag( const ExifTagName tag ) const; + ExifEntry getEntrybyTagId( const ExifTagId tag ) const; /** * @brief Get the whole exif buffer @@ -183,35 +63,29 @@ class ExifReader private: std::vector m_data; - std::map m_exif; - Endianness_t m_format; + std::map m_exif; + int m_format; void parseExif(); bool checkTagMark() const; - size_t getNumDirEntry( const size_t offsetNumDir ) const; - uint32_t getStartOffset() const; - uint16_t getExifTag( const size_t offset ) const; - uint16_t getU16( const size_t offset ) const; - uint32_t getU32( const size_t offset ) const; - uint16_t getOrientation( const size_t offset ) const; - uint16_t getResolutionUnit( const size_t offset ) const; - uint16_t getYCbCrPos( const size_t offset ) const; - - Endianness_t getFormat() const; - - ExifEntry_t parseExifEntry( const size_t offset ); - - u_rational_t getURational( const size_t offset ) const; - - std::string getString( const size_t offset ) const; - std::vector getResolution( const size_t offset ) const; - std::vector getWhitePoint( const size_t offset ) const; - std::vector getPrimaryChromaticies( const size_t offset ) const; - std::vector getYCbCrCoeffs( const size_t offset ) const; - std::vector getRefBW( const size_t offset ) const; + size_t getNumDirEntry( const size_t offsetNumDir ) const; + uint32_t getStartOffset() const; + ExifTagId getExifTagId( const size_t offset ) const; + uint16_t getU16( const size_t offset ) const; + uint32_t getU32( const size_t offset ) const; + std::string getString(const size_t offset) const; + uint16_t getOrientation( const size_t offset ) const; + int getFormat() const; + uint32_t extractIFDOffset(const ExifEntry& entry) const; + ExifEntry parseExifEntry( const size_t offset ); + std::vector getURational(const size_t offset) const; + std::vector getSRational(const size_t offset) const; private: + template + std::vector getRational(size_t offset, IntReader readInt32) const; + static const uint16_t tagMarkRequired = 0x2A; //max size of data in tag. @@ -221,18 +95,8 @@ class ExifReader //bytes per tag field static const size_t tiffFieldSize = 12; - - //number of primary chromaticies components - static const size_t primaryChromaticiesComponents = 6; - - //number of YCbCr coefficients in field - static const size_t ycbcrCoeffs = 3; - - //number of Reference Black&White components - static const size_t refBWComponents = 6; }; - } #endif /* _OPENCV_EXIF_HPP_ */ diff --git a/modules/imgcodecs/src/grfmt_base.cpp b/modules/imgcodecs/src/grfmt_base.cpp index 9c9dead740b6..76334011e724 100644 --- a/modules/imgcodecs/src/grfmt_base.cpp +++ b/modules/imgcodecs/src/grfmt_base.cpp @@ -88,9 +88,9 @@ Mat BaseImageDecoder::getMetadata(ImageMetadataType type) const return Mat(); } -ExifEntry_t BaseImageDecoder::getExifTag(const ExifTagName tag) const +ExifEntry BaseImageDecoder::getExifEntrybyTagId(const ExifTagId tag) const { - return m_exif.getTag(tag); + return m_exif.getEntrybyTagId(tag); } bool BaseImageDecoder::setSource( const String& filename ) diff --git a/modules/imgcodecs/src/grfmt_base.hpp b/modules/imgcodecs/src/grfmt_base.hpp index 889fdd0a1da8..a1a38aea9d75 100644 --- a/modules/imgcodecs/src/grfmt_base.hpp +++ b/modules/imgcodecs/src/grfmt_base.hpp @@ -88,7 +88,7 @@ class BaseImageDecoder { * @param tag The EXIF tag to retrieve. * @return The EXIF entry corresponding to the tag. */ - ExifEntry_t getExifTag(const ExifTagName tag) const; + ExifEntry getExifEntrybyTagId(const ExifTagId tag) const; /** * @brief Set the image source from a file. diff --git a/modules/imgcodecs/src/loadsave.cpp b/modules/imgcodecs/src/loadsave.cpp index 8e354c4dc939..934e211fe46c 100644 --- a/modules/imgcodecs/src/loadsave.cpp +++ b/modules/imgcodecs/src/loadsave.cpp @@ -402,13 +402,13 @@ static void ExifTransform(int orientation, OutputArray img) } } -static void ApplyExifOrientation(ExifEntry_t orientationTag, OutputArray img) +static void ApplyExifOrientation(ExifEntry orientationTag, OutputArray img) { int orientation = IMAGE_ORIENTATION_TL; - if (orientationTag.tag != INVALID_TAG) + if (orientationTag.tagId != TAG_INVALID_TAG) { - orientation = orientationTag.field_u16; //orientation is unsigned short, so check field_u16 + orientation = orientationTag.getValueAsInt(); ExifTransform(orientation, img); } } @@ -614,7 +614,7 @@ imread_( const String& filename, int flags, OutputArray mat, /// optionally rotate the data if EXIF orientation flag says so if (!mat.empty() && (flags & IMREAD_IGNORE_ORIENTATION) == 0 && flags != IMREAD_UNCHANGED ) { - ApplyExifOrientation(decoder->getExifTag(ORIENTATION), mat); + ApplyExifOrientation(decoder->getExifEntrybyTagId(TAG_ORIENTATION), mat); } return true; @@ -714,7 +714,7 @@ imreadmulti_(const String& filename, int flags, std::vector& mats, int star // optionally rotate the data if EXIF' orientation flag says so if ((flags & IMREAD_IGNORE_ORIENTATION) == 0 && flags != IMREAD_UNCHANGED) { - ApplyExifOrientation(decoder->getExifTag(ORIENTATION), mat); + ApplyExifOrientation(decoder->getExifEntrybyTagId(TAG_ORIENTATION), mat); } mats.push_back(mat); @@ -882,7 +882,7 @@ imreadanimation_(const String& filename, int flags, int start, int count, Animat // optionally rotate the data if EXIF' orientation flag says so if ((flags & IMREAD_IGNORE_ORIENTATION) == 0 && flags != IMREAD_UNCHANGED) { - ApplyExifOrientation(decoder->getExifTag(ORIENTATION), mat); + ApplyExifOrientation(decoder->getExifEntrybyTagId(TAG_ORIENTATION), mat); } if (current >= start) @@ -993,7 +993,7 @@ static bool imdecodeanimation_(InputArray buf, int flags, int start, int count, // optionally rotate the data if EXIF' orientation flag says so if ((flags & IMREAD_IGNORE_ORIENTATION) == 0 && flags != IMREAD_UNCHANGED) { - ApplyExifOrientation(decoder->getExifTag(ORIENTATION), mat); + ApplyExifOrientation(decoder->getExifEntrybyTagId(TAG_ORIENTATION), mat); } if (current >= start) @@ -1398,7 +1398,7 @@ imdecode_( const Mat& buf, int flags, Mat& mat, /// optionally rotate the data if EXIF' orientation flag says so if (!mat.empty() && (flags & IMREAD_IGNORE_ORIENTATION) == 0 && flags != IMREAD_UNCHANGED) { - ApplyExifOrientation(decoder->getExifTag(ORIENTATION), mat); + ApplyExifOrientation(decoder->getExifEntrybyTagId(TAG_ORIENTATION), mat); } return true; @@ -1554,7 +1554,7 @@ imdecodemulti_(const Mat& buf, int flags, std::vector& mats, int start, int // optionally rotate the data if EXIF' orientation flag says so if ((flags & IMREAD_IGNORE_ORIENTATION) == 0 && flags != IMREAD_UNCHANGED) { - ApplyExifOrientation(decoder->getExifTag(ORIENTATION), mat); + ApplyExifOrientation(decoder->getExifEntrybyTagId(TAG_ORIENTATION), mat); } mats.push_back(mat); @@ -1839,7 +1839,7 @@ Mat ImageCollection::Impl::readData() { return cv::Mat(); if ((m_flags & IMREAD_IGNORE_ORIENTATION) == 0 && m_flags != IMREAD_UNCHANGED) { - ApplyExifOrientation(m_decoder->getExifTag(ORIENTATION), mat); + ApplyExifOrientation(m_decoder->getExifEntrybyTagId(TAG_ORIENTATION), mat); } return mat; diff --git a/modules/imgcodecs/test/test_exif.cpp b/modules/imgcodecs/test/test_exif.cpp index 706896fedc2b..67d5b0c3b775 100644 --- a/modules/imgcodecs/test/test_exif.cpp +++ b/modules/imgcodecs/test/test_exif.cpp @@ -425,6 +425,110 @@ TEST(Imgcodecs_Jpeg, Read_Write_With_Exif) remove(outputname.c_str()); } +TEST(Imgcodecs_Png, encodeExif) +{ + const string root = cvtest::TS::ptr()->get_data_path(); + //string filename = root + "../stitching/boat2.jpg"; + string filename = root + "readwrite/testExifOrientation_7.png"; + std::vector metadata_types; + std::vector> metadata; + + Mat img1 = imreadWithMetadata(filename, metadata_types, metadata, IMREAD_UNCHANGED); + std::vector > exif_entries_vec; + decodeExif(metadata[0], exif_entries_vec); + +#if 0 + ExifEntry exifEntry = exif_entries_vec[0][4]; + exifEntry.dump(std::cout); + exif_entries_vec[0][4].type = TAG_TYPE_ASCII; + exif_entries_vec[0][4].setValueAsString("OpenCV 4.13"); + exifEntry = exif_entries_vec[0][4]; + exifEntry.dump(std::cout); +#endif + + std::vector exif_data; + encodeExif(exif_entries_vec, exif_data); + + std::vector metadata_types2 = { IMAGE_METADATA_EXIF }; + std::vector> metadata2 = { exif_data }; + + const string outputname = cv::tempfile(".png"); + imwriteWithMetadata(outputname, img1, metadata_types2, metadata2); + + std::vector metadata_types3; + std::vector> metadata3; + Mat img2 = imreadWithMetadata(outputname, metadata_types3, metadata3, IMREAD_UNCHANGED); + + EXPECT_EQ(metadata[0].size(), metadata3[0].size()); + EXPECT_EQ(0, cvtest::norm(img1, img2, NORM_INF)); + + // Clean up by removing the temporary file. + EXPECT_EQ(0, remove(outputname.c_str())); +} + +TEST(Imgcodecs_Png, Write_And_Read_Custom_Exif) +{ + const std::string root = cvtest::TS::ptr()->get_data_path(); + const std::string filename = root + "readwrite/testExifOrientation_7.jpg"; + + // Create custom EXIF entries + ExifEntry exifEntry0, exifEntry1, exifEntry2; + exifEntry0.tagId = TAG_IMAGEDESCRIPTION; + exifEntry0.type = TAG_TYPE_ASCII; + exifEntry0.setValueAsString("Test - Writing Custom Exif"); + + exifEntry1.tagId = TAG_SOFTWARE; + exifEntry1.type = TAG_TYPE_ASCII; + exifEntry1.setValueAsString("OpenCV 4.13"); + + exifEntry2.tagId = TAG_ORIENTATION; + exifEntry2.type = TAG_TYPE_SHORT; + exifEntry2.setValueAsInt(7); // orientation 7 expected + + std::vector ifd0; + ifd0.push_back(exifEntry0); + ifd0.push_back(exifEntry1); + ifd0.push_back(exifEntry2); + + std::vector> exif_entries_vec; + exif_entries_vec.push_back(ifd0); + + // Encode to binary EXIF segment + std::vector exif_data; + encodeExif(exif_entries_vec, exif_data); + + std::vector metadata_types = { IMAGE_METADATA_EXIF }; + std::vector> metadata = { exif_data }; + + Mat img = imread(filename, IMREAD_UNCHANGED); + ASSERT_FALSE(img.empty()); + + const std::string outputname = cv::tempfile(".png"); + imwriteWithMetadata(outputname, img, metadata_types, metadata); + + Mat img1 = imread(filename); + Mat img2 = imread(outputname); + EXPECT_EQ(0, cvtest::norm(img1, img2, cv::NORM_INF)); + + // Read back and validate EXIF + std::vector read_metadata_types; + std::vector> read_metadata; + imreadWithMetadata(outputname, read_metadata_types, read_metadata); + + ASSERT_FALSE(read_metadata.empty()); + ASSERT_EQ(read_metadata_types[0], IMAGE_METADATA_EXIF); + + std::vector> read_exif_entries; + bool parse_result = decodeExif(read_metadata[0], read_exif_entries); + ASSERT_TRUE(parse_result); + + EXPECT_EQ(read_exif_entries[0][0].getValueAsString(), "Test - Writing Custom Exif"); + EXPECT_EQ(read_exif_entries[0][1].getValueAsString(), "OpenCV 4.13"); + EXPECT_EQ(read_exif_entries[0][2].getValueAsInt(), 7); + + EXPECT_EQ(0, remove(outputname.c_str())); +} + TEST(Imgcodecs_Png, Read_Write_With_Exif) { int png_compression = 3; @@ -587,12 +691,23 @@ TEST_P(ReadExif_Sanity, Check) size_t iccp_size = iccp.total() * iccp.elemSize(); EXPECT_EQ(expected_iccp_size, iccp_size); } + + std::vector< std::vector > exif_entries_vec; + decodeExif(metadata[IMAGE_METADATA_EXIF], exif_entries_vec); + + std::cout << "\n------- decoded Exif IFD count : " << (int)exif_entries_vec.size() << std::endl; + for (size_t i = 0; i < exif_entries_vec.size(); i++) + for (size_t j = 0; j < exif_entries_vec[i].size(); j++) + { + exif_entries_vec[i][j].dump(std::cout); + } } static const std::vector exif_sanity_params { #ifdef HAVE_JPEG ReadExif_Sanity_Params("readwrite/testExifOrientation_3.jpg", 916, "Photoshop", 120, 3597, 940), + ReadExif_Sanity_Params("../stitching/boat2.jpg", 10630, "Photoshop", 152, 9118, 3144), #endif #ifdef OPENCV_IMGCODECS_PNG_WITH_EXIF ReadExif_Sanity_Params("readwrite/testExifOrientation_5.png", 112, "ExifTool", 102, 505, 0), @@ -600,6 +715,9 @@ static const std::vector exif_sanity_params #ifdef HAVE_AVIF ReadExif_Sanity_Params("readwrite/testExifOrientation_7.avif", 913, "Photoshop", 120, 3597, 940), #endif +#ifdef HAVE_WEBP + ReadExif_Sanity_Params("readwrite/testExifOrientation_2.webp", 112, "ExifTool", 102, 3597, 940), +#endif }; INSTANTIATE_TEST_CASE_P(Imgcodecs, ReadExif_Sanity, diff --git a/modules/python/test/test_imread.py b/modules/python/test/test_imread.py index 471c786acc91..a03fc5784493 100644 --- a/modules/python/test/test_imread.py +++ b/modules/python/test/test_imread.py @@ -29,6 +29,16 @@ def test_imread_with_meta(self): self.assertTrue(meta_types is not None) self.assertTrue(meta_data is not None) + result, exif_entries = cv.decodeExif(meta_data[0][0]) + self.assertTrue(result) + entry = exif_entries[0][0] + self.assertEqual(entry.tagId, 274) + self.assertEqual(entry.getValueAsInt(), 1) + entry = exif_entries[0][4] + self.assertEqual(entry.tagId, 305) + self.assertEqual(entry.getTagIdAsString(), 'Software') + self.assertEqual(entry.getValueAsString(), 'Adobe Photoshop CC 2015 (Windows)') + path = self.extraTestDataPath + '/highgui/readwrite/testExifOrientation_1.png' img, meta_types, meta_data = cv.imreadWithMetadata(path) self.assertTrue(img is not None)