1
0
mirror of https://github.com/opencv/opencv.git synced 2026-07-29 23:33:05 +04:00

Exif parsing for PNG files to support Exif orientation tag. Moved decoder specific Exif parsing to JPEG and PNG decoders, respectively. Issue 16579

This commit is contained in:
Rachel A
2021-02-01 12:16:44 -08:00
parent 2d2d72afbb
commit 650836d700
9 changed files with 219 additions and 193 deletions
+28 -110
View File
@@ -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<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() )
{
@@ -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
*
* @return Map where key is tag number and value is ExifEntry_t structure
*/
std::map<int, ExifEntry_t > ExifReader::getExif()
{
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 )
{
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;
}
}
if( !exifFound )
{
return m_exif;
}
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
* @param [in] data The data buffer to read EXIF data starting with endianness
* @param [in] size The size of the data buffer
*
* @return size of exif field in the file
* @return true if parsing was successful
* false in case of unsuccessful parsing
*/
size_t ExifReader::getFieldSize ()
bool ExifReader::parseExif(unsigned char* data, const size_t size)
{
unsigned char fieldSize[2];
m_stream.read( reinterpret_cast<char*>(fieldSize), 2 );
std::streamsize count = m_stream.gcount();
if (count < 2)
// Populate m_data, then call parseExif() (private)
if( data && size > 0 )
{
return 0;
m_data.assign(data, data + size);
}
else
{
return false;
}
try {
parseExif();
if( !m_exif.empty() )
{
return true;
}
return false;
}
catch( ExifParsingError& ) {
return false;
}
return ( fieldSize[0] << 8 ) + fieldSize[1];
}
/**
+10 -27
View File
@@ -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<unsigned char> m_data;
std::map<int, ExifEntry_t > 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<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;
+5
View File
@@ -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;
+3
View File
@@ -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;
};
+24
View File
@@ -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,
+19
View File
@@ -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
{
+16
View File
@@ -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;
}
}
+17 -56
View File
@@ -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();
orientation = orientationTag.field_u16; //orientation is unsigned short, so check field_u16
ExifTransform(orientation, img);
}
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
}
}
}
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<Mat>& 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;
}