summaryrefslogtreecommitdiff
path: root/image/decoders/EXIF.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'image/decoders/EXIF.cpp')
-rw-r--r--image/decoders/EXIF.cpp331
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