/**
* @brief ExifReader constructor
*/
-ExifReader::ExifReader(std::istream& stream) : m_stream(stream), m_format(NONE)
+ExifReader::ExifReader() : m_format(NONE)
{
}
{
}
-/**
- * @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
* @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<int, ExifEntry_t>::iterator it = m_exif.find(tag);
+ std::map<int, ExifEntry_t>::const_iterator it = m_exif.find(tag);
if( it != m_exif.end() )
{
/**
- * @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<int, ExifEntry_t > 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<char*>(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<long>( 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<long>( offsetToTiffHeader ), m_stream.cur );
- if ( m_stream.fail() ) {
- throw ExifParsingError();
- }
- m_stream.read( reinterpret_cast<char*>(&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<char*>(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];
}
/**
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)
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
* @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<unsigned char> m_data;
std::map<int, ExifEntry_t > m_exif;
Endianess_t m_format;
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;
u_rational_t getURational( const size_t offset ) const;
- std::map<int, ExifEntry_t > getExif();
std::string getString( const size_t offset ) const;
std::vector<u_rational_t> getResolution( const size_t offset ) const;
std::vector<u_rational_t> getWhitePoint( const size_t offset ) const;
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;
#include "utils.hpp"
#include "bitstrm.hpp"
+#include "exif.hpp"
namespace cv
{
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 );
String m_signature;
Mat m_buf;
bool m_buf_supported;
+ ExifReader m_exif;
};
if (state->cinfo.src != 0)
{
+ jpeg_save_markers(&state->cinfo, APP1, 0xffff);
jpeg_read_header( &state->cinfo, TRUE );
state->cinfo.scale_num=1;
}
}
+ // 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,
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
{
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;
}
}
}
}
-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<char*>(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);
}
/**
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;
}
// 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);
/// 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;
}
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 :
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;
}
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;
}
#ifdef HAVE_PNG
+#ifdef HAVE_LIBPNG_PNG_H
+#include <libpng/png.h>
+#else
+#include <png.h>
+#endif
+
TEST(Imgcodecs_Png, write_big)
{
const string root = cvtest::TS::ptr()->get_data_path();
EXPECT_EQ(img.at<Vec3b>(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<string> 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<Vec3b>(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<Vec3b>(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<Vec3b>(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