imagecore/formats/exif/exifreader.h (171 lines of code) (raw):

/* * MIT License * * Copyright (c) 2017 Twitter * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #pragma once #include <inttypes.h> #include "imagecore/image/image.h" #include "imagecore/utils/endianutils.h" #include "imagecore/utils/memorystream.h" #include "imagecore/imagecore.h" #include "imagecore/utils/mathtypes.h" #include "imagecore/utils/mathutils.h" #include "exifcommon.h" namespace imagecore { class ExifReader { public: ExifReader(); ~ExifReader(); void initialize(const uint8_t* Data, uint32_t dataSize); // Most functionality is in this function, given an internal exif tag id it tries to find it in provided exif data. If tag is no found, default value is returned, // if tag is found, but contains invalid data, default value is returned. // TODO: Optimize this to avoid full traverse for multiple queries template<typename ReturnType> ReturnType getValue(ReturnType DefaultValue, ExifCommon::kTagId inId) { if(m_cachedEntries[(uint32_t)inId]) { // value already been found and cached? return *(ReturnType*)m_cachedEntries[(uint32_t)inId]; } if((!m_valid) || (m_directoryInfo == NULL)) { return DefaultValue; } do { const uint8_t* directoryData = m_directoryInfo->m_data; MemoryStreamReader memoryStream(directoryData, (uint16_t)(m_dataSize - (directoryData - m_TIFFHeader)), m_isBe); uint16_t dirSize = memoryStream.get_short_advance(); if(!memoryStream.isLastReadValid()) { return DefaultValue; } for(; m_tagIndex < dirSize; m_tagIndex++ ) { memoryStream.seek(2 + m_tagIndex * kDirectoryEntrySize); // advance to current tag index uint16_t exifTagId = memoryStream.get_short_advance(); // exif id if(!memoryStream.isLastReadValid()) { m_tagIndex++; return DefaultValue; } int32_t tagId = ExifCommon::Instance().getTagId((uint32_t)m_directoryInfo->m_type, exifTagId); // translate exif id to our internal id if(tagId != -1) // is this an id that we potentially care about? { switch((ExifCommon::kExifTagID)exifTagId) { // offset to another subdirectory case ExifCommon::kExifTagID::kExifOffset: case ExifCommon::kExifTagID::kExifGPSInfo: { memoryStream.advance(6); // skip type and count uint32_t offset = memoryStream.get_uint_advance(); if(!memoryStream.isLastReadValid()) { m_tagIndex++; return DefaultValue; } if(validateOffset(offset, sizeof(uint16_t) + kDirectoryEntrySize)) { // should have at least enough space for dir size and one entry. if(m_directoryQueue.push(m_TIFFHeader + offset, ExifCommon::Instance().getDirectoryType((ExifCommon::kExifTagID)exifTagId)) != 0) { m_tagIndex++; return DefaultValue; } } break; } default: { uint16_t tagType = memoryStream.get_short_advance(); if(!memoryStream.isLastReadValid()) { m_tagIndex++; return DefaultValue; } uint32_t tagCount = memoryStream.get_uint_advance(); if(!memoryStream.isLastReadValid()) { m_tagIndex++; return DefaultValue; } const ExifCommon::TagHeaderType* TagHeaderRef = ExifCommon::Instance().getTagHeader((ExifCommon::kTagId)tagId); if(TagHeaderRef->verifyTypeAndCount(tagType, tagCount)) { uint8_t readBuffer[512]; // we currently dont have any data type that exceeds 512 bytes, biggest one is ExifString at 258 bytes uint32_t size; readValue(readBuffer, size, memoryStream, tagType, tagCount); if(!memoryStream.isLastReadValid()) { m_tagIndex++; return DefaultValue; } if( TagHeaderRef->m_rangeValidator(readBuffer, tagCount)) { m_cachedEntries[tagId] = createCacheEntry(size, readBuffer); if((uint32_t)inId == tagId) { // is it the actual value we are looking for? m_tagIndex++; ReturnType res; memcpy(&res, readBuffer, sizeof(ReturnType)); return res; } } } break; } } } } m_directoryInfo = m_directoryQueue.pop(); // returns null if queue is empty m_tagIndex = 0; }while(m_directoryInfo != nullptr); return DefaultValue; } private: struct directoryInfoType { directoryInfoType() : m_data(nullptr), m_type(ExifCommon::kDirectoryType::kDirectoryExif) {} directoryInfoType(const uint8_t* data, ExifCommon::kDirectoryType type) : m_data(data), m_type(type) {} const uint8_t* m_data; ExifCommon::kDirectoryType m_type; // currently exif or gps }; // This should be abstracted with some generic queue implementation. // Left in here for now since this method is the only thing that uses this helper class. const uint32_t kDirectoryEntrySize = 12; static const uint32_t kDirectoryQueueSize = 8; class DirectoryQueueType { public: DirectoryQueueType() : m_readPtr(0), m_writePtr(0) {} int push(const uint8_t* data, ExifCommon::kDirectoryType dirType) { if(m_writePtr == ExifReader::kDirectoryQueueSize) { return -1; } m_directoryInfos[m_writePtr] = ExifReader::directoryInfoType(data, dirType); m_writePtr++; return 0; } const ExifReader::directoryInfoType* pop() { if(isEmpty()) { return nullptr; } return &m_directoryInfos[m_readPtr++]; } private: bool isEmpty() { return m_readPtr == m_writePtr; } ExifReader::directoryInfoType m_directoryInfos[ExifReader::kDirectoryQueueSize]; uint16_t m_readPtr; uint16_t m_writePtr; }; void readValue(uint8_t* buffer, uint32_t& size, MemoryStreamReader& memoryStream, uint16_t tagType, uint32_t tagCount); void readValue(uint16_t& value, MemoryStreamReader& memoryStream, uint32_t) const; void readValue(int8_t& value, MemoryStreamReader& memoryStream, uint32_t) const; void readValue(ExifCommon::ExifString& value, MemoryStreamReader& memoryStream, uint32_t count) const; void readValue(Rational<uint32_t>& value, MemoryStreamReader& memoryStream, uint32_t count) const; void readValue(ExifCommon::ExifU64Rational3& value, MemoryStreamReader& memoryStream, uint32_t count) const; bool validateOffset(uint32_t offset, uint32_t count) const; void* createCacheEntry(uint32_t entrySize, uint8_t* entryData); static const uint32_t kHeaderSize = 8; directoryInfoType m_rootDirectory; // the main Exif directory DirectoryQueueType m_directoryQueue; const directoryInfoType* m_directoryInfo; uint32_t m_tagIndex; void* m_cachedEntries[(int32_t)ExifCommon::kTagId::kTagIdMax]; static const uint32_t kCacheSize = 64 * 1024; uint32_t m_cacheOffset; uint8_t* m_cacheData; const uint8_t* m_TIFFHeader; // beginning of the whole data block uint32_t m_dataSize; bool m_isBe; bool m_valid; }; }