diff --git a/modules/imgcodecs/src/exif.cpp b/modules/imgcodecs/src/exif.cpp index 051999c0fa..28d52047d8 100644 --- a/modules/imgcodecs/src/exif.cpp +++ b/modules/imgcodecs/src/exif.cpp @@ -62,7 +62,7 @@ ExifEntry_t::ExifEntry_t() : /** * @brief ExifReader constructor */ -ExifReader::ExifReader(std::istream& stream) : m_stream(stream), m_format(NONE) +ExifReader::ExifReader() : m_format(NONE) { } @@ -73,25 +73,6 @@ ExifReader::~ExifReader() { } -/** - * @brief Parsing the file and prepare (internally) exif directory structure - * @return true if parsing was successful and exif information exists in JpegReader object - * false in case of unsuccessful parsing - */ -bool ExifReader::parse() -{ - try { - m_exif = getExif(); - if( !m_exif.empty() ) - { - return true; - } - return false; - } catch (ExifParsingError&) { - return false; - } -} - /** * @brief Get tag value by tag number @@ -101,10 +82,10 @@ bool ExifReader::parse() * @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) +ExifEntry_t ExifReader::getTag(const ExifTagName tag) const { ExifEntry_t entry; - std::map::iterator it = m_exif.find(tag); + std::map::const_iterator it = m_exif.find(tag); if( it != m_exif.end() ) { @@ -115,100 +96,37 @@ ExifEntry_t ExifReader::getTag(const ExifTagName tag) /** - * @brief Get exif directory structure contained in file (if any) - * This is internal function and is not exposed to client + * @brief Parsing the exif data buffer and prepare (internal) exif directory + * + * @param [in] data The data buffer to read EXIF data starting with endianness + * @param [in] size The size of the data buffer * - * @return Map where key is tag number and value is ExifEntry_t structure + * @return true if parsing was successful + * false in case of unsuccessful parsing */ -std::map ExifReader::getExif() +bool ExifReader::parseExif(unsigned char* data, const size_t size) { - const std::streamsize markerSize = 2; - const std::streamsize offsetToTiffHeader = 6; //bytes from Exif size field to the first TIFF header - unsigned char appMarker[markerSize]; - m_exif.erase( m_exif.begin(), m_exif.end() ); - - std::streamsize count; - - bool exifFound = false, stopSearch = false; - while( ( !m_stream.eof() ) && !exifFound && !stopSearch ) + // Populate m_data, then call parseExif() (private) + if( data && size > 0 ) { - m_stream.read( reinterpret_cast(appMarker), markerSize ); - count = m_stream.gcount(); - if( count < markerSize ) - { - break; - } - unsigned char marker = appMarker[1]; - size_t bytesToSkip; - size_t exifSize; - switch( marker ) - { - //For all the markers just skip bytes in file pointed by followed two bytes (field size) - case SOF0: case SOF2: case DHT: case DQT: case DRI: case SOS: - case RST0: case RST1: case RST2: case RST3: case RST4: case RST5: case RST6: case RST7: - case APP0: case APP2: case APP3: case APP4: case APP5: case APP6: case APP7: case APP8: - case APP9: case APP10: case APP11: case APP12: case APP13: case APP14: case APP15: - case COM: - bytesToSkip = getFieldSize(); - if (bytesToSkip < markerSize) { - throw ExifParsingError(); - } - m_stream.seekg( static_cast( bytesToSkip - markerSize ), m_stream.cur ); - if ( m_stream.fail() ) { - throw ExifParsingError(); - } - break; - - //SOI and EOI don't have the size field after the marker - case SOI: case EOI: - break; - - case APP1: //actual Exif Marker - exifSize = getFieldSize(); - if (exifSize <= offsetToTiffHeader) { - throw ExifParsingError(); - } - m_data.resize( exifSize - offsetToTiffHeader ); - m_stream.seekg( static_cast( offsetToTiffHeader ), m_stream.cur ); - if ( m_stream.fail() ) { - throw ExifParsingError(); - } - m_stream.read( reinterpret_cast(&m_data[0]), exifSize - offsetToTiffHeader ); - exifFound = true; - break; - - default: //No other markers are expected according to standard. May be a signal of error - stopSearch = true; - break; - } + m_data.assign(data, data + size); } - - if( !exifFound ) + else { - return m_exif; + return false; } - parseExif(); - - return m_exif; -} - -/** - * @brief Get the size of exif field (required to properly ready whole exif from the file) - * This is internal function and is not exposed to client - * - * @return size of exif field in the file - */ -size_t ExifReader::getFieldSize () -{ - unsigned char fieldSize[2]; - m_stream.read( reinterpret_cast(fieldSize), 2 ); - std::streamsize count = m_stream.gcount(); - if (count < 2) - { - return 0; + try { + parseExif(); + if( !m_exif.empty() ) + { + return true; + } + return false; + } + catch( ExifParsingError& ) { + return false; } - return ( fieldSize[0] << 8 ) + fieldSize[1]; } /** diff --git a/modules/imgcodecs/src/exif.hpp b/modules/imgcodecs/src/exif.hpp index dc9a58ab0b..6cc95afb1a 100644 --- a/modules/imgcodecs/src/exif.hpp +++ b/modules/imgcodecs/src/exif.hpp @@ -54,24 +54,6 @@ namespace cv { -/** - * @brief Jpeg markers that can encounter in Jpeg file - */ -enum AppMarkerTypes -{ - SOI = 0xD8, SOF0 = 0xC0, SOF2 = 0xC2, DHT = 0xC4, - DQT = 0xDB, DRI = 0xDD, SOS = 0xDA, - - RST0 = 0xD0, RST1 = 0xD1, RST2 = 0xD2, RST3 = 0xD3, - RST4 = 0xD4, RST5 = 0xD5, RST6 = 0xD6, RST7 = 0xD7, - - APP0 = 0xE0, APP1 = 0xE1, APP2 = 0xE2, APP3 = 0xE3, - APP4 = 0xE4, APP5 = 0xE5, APP6 = 0xE6, APP7 = 0xE7, - APP8 = 0xE8, APP9 = 0xE9, APP10 = 0xEA, APP11 = 0xEB, - APP12 = 0xEC, APP13 = 0xED, APP14 = 0xEE, APP15 = 0xEF, - - COM = 0xFE, EOI = 0xD9 -}; /** * @brief Base Exif tags used by IFD0 (main image) @@ -168,19 +150,22 @@ class ExifReader public: /** * @brief ExifReader constructor. Constructs an object of exif reader - * - * @param [in]stream An istream to look for EXIF bytes from */ - explicit ExifReader( std::istream& stream ); + ExifReader(); ~ExifReader(); /** * @brief Parse the file with exif info * - * @return true if parsing was successful and exif information exists in JpegReader object + * @param [in] data The data buffer to read EXIF data starting with endianness + * @param [in] size The size of the data buffer + * + * @return true if successful parsing + * false if parsing error */ - bool parse(); + + bool parseExif(unsigned char* data, const size_t size); /** * @brief Get tag info by tag number @@ -188,10 +173,10 @@ public: * @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 ); + ExifEntry_t getTag( const ExifTagName tag ) const; + private: - std::istream& m_stream; std::vector m_data; std::map m_exif; Endianess_t m_format; @@ -199,7 +184,6 @@ private: void parseExif(); bool checkTagMark() const; - size_t getFieldSize (); size_t getNumDirEntry( const size_t offsetNumDir ) const; uint32_t getStartOffset() const; uint16_t getExifTag( const size_t offset ) const; @@ -215,7 +199,6 @@ private: u_rational_t getURational( const size_t offset ) const; - std::map getExif(); std::string getString( const size_t offset ) const; std::vector getResolution( const size_t offset ) const; std::vector getWhitePoint( const size_t offset ) const; diff --git a/modules/imgcodecs/src/grfmt_base.cpp b/modules/imgcodecs/src/grfmt_base.cpp index b7032c1723..c1a8854a42 100644 --- a/modules/imgcodecs/src/grfmt_base.cpp +++ b/modules/imgcodecs/src/grfmt_base.cpp @@ -55,6 +55,11 @@ BaseImageDecoder::BaseImageDecoder() m_scale_denom = 1; } + +ExifEntry_t BaseImageDecoder::getExifTag(const ExifTagName tag) const +{ + return m_exif.getTag(tag); +} bool BaseImageDecoder::setSource( const String& filename ) { m_filename = filename; diff --git a/modules/imgcodecs/src/grfmt_base.hpp b/modules/imgcodecs/src/grfmt_base.hpp index 7d75636cf5..816bef98fb 100644 --- a/modules/imgcodecs/src/grfmt_base.hpp +++ b/modules/imgcodecs/src/grfmt_base.hpp @@ -45,6 +45,7 @@ #include "utils.hpp" #include "bitstrm.hpp" +#include "exif.hpp" namespace cv { @@ -65,6 +66,7 @@ public: int height() const { return m_height; } virtual int type() const { return m_type; } + ExifEntry_t getExifTag(const ExifTagName tag) const; virtual bool setSource( const String& filename ); virtual bool setSource( const Mat& buf ); virtual int setScale( const int& scale_denom ); @@ -87,6 +89,7 @@ protected: String m_signature; Mat m_buf; bool m_buf_supported; + ExifReader m_exif; }; diff --git a/modules/imgcodecs/src/grfmt_jpeg.cpp b/modules/imgcodecs/src/grfmt_jpeg.cpp index ba5dab41df..758ac512e8 100644 --- a/modules/imgcodecs/src/grfmt_jpeg.cpp +++ b/modules/imgcodecs/src/grfmt_jpeg.cpp @@ -244,6 +244,7 @@ bool JpegDecoder::readHeader() if (state->cinfo.src != 0) { + jpeg_save_markers(&state->cinfo, APP1, 0xffff); jpeg_read_header( &state->cinfo, TRUE ); state->cinfo.scale_num=1; @@ -456,6 +457,29 @@ bool JpegDecoder::readData( Mat& img ) } } + // Check for Exif marker APP1 + jpeg_saved_marker_ptr exif_marker = NULL; + jpeg_saved_marker_ptr cmarker = cinfo->marker_list; + while( cmarker && exif_marker == NULL ) + { + if (cmarker->marker == APP1) + exif_marker = cmarker; + + cmarker = cmarker->next; + } + + // Parse Exif data + if( exif_marker ) + { + const std::streamsize offsetToTiffHeader = 6; //bytes from Exif size field to the first TIFF header + + if (exif_marker->data_length > offsetToTiffHeader) + { + m_exif.parseExif(exif_marker->data + offsetToTiffHeader, exif_marker->data_length - offsetToTiffHeader); + } + } + + jpeg_start_decompress( cinfo ); buffer = (*cinfo->mem->alloc_sarray)((j_common_ptr)cinfo, diff --git a/modules/imgcodecs/src/grfmt_jpeg.hpp b/modules/imgcodecs/src/grfmt_jpeg.hpp index 90d80b4b59..e7c8c25457 100644 --- a/modules/imgcodecs/src/grfmt_jpeg.hpp +++ b/modules/imgcodecs/src/grfmt_jpeg.hpp @@ -52,6 +52,25 @@ namespace cv { +/** +* @brief Jpeg markers that can be encountered in a Jpeg file +*/ +enum AppMarkerTypes +{ + SOI = 0xD8, SOF0 = 0xC0, SOF2 = 0xC2, DHT = 0xC4, + DQT = 0xDB, DRI = 0xDD, SOS = 0xDA, + + RST0 = 0xD0, RST1 = 0xD1, RST2 = 0xD2, RST3 = 0xD3, + RST4 = 0xD4, RST5 = 0xD5, RST6 = 0xD6, RST7 = 0xD7, + + APP0 = 0xE0, APP1 = 0xE1, APP2 = 0xE2, APP3 = 0xE3, + APP4 = 0xE4, APP5 = 0xE5, APP6 = 0xE6, APP7 = 0xE7, + APP8 = 0xE8, APP9 = 0xE9, APP10 = 0xEA, APP11 = 0xEB, + APP12 = 0xEC, APP13 = 0xED, APP14 = 0xEE, APP15 = 0xEF, + + COM = 0xFE, EOI = 0xD9 +}; + class JpegDecoder CV_FINAL : public BaseImageDecoder { diff --git a/modules/imgcodecs/src/grfmt_png.cpp b/modules/imgcodecs/src/grfmt_png.cpp index b533cd849f..9e1a2d4c71 100644 --- a/modules/imgcodecs/src/grfmt_png.cpp +++ b/modules/imgcodecs/src/grfmt_png.cpp @@ -284,6 +284,22 @@ bool PngDecoder::readData( Mat& img ) png_read_image( png_ptr, buffer ); png_read_end( png_ptr, end_info ); +#ifdef PNG_eXIf_SUPPORTED + png_uint_32 num_exif = 0; + png_bytep exif = 0; + + // Exif info could be in info_ptr (intro_info) or end_info per specification + if( png_get_valid(png_ptr, info_ptr, PNG_INFO_eXIf) ) + png_get_eXIf_1(png_ptr, info_ptr, &num_exif, &exif); + else if( png_get_valid(png_ptr, end_info, PNG_INFO_eXIf) ) + png_get_eXIf_1(png_ptr, end_info, &num_exif, &exif); + + if( exif && num_exif > 0 ) + { + m_exif.parseExif(exif, num_exif); + } +#endif + result = true; } } diff --git a/modules/imgcodecs/src/loadsave.cpp b/modules/imgcodecs/src/loadsave.cpp index 44c458c727..c8fcbea7ee 100644 --- a/modules/imgcodecs/src/loadsave.cpp +++ b/modules/imgcodecs/src/loadsave.cpp @@ -354,48 +354,15 @@ static void ExifTransform(int orientation, Mat& img) } } -static void ApplyExifOrientation(const String& filename, Mat& img) +static void ApplyExifOrientation(ExifEntry_t orientationTag, Mat& img) { int orientation = IMAGE_ORIENTATION_TL; - if (filename.size() > 0) + if (orientationTag.tag != INVALID_TAG) { - std::ifstream stream( filename.c_str(), std::ios_base::in | std::ios_base::binary ); - ExifReader reader( stream ); - if( reader.parse() ) - { - ExifEntry_t entry = reader.getTag( ORIENTATION ); - if (entry.tag != INVALID_TAG) - { - orientation = entry.field_u16; //orientation is unsigned short, so check field_u16 - } - } - stream.close(); - } - - ExifTransform(orientation, img); -} - -static void ApplyExifOrientation(const Mat& buf, Mat& img) -{ - int orientation = IMAGE_ORIENTATION_TL; - - if( buf.isContinuous() ) - { - ByteStreamBuffer bsb( reinterpret_cast(buf.data), buf.total() * buf.elemSize() ); - std::istream stream( &bsb ); - ExifReader reader( stream ); - if( reader.parse() ) - { - ExifEntry_t entry = reader.getTag( ORIENTATION ); - if (entry.tag != INVALID_TAG) - { - orientation = entry.field_u16; //orientation is unsigned short, so check field_u16 - } - } + orientation = orientationTag.field_u16; //orientation is unsigned short, so check field_u16 + ExifTransform(orientation, img); } - - ExifTransform(orientation, img); } /** @@ -537,6 +504,12 @@ imread_( const String& filename, int flags, int hdrtype, Mat* mat=0 ) resize( *mat, *mat, Size( size.width / scale_denom, size.height / scale_denom ), 0, 0, INTER_LINEAR_EXACT); } + /// optionally rotate the data if EXIF orientation flag says so + if( mat && !mat->empty() && (flags & IMREAD_IGNORE_ORIENTATION) == 0 && flags != IMREAD_UNCHANGED ) + { + ApplyExifOrientation(decoder->getExifTag(ORIENTATION), *mat); + } + return hdrtype == LOAD_CVMAT ? (void*)matrix : hdrtype == LOAD_IMAGE ? (void*)image : (void*)mat; } @@ -634,7 +607,7 @@ imreadmulti_(const String& filename, int flags, std::vector& mats) // optionally rotate the data if EXIF' orientation flag says so if( (flags & IMREAD_IGNORE_ORIENTATION) == 0 && flags != IMREAD_UNCHANGED ) { - ApplyExifOrientation(filename, mat); + ApplyExifOrientation(decoder->getExifTag(ORIENTATION), mat); } mats.push_back(mat); @@ -665,12 +638,6 @@ Mat imread( const String& filename, int flags ) /// load the data imread_( filename, flags, LOAD_MAT, &img ); - /// optionally rotate the data if EXIF' orientation flag says so - if( !img.empty() && (flags & IMREAD_IGNORE_ORIENTATION) == 0 && flags != IMREAD_UNCHANGED ) - { - ApplyExifOrientation(filename, img); - } - /// return a reference to the data return img; } @@ -932,6 +899,12 @@ imdecode_( const Mat& buf, int flags, int hdrtype, Mat* mat=0 ) resize( *mat, *mat, Size( size.width / scale_denom, size.height / scale_denom ), 0, 0, INTER_LINEAR_EXACT); } + /// 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); + } + decoder.release(); return hdrtype == LOAD_CVMAT ? (void*)matrix : @@ -946,12 +919,6 @@ Mat imdecode( InputArray _buf, int flags ) Mat buf = _buf.getMat(), img; imdecode_( buf, flags, LOAD_MAT, &img ); - /// optionally rotate the data if EXIF' orientation flag says so - if( !img.empty() && (flags & IMREAD_IGNORE_ORIENTATION) == 0 && flags != IMREAD_UNCHANGED ) - { - ApplyExifOrientation(buf, img); - } - return img; } @@ -963,12 +930,6 @@ Mat imdecode( InputArray _buf, int flags, Mat* dst ) dst = dst ? dst : &img; imdecode_( buf, flags, LOAD_MAT, dst ); - /// optionally rotate the data if EXIF' orientation flag says so - if( !dst->empty() && (flags & IMREAD_IGNORE_ORIENTATION) == 0 && flags != IMREAD_UNCHANGED ) - { - ApplyExifOrientation(buf, *dst); - } - return *dst; } diff --git a/modules/imgcodecs/test/test_png.cpp b/modules/imgcodecs/test/test_png.cpp index 051cbf69e6..74920ee9ae 100644 --- a/modules/imgcodecs/test/test_png.cpp +++ b/modules/imgcodecs/test/test_png.cpp @@ -7,6 +7,12 @@ namespace opencv_test { namespace { #ifdef HAVE_PNG +#ifdef HAVE_LIBPNG_PNG_H +#include +#else +#include +#endif + TEST(Imgcodecs_Png, write_big) { const string root = cvtest::TS::ptr()->get_data_path(); @@ -93,6 +99,97 @@ TEST(Imgcodecs_Png, read_color_palette_with_alpha) EXPECT_EQ(img.at(0, 1), Vec3b(0, 0, 255)); } +#ifdef PNG_eXIf_SUPPORTED +/** + * Test for check whether reading exif orientation tag was processed successfully or not + * The test info is the set of 8 images named testExifRotate_{1 to 8}.png + * The test image is the square 10x10 points divided by four sub-squares: + * (R corresponds to Red, G to Green, B to Blue, W to white) + * --------- --------- + * | R | G | | G | R | + * |-------| - (tag 1) |-------| - (tag 2) + * | B | W | | W | B | + * --------- --------- + * + * --------- --------- + * | W | B | | B | W | + * |-------| - (tag 3) |-------| - (tag 4) + * | G | R | | R | G | + * --------- --------- + * + * --------- --------- + * | R | B | | G | W | + * |-------| - (tag 5) |-------| - (tag 6) + * | G | W | | R | B | + * --------- --------- + * + * --------- --------- + * | W | G | | B | R | + * |-------| - (tag 7) |-------| - (tag 8) + * | B | R | | W | G | + * --------- --------- + * + * + * Every image contains exif field with orientation tag (0x112) + * After reading each image and applying the orientation tag, + * the resulting image should be: + * --------- + * | R | G | + * |-------| + * | B | W | + * --------- + * + */ + +typedef testing::TestWithParam Imgcodecs_PNG_Exif; + +// Solution to issue 16579: PNG read doesn't support Exif orientation data +TEST_P(Imgcodecs_PNG_Exif, exif_orientation) +{ + const string root = cvtest::TS::ptr()->get_data_path(); + const string filename = root + GetParam(); + const int colorThresholdHigh = 250; + const int colorThresholdLow = 5; + + Mat m_img = imread(filename); + ASSERT_FALSE(m_img.empty()); + Vec3b vec; + + //Checking the first quadrant (with supposed red) + vec = m_img.at(2, 2); //some point inside the square + EXPECT_LE(vec.val[0], colorThresholdLow); + EXPECT_LE(vec.val[1], colorThresholdLow); + EXPECT_GE(vec.val[2], colorThresholdHigh); + + //Checking the second quadrant (with supposed green) + vec = m_img.at(2, 7); //some point inside the square + EXPECT_LE(vec.val[0], colorThresholdLow); + EXPECT_GE(vec.val[1], colorThresholdHigh); + EXPECT_LE(vec.val[2], colorThresholdLow); + + //Checking the third quadrant (with supposed blue) + vec = m_img.at(7, 2); //some point inside the square + EXPECT_GE(vec.val[0], colorThresholdHigh); + EXPECT_LE(vec.val[1], colorThresholdLow); + EXPECT_LE(vec.val[2], colorThresholdLow); +} + +const string exif_files[] = +{ + "readwrite/testExifOrientation_1.png", + "readwrite/testExifOrientation_2.png", + "readwrite/testExifOrientation_3.png", + "readwrite/testExifOrientation_4.png", + "readwrite/testExifOrientation_5.png", + "readwrite/testExifOrientation_6.png", + "readwrite/testExifOrientation_7.png", + "readwrite/testExifOrientation_8.png" +}; + +INSTANTIATE_TEST_CASE_P(ExifFiles, Imgcodecs_PNG_Exif, + testing::ValuesIn(exif_files)); +#endif // PNG_eXIf_SUPPORTED + #endif // HAVE_PNG }} // namespace