diff --git a/modules/imgcodecs/CMakeLists.txt b/modules/imgcodecs/CMakeLists.txt index 6d565217a0..7b3fc0bd76 100644 --- a/modules/imgcodecs/CMakeLists.txt +++ b/modules/imgcodecs/CMakeLists.txt @@ -61,6 +61,8 @@ list(APPEND grfmt_hdrs ${CMAKE_CURRENT_LIST_DIR}/src/bitstrm.hpp) list(APPEND grfmt_srcs ${CMAKE_CURRENT_LIST_DIR}/src/bitstrm.cpp) list(APPEND grfmt_hdrs ${CMAKE_CURRENT_LIST_DIR}/src/rgbe.hpp) list(APPEND grfmt_srcs ${CMAKE_CURRENT_LIST_DIR}/src/rgbe.cpp) +list(APPEND grfmt_hdrs ${CMAKE_CURRENT_LIST_DIR}/src/jpeg_exif.hpp) +list(APPEND grfmt_srcs ${CMAKE_CURRENT_LIST_DIR}/src/jpeg_exif.cpp) source_group("Src\\grfmts" FILES ${grfmt_hdrs} ${grfmt_srcs}) diff --git a/modules/imgcodecs/src/grfmt_jpeg.cpp b/modules/imgcodecs/src/grfmt_jpeg.cpp index 6502db9418..6862a06ebb 100644 --- a/modules/imgcodecs/src/grfmt_jpeg.cpp +++ b/modules/imgcodecs/src/grfmt_jpeg.cpp @@ -41,6 +41,7 @@ #include "precomp.hpp" #include "grfmt_jpeg.hpp" +#include "jpeg_exif.hpp" #ifdef HAVE_JPEG @@ -177,6 +178,7 @@ JpegDecoder::JpegDecoder() m_state = 0; m_f = 0; m_buf_supported = true; + m_orientation = JPEG_ORIENTATION_TL; } @@ -253,12 +255,64 @@ bool JpegDecoder::readHeader() } } + m_orientation = getOrientation(); + if( !result ) close(); return result; } +int JpegDecoder::getOrientation() +{ + int orientation = JPEG_ORIENTATION_TL; + + ExifReader reader( m_filename ); + if( reader.parse() ) + { + orientation = reader.getTag( ORIENTATION ).field_u16;//orientation is unsigned short, so check field_u16 + } + + return orientation; +} + +void JpegDecoder::setOrientation(Mat& img) +{ + switch( m_orientation ) + { + case JPEG_ORIENTATION_TL: //0th row == visual top, 0th column == visual left-hand side + //do nothing, the image already has proper orientation + break; + case JPEG_ORIENTATION_TR: //0th row == visual top, 0th column == visual right-hand side + flip(img, img, 1); //flip horizontally + break; + case JPEG_ORIENTATION_BR: //0th row == visual bottom, 0th column == visual right-hand side + flip(img, img, -1);//flip both horizontally and vertically + break; + case JPEG_ORIENTATION_BL: //0th row == visual bottom, 0th column == visual left-hand side + flip(img, img, 0); //flip vertically + break; + case JPEG_ORIENTATION_LT: //0th row == visual left-hand side, 0th column == visual top + transpose(img, img); + break; + case JPEG_ORIENTATION_RT: //0th row == visual right-hand side, 0th column == visual top + transpose(img, img); + flip(img, img, 1); //flip horizontally + break; + case JPEG_ORIENTATION_RB: //0th row == visual right-hand side, 0th column == visual bottom + transpose(img, img); + flip(img, img, -1); //flip both horizontally and vertically + break; + case JPEG_ORIENTATION_LB: //0th row == visual left-hand side, 0th column == visual bottom + transpose(img, img); + flip(img, img, 0); //flip vertically + break; + default: + //by default the image read has normal (JPEG_ORIENTATION_TL) orientation + break; + } +} + /*************************************************************************** * following code is for supporting MJPEG image files * based on a message of Laurent Pinchart on the video4linux mailing list @@ -472,8 +526,10 @@ bool JpegDecoder::readData( Mat& img ) icvCvt_CMYK2Gray_8u_C4C1R( buffer[0], 0, data, 0, cvSize(m_width,1) ); } } + result = true; jpeg_finish_decompress( cinfo ); + setOrientation( img ); } } diff --git a/modules/imgcodecs/src/grfmt_jpeg.hpp b/modules/imgcodecs/src/grfmt_jpeg.hpp index 8455b197b0..d0a0991c59 100644 --- a/modules/imgcodecs/src/grfmt_jpeg.hpp +++ b/modules/imgcodecs/src/grfmt_jpeg.hpp @@ -70,6 +70,12 @@ protected: FILE* m_f; void* m_state; + +private: + //Support for handling exif orientation tag in Jpeg file + int m_orientation; + int getOrientation(); + void setOrientation(Mat& img); }; diff --git a/modules/imgcodecs/src/jpeg_exif.cpp b/modules/imgcodecs/src/jpeg_exif.cpp new file mode 100644 index 0000000000..caade4803c --- /dev/null +++ b/modules/imgcodecs/src/jpeg_exif.cpp @@ -0,0 +1,582 @@ +/*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*/ + +#include "jpeg_exif.hpp" + +namespace cv +{ + +/** + * @brief ExifReader constructor + */ +ExifReader::ExifReader(std::string filename) : m_filename(filename) +{ +} + +/** + * @brief ExifReader destructor + */ +ExifReader::~ExifReader() +{ +} + +/** + * @brief Parsing the jpeg 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() +{ + m_exif = getExif(); + if( !m_exif.empty() ) + { + return true; + } + return false; +} + + +/** + * @brief Get tag value 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 ExifReader::getTag(const ExifTagName tag) +{ + ExifEntry_t entry; + std::map::iterator it = m_exif.find(tag); + + if( it != m_exif.end() ) + { + entry = it->second; + } + return entry; +} + + +/** + * @brief Get exif directory structure contained in jpeg file (if any) + * This is internal function and is not exposed to client + * + * @return Map where key is tag number and value is ExifEntry_t structure + */ +std::map ExifReader::getExif() +{ + const size_t markerSize = 2; + const size_t 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() ); + + size_t count; + + FILE* f = fopen( m_filename.c_str(), "rb" ); + + if( !f ) + { + return m_exif; //Until this moment the map is empty + } + + bool exifFound = false; + while( ( !feof( f ) ) && !exifFound ) + { + count = fread( appMarker, sizeof(unsigned char), markerSize, f ); + 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( f ); + fseek( f, static_cast( bytesToSkip - markerSize ), SEEK_CUR ); + 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(f); + m_data.resize( exifSize - offsetToTiffHeader ); + fseek(f, static_cast( offsetToTiffHeader ), SEEK_CUR); + count = fread( &m_data[0], sizeof( unsigned char ), exifSize - offsetToTiffHeader, f ); + exifFound = true; + break; + + default: //No other markers are expected according to standard. May be a signal of error + break; + } + } + + fclose(f); + + 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 + * + * @return size of exif field in the file + */ +size_t ExifReader::getFieldSize (FILE* f) const +{ + unsigned char fieldSize[2]; + size_t count = fread ( fieldSize, sizeof( char ), 2, f ); + if (count < 2) + { + return 0; + } + return ( fieldSize[0] << 8 ) + fieldSize[1]; +} + +/** + * @brief Filling m_exif member with exif directory elements + * This is internal function and is not exposed to client + * + * @return The function doesn't return any value. In case of unsiccessful parsing + * the m_exif member is not filled up + */ +void ExifReader::parseExif() +{ + m_format = getFormat(); + + if( !checkTagMark() ) + { + return; + } + + uint32_t offset = getStartOffset(); + + size_t numEntry = getNumDirEntry(); + + 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 ) ); + offset += tiffFieldSize; + } +} + +/** + * @brief Get endianness of exif information + * This is internal function and is not exposed to client + * + * @return INTEL, MOTO or NONE + */ +Endianess_t ExifReader::getFormat() const +{ + if( m_data[0] != m_data[1] ) + { + return NONE; + } + + if( m_data[0] == 'I' ) + { + return INTEL; + } + + if( m_data[0] == 'M' ) + { + return MOTO; + } + + return NONE; +} + +/** + * @brief Checking whether Tag Mark (0x002A) correspond to one contained in the Jpeg file + * This is internal function and is not exposed to client + * + * @return true if tag mark equals 0x002A, false otherwise + */ +bool ExifReader::checkTagMark() const +{ + uint16_t tagMark = getU16( 2 ); + + if( tagMark != tagMarkRequired ) + { + return false; + } + return true; +} + +/** + * @brief The utility function for extracting actual offset exif IFD0 info is started from + * This is internal function and is not exposed to client + * + * @return offset of IFD0 field + */ +uint32_t ExifReader::getStartOffset() const +{ + return getU32( 4 ); +} + +/** + * @brief Get the number of Directory Entries in Jpeg file + * + * @return The number of directory entries + */ +size_t ExifReader::getNumDirEntry() const +{ + return getU16( offsetNumDir ); +} + +/** + * @brief Parsing particular entry in exif directory + * This is internal function and is not exposed to client + * + * Entries are divided into 12-bytes blocks each + * Each block corresponds the following structure: + * + * +------+-------------+-------------------+------------------------+ + * | Type | Data format | Num of components | Data or offset to data | + * +======+=============+===================+========================+ + * | TTTT | ffff | NNNNNNNN | DDDDDDDD | + * +------+-------------+-------------------+------------------------+ + * + * 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 + * + */ +ExifEntry_t ExifReader::parseExifEntry(const size_t offset) +{ + ExifEntry_t entry; + uint16_t tagNum = getExifTag( offset ); + entry.tag = tagNum; + + switch( tagNum ) + { + 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; + } + 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 ); +} + +/** + * @brief Get string 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 string value + */ +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 + if( size > maxDataSize ) + { + dataOffset = getU32( offset + 8 ); + } + std::vector::const_iterator it = m_data.begin() + dataOffset; + std::string result( it, it + size ); //copy vector content into result + + return result; +} + +/** + * @brief Get unsigned short data 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 Unsigned short data + */ +uint16_t ExifReader::getU16(const size_t offset) const +{ + if( m_format == INTEL ) + { + return m_data[offset] + ( m_data[offset + 1] << 8 ); + } + return ( m_data[offset] << 8 ) + m_data[offset + 1]; +} + +/** + * @brief Get unsigned 32-bit data 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 Unsigned 32-bit data + */ +uint32_t ExifReader::getU32(const size_t offset) const +{ + if( m_format == INTEL ) + { + return m_data[offset] + + ( m_data[offset + 1] << 8 ) + + ( m_data[offset + 2] << 16 ) + + ( m_data[offset + 3] << 24 ); + } + + return ( m_data[offset] << 24 ) + + ( m_data[offset + 1] << 16 ) + + ( m_data[offset + 2] << 8 ) + + m_data[offset + 3]; +} + +/** + * @brief Get unsigned rational data 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 Unsigned rational data + * + * "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 +{ + u_rational_t result; + 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 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++ ) + { + result.push_back( getURational( rationalOffset ) ); + rationalOffset += 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 result; + uint32_t rationalOffset = getU32( offset + 8 ); + for( size_t i = 0; i < ycbcrCoeffs; i++ ) + { + result.push_back( getURational( rationalOffset ) ); + rationalOffset += 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 +{ + return getU16( offset + 8 ); +} + +/** + * @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 +{ + const size_t rationalFieldSize = 8; + std::vector result; + uint32_t rationalOffset = getU32( offset + rationalFieldSize ); + for( size_t i = 0; i < refBWComponents; i++ ) + { + result.push_back( getURational( rationalOffset ) ); + rationalOffset += rationalFieldSize; + } + return result; +} + +} //namespace cv diff --git a/modules/imgcodecs/src/jpeg_exif.hpp b/modules/imgcodecs/src/jpeg_exif.hpp new file mode 100644 index 0000000000..9ca1381679 --- /dev/null +++ b/modules/imgcodecs/src/jpeg_exif.hpp @@ -0,0 +1,251 @@ +/*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*/ + + +#ifndef _OPENCV_JPEG_EXIF_HPP_ +#define _OPENCV_JPEG_EXIF_HPP_ + +#include +#include +#include +#include +#include +#include +#include + +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) + */ +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 Endianess_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 +{ + 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 JPEG's 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 JpegOrientation +{ + JPEG_ORIENTATION_TL = 1, ///< 0th row == visual top, 0th column == visual left-hand side + JPEG_ORIENTATION_TR = 2, ///< 0th row == visual top, 0th column == visual right-hand side + JPEG_ORIENTATION_BR = 3, ///< 0th row == visual bottom, 0th column == visual right-hand side + JPEG_ORIENTATION_BL = 4, ///< 0th row == visual bottom, 0th column == visual left-hand side + JPEG_ORIENTATION_LT = 5, ///< 0th row == visual left-hand side, 0th column == visual top + JPEG_ORIENTATION_RT = 6, ///< 0th row == visual right-hand side, 0th column == visual top + JPEG_ORIENTATION_RB = 7, ///< 0th row == visual right-hand side, 0th column == visual bottom + JPEG_ORIENTATION_LB = 8 ///< 0th row == visual left-hand side, 0th column == visual bottom +}; + +/** + * @brief Reading exif information from Jpeg file + * + * Usage example for getting the orientation of the image: + * + * @code + * ExifReader reader(fileName); + * if( reader.parse() ) + * { + * int orientation = reader.getTag(Orientation).field_u16; + * } + * @endcode + * + */ +class ExifReader +{ +public: + /** + * @brief ExifReader constructor. Constructs an object of exif reader + * + * @param [in]filename The name of file to look exif info in + */ + explicit ExifReader( std::string filename ); + ~ExifReader(); + + + /** + * @brief Parse the file with exif info + * + * @return true if parsing was successful and exif information exists in JpegReader object + */ + bool parse(); + + /** + * @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 ); + +private: + std::string m_filename; + std::vector m_data; + std::map m_exif; + Endianess_t m_format; + + void parseExif(); + bool checkTagMark() const; + + size_t getFieldSize ( FILE* f ) const; + size_t getNumDirEntry() 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; + + Endianess_t getFormat() const; + + ExifEntry_t parseExifEntry( const size_t offset ); + + 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; + std::vector getPrimaryChromaticies( const size_t offset ) const; + std::vector getYCbCrCoeffs( const size_t offset ) const; + std::vector getRefBW( const size_t offset ) const; + +private: + static const uint16_t tagMarkRequired = 0x2A; + + //offset to the _number-of-directory-entry_ field + static const size_t offsetNumDir = 8; + + //max size of data in tag. + //'DDDDDDDD' contains the value of that Tag. If its size is over 4bytes, + //'DDDDDDDD' contains the offset to data stored address. + static const size_t maxDataSize = 4; + + //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 /* JPEG_EXIF_HPP_ */ diff --git a/modules/imgcodecs/test/test_grfmt.cpp b/modules/imgcodecs/test/test_grfmt.cpp index ae0b428ecd..f305ca137a 100644 --- a/modules/imgcodecs/test/test_grfmt.cpp +++ b/modules/imgcodecs/test/test_grfmt.cpp @@ -43,6 +43,7 @@ #include "test_precomp.hpp" #include +#include using namespace cv; using namespace std; @@ -118,6 +119,150 @@ TEST(Imgcodecs_imread, regression) } } +template +string to_string(T i) +{ + stringstream ss; + string s; + ss << i; + s = ss.str(); + + return s; +} + + +/** + * 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}.jpg + * 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 the corresponding matrix must be read as + * --------- + * | R | G | + * |-------| + * | B | W | + * --------- + * + */ +class CV_GrfmtJpegExifOrientationTest : public cvtest::BaseTest +{ +public: + void run(int) + { + try + { + for( int i = 1; i <= 8; ++i) + { + string fileName = "readwrite/testExifOrientation_" + to_string(i) + ".jpg"; + m_img = imread(string(ts->get_data_path()) + fileName); + if( !m_img.data ) + { + ts->set_failed_test_info(cvtest::TS::FAIL_MISSING_TEST_DATA); + } + ts->printf(cvtest::TS::LOG, "start reading image\t%s\n", fileName.c_str()); + if( !checkOrientation() ) + { + ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH); + } + } + + } + catch(...) + { + ts->set_failed_test_info(cvtest::TS::FAIL_EXCEPTION); + } + } +private: + bool checkOrientation(); + Mat m_img; +}; + + +bool CV_GrfmtJpegExifOrientationTest::checkOrientation() +{ + Vec3b vec; + int red = 0; + int green = 0; + int blue = 0; + + const int colorThresholdHigh = 250; + const int colorThresholdLow = 5; + + //Checking the first quadrant (with supposed red) + vec = m_img.at(2, 2); //some point inside the square + red = vec.val[2]; + green = vec.val[1]; + blue = vec.val[0]; + + ts->printf(cvtest::TS::LOG, "RED QUADRANT:\n"); + ts->printf(cvtest::TS::LOG, "Red calculated:\t\t%d\n", red); + ts->printf(cvtest::TS::LOG, "Green calculated:\t%d\n", green); + ts->printf(cvtest::TS::LOG, "Blue calculated:\t%d\n", blue); + if( red < colorThresholdHigh ) return false; + if( blue > colorThresholdLow ) return false; + if( green > colorThresholdLow ) return false; + + //Checking the second quadrant (with supposed green) + vec = m_img.at(2, 7); //some point inside the square + red = vec.val[2]; + green = vec.val[1]; + blue = vec.val[0]; + ts->printf(cvtest::TS::LOG, "GREEN QUADRANT:\n"); + ts->printf(cvtest::TS::LOG, "Red calculated:\t\t%d\n", red); + ts->printf(cvtest::TS::LOG, "Green calculated:\t%d\n", green); + ts->printf(cvtest::TS::LOG, "Blue calculated:\t%d\n", blue); + if( green < colorThresholdHigh ) return false; + if( red > colorThresholdLow ) return false; + if( blue > colorThresholdLow ) return false; + + //Checking the third quadrant (with supposed blue) + vec = m_img.at(7, 2); //some point inside the square + red = vec.val[2]; + green = vec.val[1]; + blue = vec.val[0]; + ts->printf(cvtest::TS::LOG, "BLUE QUADRANT:\n"); + ts->printf(cvtest::TS::LOG, "Red calculated:\t\t%d\n", red); + ts->printf(cvtest::TS::LOG, "Green calculated:\t%d\n", green); + ts->printf(cvtest::TS::LOG, "Blue calculated:\t%d\n", blue); + if( blue < colorThresholdHigh ) return false; + if( red > colorThresholdLow ) return false; + if( green > colorThresholdLow ) return false; + + return true; +} + +TEST(Imgcodecs_jpeg_exif, setOrientation) +{ + CV_GrfmtJpegExifOrientationTest test; + test.safe_run(); +} + #ifdef HAVE_JASPER TEST(Imgcodecs_jasper, regression) {