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