diff options
Diffstat (limited to 'image/decoders/EXIF.cpp')
-rw-r--r-- | image/decoders/EXIF.cpp | 331 |
1 files changed, 331 insertions, 0 deletions
diff --git a/image/decoders/EXIF.cpp b/image/decoders/EXIF.cpp new file mode 100644 index 0000000000..8197c886c3 --- /dev/null +++ b/image/decoders/EXIF.cpp @@ -0,0 +1,331 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "EXIF.h" + +#include "mozilla/EndianUtils.h" + +namespace mozilla { +namespace image { + +// Section references in this file refer to the EXIF v2.3 standard, also known +// as CIPA DC-008-Translation-2010. + +// See Section 4.6.4, Table 4. +// Typesafe enums are intentionally not used here since we're comparing to raw +// integers produced by parsing. +enum EXIFTag +{ + OrientationTag = 0x112, +}; + +// See Section 4.6.2. +enum EXIFType +{ + ByteType = 1, + ASCIIType = 2, + ShortType = 3, + LongType = 4, + RationalType = 5, + UndefinedType = 7, + SignedLongType = 9, + SignedRational = 10, +}; + +static const char* EXIFHeader = "Exif\0\0"; +static const uint32_t EXIFHeaderLength = 6; + +///////////////////////////////////////////////////////////// +// Parse EXIF data, typically found in a JPEG's APP1 segment. +///////////////////////////////////////////////////////////// +EXIFData +EXIFParser::ParseEXIF(const uint8_t* aData, const uint32_t aLength) +{ + if (!Initialize(aData, aLength)) { + return EXIFData(); + } + + if (!ParseEXIFHeader()) { + return EXIFData(); + } + + uint32_t offsetIFD; + if (!ParseTIFFHeader(offsetIFD)) { + return EXIFData(); + } + + JumpTo(offsetIFD); + + Orientation orientation; + if (!ParseIFD0(orientation)) { + return EXIFData(); + } + + // We only care about orientation at this point, so we don't bother with the + // other IFDs. If we got this far we're done. + return EXIFData(orientation); +} + +///////////////////////////////////////////////////////// +// Parse the EXIF header. (Section 4.7.2, Figure 30) +///////////////////////////////////////////////////////// +bool +EXIFParser::ParseEXIFHeader() +{ + return MatchString(EXIFHeader, EXIFHeaderLength); +} + +///////////////////////////////////////////////////////// +// Parse the TIFF header. (Section 4.5.2, Table 1) +///////////////////////////////////////////////////////// +bool +EXIFParser::ParseTIFFHeader(uint32_t& aIFD0OffsetOut) +{ + // Determine byte order. + if (MatchString("MM\0*", 4)) { + mByteOrder = ByteOrder::BigEndian; + } else if (MatchString("II*\0", 4)) { + mByteOrder = ByteOrder::LittleEndian; + } else { + return false; + } + + // Determine offset of the 0th IFD. (It shouldn't be greater than 64k, which + // is the maximum size of the entry APP1 segment.) + uint32_t ifd0Offset; + if (!ReadUInt32(ifd0Offset) || ifd0Offset > 64 * 1024) { + return false; + } + + // The IFD offset is relative to the beginning of the TIFF header, which + // begins after the EXIF header, so we need to increase the offset + // appropriately. + aIFD0OffsetOut = ifd0Offset + EXIFHeaderLength; + return true; +} + +///////////////////////////////////////////////////////// +// Parse the entries in IFD0. (Section 4.6.2) +///////////////////////////////////////////////////////// +bool +EXIFParser::ParseIFD0(Orientation& aOrientationOut) +{ + uint16_t entryCount; + if (!ReadUInt16(entryCount)) { + return false; + } + + for (uint16_t entry = 0 ; entry < entryCount ; ++entry) { + // Read the fields of the entry. + uint16_t tag; + if (!ReadUInt16(tag)) { + return false; + } + + // Right now, we only care about orientation, so we immediately skip to the + // next entry if we find anything else. + if (tag != OrientationTag) { + Advance(10); + continue; + } + + uint16_t type; + if (!ReadUInt16(type)) { + return false; + } + + uint32_t count; + if (!ReadUInt32(count)) { + return false; + } + + // We should have an orientation value here; go ahead and parse it. + if (!ParseOrientation(type, count, aOrientationOut)) { + return false; + } + + // Since the orientation is all we care about, we're done. + return true; + } + + // We didn't find an orientation field in the IFD. That's OK; we assume the + // default orientation in that case. + aOrientationOut = Orientation(); + return true; +} + +bool +EXIFParser::ParseOrientation(uint16_t aType, uint32_t aCount, Orientation& aOut) +{ + // Sanity check the type and count. + if (aType != ShortType || aCount != 1) { + return false; + } + + uint16_t value; + if (!ReadUInt16(value)) { + return false; + } + + switch (value) { + case 1: aOut = Orientation(Angle::D0, Flip::Unflipped); break; + case 2: aOut = Orientation(Angle::D0, Flip::Horizontal); break; + case 3: aOut = Orientation(Angle::D180, Flip::Unflipped); break; + case 4: aOut = Orientation(Angle::D180, Flip::Horizontal); break; + case 5: aOut = Orientation(Angle::D90, Flip::Horizontal); break; + case 6: aOut = Orientation(Angle::D90, Flip::Unflipped); break; + case 7: aOut = Orientation(Angle::D270, Flip::Horizontal); break; + case 8: aOut = Orientation(Angle::D270, Flip::Unflipped); break; + default: return false; + } + + // This is a 32-bit field, but the orientation value only occupies the first + // 16 bits. We need to advance another 16 bits to consume the entire field. + Advance(2); + return true; +} + +bool +EXIFParser::Initialize(const uint8_t* aData, const uint32_t aLength) +{ + if (aData == nullptr) { + return false; + } + + // An APP1 segment larger than 64k violates the JPEG standard. + if (aLength > 64 * 1024) { + return false; + } + + mStart = mCurrent = aData; + mLength = mRemainingLength = aLength; + mByteOrder = ByteOrder::Unknown; + return true; +} + +void +EXIFParser::Advance(const uint32_t aDistance) +{ + if (mRemainingLength >= aDistance) { + mCurrent += aDistance; + mRemainingLength -= aDistance; + } else { + mCurrent = mStart; + mRemainingLength = 0; + } +} + +void +EXIFParser::JumpTo(const uint32_t aOffset) +{ + if (mLength >= aOffset) { + mCurrent = mStart + aOffset; + mRemainingLength = mLength - aOffset; + } else { + mCurrent = mStart; + mRemainingLength = 0; + } +} + +bool +EXIFParser::MatchString(const char* aString, const uint32_t aLength) +{ + if (mRemainingLength < aLength) { + return false; + } + + for (uint32_t i = 0 ; i < aLength ; ++i) { + if (mCurrent[i] != aString[i]) { + return false; + } + } + + Advance(aLength); + return true; +} + +bool +EXIFParser::MatchUInt16(const uint16_t aValue) +{ + if (mRemainingLength < 2) { + return false; + } + + bool matched; + switch (mByteOrder) { + case ByteOrder::LittleEndian: + matched = LittleEndian::readUint16(mCurrent) == aValue; + break; + case ByteOrder::BigEndian: + matched = BigEndian::readUint16(mCurrent) == aValue; + break; + default: + NS_NOTREACHED("Should know the byte order by now"); + matched = false; + } + + if (matched) { + Advance(2); + } + + return matched; +} + +bool +EXIFParser::ReadUInt16(uint16_t& aValue) +{ + if (mRemainingLength < 2) { + return false; + } + + bool matched = true; + switch (mByteOrder) { + case ByteOrder::LittleEndian: + aValue = LittleEndian::readUint16(mCurrent); + break; + case ByteOrder::BigEndian: + aValue = BigEndian::readUint16(mCurrent); + break; + default: + NS_NOTREACHED("Should know the byte order by now"); + matched = false; + } + + if (matched) { + Advance(2); + } + + return matched; +} + +bool +EXIFParser::ReadUInt32(uint32_t& aValue) +{ + if (mRemainingLength < 4) { + return false; + } + + bool matched = true; + switch (mByteOrder) { + case ByteOrder::LittleEndian: + aValue = LittleEndian::readUint32(mCurrent); + break; + case ByteOrder::BigEndian: + aValue = BigEndian::readUint32(mCurrent); + break; + default: + NS_NOTREACHED("Should know the byte order by now"); + matched = false; + } + + if (matched) { + Advance(4); + } + + return matched; +} + +} // namespace image +} // namespace mozilla |