Snap for 8564071 from 4579b759f7ef925978b613b3b4a40cbbb8caf0f1 to mainline-adbd-release

Change-Id: I4fd1a066452d3f359c8ced92bafc4905c91e2fbb
diff --git a/Android.bp b/Android.bp
index 8486648..d376d8f 100644
--- a/Android.bp
+++ b/Android.bp
@@ -81,8 +81,9 @@
         enabled: true,
     },
     srcs: [
-        "src/tiff_parser.cc",
         "src/piex.cc",
+        "src/piex_cr3.cc",
+        "src/tiff_parser.cc",
     ],
     export_include_dirs: ["."],
     static_libs: [
diff --git a/piex.gyp b/piex.gyp
index 737a0c2..15741dc 100755
--- a/piex.gyp
+++ b/piex.gyp
@@ -18,11 +18,13 @@
   'type': 'static_library',
   'sources': [
     'src/piex.cc',
+    'src/piex_cr3.cc',
     'src/tiff_parser.cc',
   ],
   'variables': {
     'headers': [
       'src/piex.h',
+      'src/piex_cr3.h',
       'src/piex_types.h',
       'src/tiff_parser.h',
     ],
diff --git a/src/image_type_recognition/image_type_recognition_lite.cc b/src/image_type_recognition/image_type_recognition_lite.cc
index c56c1c0..5976f42 100644
--- a/src/image_type_recognition/image_type_recognition_lite.cc
+++ b/src/image_type_recognition/image_type_recognition_lite.cc
@@ -183,13 +183,14 @@
     // Search for (kSignatureFileTypeSection + kSignatureVersions[i]) in first
     // requested bytes
     const string kSignatureSection("\x00\xb0\x01\x00\x04\x00\x00\x00", 8);
-    const int kSignatureVersionsSize = 5;
+    const int kSignatureVersionsSize = 6;
     const string kSignatureVersions[kSignatureVersionsSize] = {
         string("\x02\x00", 2),  // ARW 1.0
         string("\x03\x00", 2),  // ARW 2.0
         string("\x03\x01", 2),  // ARW 2.1
         string("\x03\x02", 2),  // ARW 2.2
         string("\x03\x03", 2),  // ARW 2.3
+        string("\x04\x00", 2),  // ARW 4.0
     };
     bool matched = false;
     for (int i = 0; i < kSignatureVersionsSize; ++i) {
@@ -201,6 +202,25 @@
   }
 };
 
+// Canon RAW (CR3 extension).
+class Cr3TypeChecker : public TypeChecker {
+ public:
+  static constexpr size_t kSignatureOffset = 4;
+  static constexpr const char* kSignature = "ftypcrx ";
+
+  virtual RawImageTypes Type() const { return kCr3Image; }
+
+  virtual size_t RequestedSize() const {
+    return kSignatureOffset + strlen(kSignature);
+  }
+
+  // Checks for the ftyp box w/ brand 'crx '.
+  virtual bool IsMyType(const RangeCheckedBytePtr& source) const {
+    RangeCheckedBytePtr limited_source = LimitSource(source);
+    return IsSignatureMatched(limited_source, kSignatureOffset, kSignature);
+  }
+};
+
 // Canon RAW (CR2 extension).
 class Cr2TypeChecker : public TypeChecker {
  public:
@@ -749,6 +769,7 @@
   TypeCheckerList() {
     // Add all supported RAW type checkers here.
     checkers_.push_back(new ArwTypeChecker());
+    checkers_.push_back(new Cr3TypeChecker());
     checkers_.push_back(new Cr2TypeChecker());
     checkers_.push_back(new CrwTypeChecker());
     checkers_.push_back(new DcrTypeChecker());
@@ -841,6 +862,7 @@
 
     // Raw image types
     case kArwImage:
+    case kCr3Image:
     case kCr2Image:
     case kCrwImage:
     case kDcrImage:
diff --git a/src/image_type_recognition/image_type_recognition_lite.h b/src/image_type_recognition/image_type_recognition_lite.h
index a7e212d..30db915 100644
--- a/src/image_type_recognition/image_type_recognition_lite.h
+++ b/src/image_type_recognition/image_type_recognition_lite.h
@@ -40,6 +40,7 @@
   // raw image types
   kArwImage,
   kCr2Image,
+  kCr3Image,
   kCrwImage,
   kDcrImage,
   kDngImage,
diff --git a/src/piex.cc b/src/piex.cc
index 338e581..ac2ef0b 100644
--- a/src/piex.cc
+++ b/src/piex.cc
@@ -23,6 +23,7 @@
 
 #include "src/binary_parse/range_checked_byte_ptr.h"
 #include "src/image_type_recognition/image_type_recognition_lite.h"
+#include "src/piex_cr3.h"
 #include "src/tiff_parser.h"
 
 namespace piex {
@@ -282,9 +283,9 @@
   }
 
   if (raw_processing_ifd.Has(kOlymTagAspectFrame)) {
-    std::vector<std::uint32_t> aspect_frame(4);
+    std::vector<std::uint32_t> aspect_frame;
     if (raw_processing_ifd.Get(kOlymTagAspectFrame, &aspect_frame) &&
-        aspect_frame[2] > aspect_frame[0] &&
+        aspect_frame.size() == 4 && aspect_frame[2] > aspect_frame[0] &&
         aspect_frame[3] > aspect_frame[1]) {
       preview_image_data->full_width = aspect_frame[2] - aspect_frame[0] + 1;
       preview_image_data->full_height = aspect_frame[3] - aspect_frame[1] + 1;
@@ -649,7 +650,8 @@
 }
 
 Error GetPreviewImageData(StreamInterface* data,
-                          PreviewImageData* preview_image_data) {
+                          PreviewImageData* preview_image_data,
+                          RawImageTypes* output_type) {
   const size_t bytes = BytesRequiredForIsRaw();
   if (data == nullptr || bytes == 0) {
     return kFail;
@@ -662,11 +664,15 @@
   }
   RangeCheckedBytePtr header_buffer(file_header.data(), file_header.size());
 
-  switch (RecognizeRawImageTypeLite(header_buffer)) {
+  RawImageTypes type = RecognizeRawImageTypeLite(header_buffer);
+  if (output_type != nullptr) *output_type = type;
+  switch (type) {
     case image_type_recognition::kArwImage:
       return ArwGetPreviewData(data, preview_image_data);
     case image_type_recognition::kCr2Image:
       return Cr2GetPreviewData(data, preview_image_data);
+    case image_type_recognition::kCr3Image:
+      return Cr3GetPreviewData(data, preview_image_data);
     case image_type_recognition::kDngImage:
       return DngGetPreviewData(data, preview_image_data);
     case image_type_recognition::kNefImage:
@@ -703,24 +709,32 @@
   using image_type_recognition::GetNumberOfBytesForIsOfType;
   using image_type_recognition::IsOfType;
 
-  std::vector<std::uint8_t> file_header(
-      GetNumberOfBytesForIsOfType(image_type_recognition::kRafImage));
+  size_t min_header_bytes =
+      std::max(GetNumberOfBytesForIsOfType(image_type_recognition::kRafImage),
+               GetNumberOfBytesForIsOfType(image_type_recognition::kCr3Image));
+
+  std::vector<std::uint8_t> file_header(min_header_bytes);
   if (data->GetData(0, file_header.size(), file_header.data()) != kOk) {
     return false;
   }
 
-  // For RAF files a special routine is necessary to get orientation. For others
-  // the general approach is sufficient.
+  // For RAF and CR# files a special routine is necessary to get orientation.
+  // For others the general approach is sufficient.
   if (IsOfType(RangeCheckedBytePtr(file_header.data(), file_header.size()),
                image_type_recognition::kRafImage)) {
     return RafGetOrientation(data, orientation);
+  } else if (IsOfType(
+                 RangeCheckedBytePtr(file_header.data(), file_header.size()),
+                 image_type_recognition::kCr3Image)) {
+    return Cr3GetOrientation(data, orientation);
   } else {
     return GetExifOrientation(data, 0 /* offset */, orientation);
   }
 }
 
 std::vector<std::string> SupportedExtensions() {
-  return {"ARW", "CR2", "DNG", "NEF", "NRW", "ORF", "PEF", "RAF", "RW2", "SRW"};
+  return {"ARW", "CR2", "CR3", "DNG", "NEF", "NRW",
+          "ORF", "PEF", "RAF", "RW2", "SRW"};
 }
 
 }  // namespace piex
diff --git a/src/piex.h b/src/piex.h
index 3225421..8d74ca0 100644
--- a/src/piex.h
+++ b/src/piex.h
@@ -49,6 +49,7 @@
 #include <string>
 #include <vector>
 
+#include "src/image_type_recognition/image_type_recognition_lite.h"
 #include "src/piex_types.h"
 
 namespace piex {
@@ -70,8 +71,11 @@
 //
 // One could check the "preview_image_data->preview_length != 0" for the
 // existance of a preview image.
-Error GetPreviewImageData(StreamInterface* data,
-                          PreviewImageData* preview_image_data);
+//
+// Updates output_type based on data, if output_type is non-null.
+Error GetPreviewImageData(
+    StreamInterface* data, PreviewImageData* preview_image_data,
+    image_type_recognition::RawImageTypes* output_type = nullptr);
 
 // Returns true if the full width and height and the mosaic pattern dimension of
 // a DNG image could be obtained. False otherwise.
diff --git a/src/piex_cr3.cc b/src/piex_cr3.cc
new file mode 100644
index 0000000..4fa82b7
--- /dev/null
+++ b/src/piex_cr3.cc
@@ -0,0 +1,559 @@
+// Copyright 2020 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#include "src/piex_cr3.h"
+
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <limits>
+#include <unordered_set>
+
+#include "src/binary_parse/range_checked_byte_ptr.h"
+#include "src/piex_types.h"
+#include "src/tiff_directory/tiff_directory.h"
+#include "src/tiff_parser.h"
+
+namespace piex {
+namespace {
+
+constexpr size_t kUuidSize = 16;
+using Uuid = std::array<std::uint8_t, kUuidSize>;
+// Uuid of uuid box under the moov box.
+constexpr Uuid kUuidMoov = {0x85, 0xc0, 0xb6, 0x87, 0x82, 0x0f, 0x11, 0xe0,
+                            0x81, 0x11, 0xf4, 0xce, 0x46, 0x2b, 0x6a, 0x48};
+
+// Uuid of uuid box containing PRVW box.
+constexpr Uuid kUuidPrvw = {0xea, 0xf4, 0x2b, 0x5e, 0x1c, 0x98, 0x4b, 0x88,
+                            0xb9, 0xfb, 0xb7, 0xdc, 0x40, 0x6e, 0x4d, 0x16};
+
+constexpr size_t kTagSize = 4;
+using BoxTag = std::array<char, kTagSize>;
+
+constexpr BoxTag NewTag(const char s[kTagSize + 1]) {
+  return BoxTag{s[0], s[1], s[2], s[3]};
+}
+
+constexpr BoxTag kUuidTag = NewTag("uuid");
+constexpr BoxTag kPrvwTag = NewTag("PRVW");
+constexpr BoxTag kThmbTag = NewTag("THMB");
+constexpr BoxTag kCmt1Tag = NewTag("CMT1");
+constexpr BoxTag kCmt2Tag = NewTag("CMT2");
+constexpr BoxTag kStblTag = NewTag("stbl");
+constexpr BoxTag kStsdTag = NewTag("stsd");
+constexpr BoxTag kCrawTag = NewTag("CRAW");
+constexpr BoxTag kStszTag = NewTag("stsz");
+constexpr BoxTag kCo64Tag = NewTag("co64");
+constexpr BoxTag kMdatTag = NewTag("mdat");
+
+// Convenience class for a box.
+class Box {
+ public:
+  Box()
+      : is_valid_(false),
+        tag_(BoxTag()),
+        offset_(0),
+        header_offset_(0),
+        next_box_offset_(0) {}
+  Box(const BoxTag& tag, size_t offset, size_t header_length, size_t length)
+      : is_valid_(true),
+        tag_(tag),
+        offset_(offset),
+        header_offset_(offset + header_length),
+        next_box_offset_(offset + length) {}
+
+  bool IsValid() const { return is_valid_ && next_box_offset_ > offset_; }
+  const BoxTag& tag() const { return tag_; }
+
+  // Returns offset from start of file.
+  size_t offset() const { return offset_; }
+  // Returns offset from start of file, including box's header.
+  size_t header_offset() const { return header_offset_; }
+  // Returns offset from start of file of the next box, accounting for size of
+  // this box.
+  size_t next_box_offset() const { return next_box_offset_; }
+
+ private:
+  bool is_valid_;
+  BoxTag tag_;
+  size_t offset_;
+  size_t header_offset_;
+  size_t next_box_offset_;
+};
+
+struct ProcessData {
+  PreviewImageData* preview_image_data = nullptr;
+  Image mdat_image;
+  Image prvw_image;
+};
+
+// Wraps Get16u w/ assumption that CR3 is always big endian, based on
+// ISO/IEC 14496-12 specification that all box fields are big endian.
+bool Get16u(StreamInterface* stream, size_t offset, std::uint16_t* value) {
+  return Get16u(stream, offset, tiff_directory::kBigEndian, value);
+}
+
+// Wraps Get32u w/ assumption that CR3 is always big endian, based on
+// ISO/IEC 14496-12 specification that all box fields are big endian.
+bool Get32u(StreamInterface* stream, size_t offset, std::uint32_t* value) {
+  return Get32u(stream, offset, tiff_directory::kBigEndian, value);
+}
+
+// Always big endian, based on ISO/IEC 14496-12 specification that all box
+// fields are big endian.
+bool Get64u(StreamInterface* stream, size_t offset, std::uint64_t* value) {
+  std::uint8_t data[8];
+  if (stream->GetData(offset, 8, data) == kOk) {
+    *value = (data[0] * 0x1000000u) | (data[1] * 0x10000u) |
+             (data[2] * 0x100u) | data[3];
+    *value <<= 32;
+    *value = (data[4] * 0x1000000u) | (data[5] * 0x10000u) |
+             (data[6] * 0x100u) | data[7];
+    return true;
+  } else {
+    return false;
+  }
+}
+
+// Jpeg box offsets based on the box tag. The expected layout is as follows:
+//        Byte Offset Type     Meaning
+//                  0 [long]   size of box
+//                  4 [char[]] box tag
+//       offset.width [short]  width of jpeg
+//      offset.height [short]  height of jpeg
+//   offset.jpeg_size [long]   number of bytes in jpeg
+//   offset.jpeg_data [byte[]] start of jpeg data
+struct JpegBoxOffset {
+  size_t width = 0;
+  size_t height = 0;
+  size_t jpeg_size = 0;
+  size_t jpeg_data = 0;
+};
+
+// Processes box w/ JPEG data. Box must be PRVW and THMB boxes.
+bool ProcessJpegBox(StreamInterface* stream, const Box& box, Image* image) {
+  static constexpr JpegBoxOffset kPrvwJpegOffsets{14, 16, 20, 24};
+  static constexpr JpegBoxOffset kThmbJpegOffsets{12, 14, 16, 24};
+  if (box.tag() != kPrvwTag && box.tag() != kThmbTag) {
+    return false;
+  }
+  const JpegBoxOffset& offsets =
+      box.tag() == kPrvwTag ? kPrvwJpegOffsets : kThmbJpegOffsets;
+  uint16_t width, height;
+  uint32_t jpeg_size;
+  if (!Get16u(stream, box.offset() + offsets.width, &width)) {
+    return false;
+  }
+  if (!Get16u(stream, box.offset() + offsets.height, &height)) {
+    return false;
+  }
+  if (!Get32u(stream, box.offset() + offsets.jpeg_size, &jpeg_size)) {
+    return false;
+  }
+  image->format = Image::kJpegCompressed;
+  image->width = width;
+  image->height = height;
+  image->offset = box.offset() + offsets.jpeg_data;
+  image->length = jpeg_size;
+  return true;
+}
+
+// Parses the Exif IFD0 tags at tiff_offset.
+bool ParseExifIfd0(StreamInterface* stream, size_t tiff_offset,
+                   PreviewImageData* preview_image_data) {
+  static const TagSet kIfd0TagSet = {kTiffTagModel, kTiffTagMake,
+                                     kTiffTagOrientation, kTiffTagImageWidth,
+                                     kTiffTagImageLength};
+  TiffContent content;
+  TiffParser(stream, tiff_offset).Parse(kIfd0TagSet, 1, &content);
+  if (content.tiff_directory.size() != 1) {
+    return false;
+  }
+
+  content.tiff_directory[0].Get(kTiffTagModel, &preview_image_data->model);
+  content.tiff_directory[0].Get(kTiffTagMake, &preview_image_data->maker);
+  content.tiff_directory[0].Get(kTiffTagOrientation,
+                                &preview_image_data->exif_orientation);
+  content.tiff_directory[0].Get(kTiffTagImageWidth,
+                                &preview_image_data->full_width);
+  content.tiff_directory[0].Get(kTiffTagImageLength,
+                                &preview_image_data->full_height);
+  return true;
+}
+
+// Parses the Exif Exif IFD tags at tiff_offset.
+bool ParseExifExifIfd(StreamInterface* stream, size_t tiff_offset,
+                      PreviewImageData* preview_image_data) {
+  static const TagSet kExifIfdTagSet = {kExifTagDateTimeOriginal,
+                                        kExifTagExposureTime, kExifTagFnumber,
+                                        kExifTagFocalLength, kExifTagIsoSpeed};
+  TiffContent content;
+  TiffParser(stream, tiff_offset).Parse(kExifIfdTagSet, 1, &content);
+  if (content.tiff_directory.size() != 1) {
+    return false;
+  }
+
+  content.tiff_directory[0].Get(kExifTagDateTimeOriginal,
+                                &preview_image_data->date_time);
+  GetRational(kExifTagExposureTime, content.tiff_directory[0], 1,
+              &preview_image_data->exposure_time);
+  GetRational(kExifTagFnumber, content.tiff_directory[0], 1,
+              &preview_image_data->fnumber);
+  GetRational(kExifTagFocalLength, content.tiff_directory[0], 1,
+              &preview_image_data->focal_length);
+  content.tiff_directory[0].Get(kExifTagIsoSpeed, &preview_image_data->iso);
+  return true;
+}
+
+// Returns the next box or an invalid box.
+//
+// Based on ISO/IEC 14496-12: boxes start with a header: size and type. The size
+// can be compact (32-bits) or extended (64-bit, e.g. mdat box).
+// The type can be compact (32 bits) or extended (full UUID, e.g. uuid boxes).
+// values are stored after the compact size/type.
+//
+// Fields in a box are big-endian.
+Box GetNextBox(StreamInterface* stream, size_t offset) {
+  uint32_t length_32;
+  if (!Get32u(stream, offset, &length_32)) {
+    return Box();
+  }
+  BoxTag tag;
+  Error status = stream->GetData(offset + sizeof(length_32), kTagSize,
+                                 reinterpret_cast<std::uint8_t*>(tag.data()));
+  if (status != kOk) {
+    return Box();
+  }
+  size_t length;
+  size_t header_offset = sizeof(length_32) + sizeof(tag);
+  if (length_32 == 1) {
+    // Magic number of 1 implies extended size.
+    uint64_t length_64 = 0;
+    if (!Get64u(stream, offset + header_offset, &length_64)) {
+      return Box();
+    }
+    length = length_64;
+    header_offset += sizeof(length_64);
+  } else {
+    // Compact size.
+    length = length_32;
+  }
+  return Box(tag, offset, header_offset, length);
+}
+
+// Searches for the next box with the given tag.
+Box GetNextBoxWithTag(StreamInterface* stream, size_t offset,
+                      const BoxTag& expected_tag) {
+  while (true) {
+    Box box = GetNextBox(stream, offset);
+    if (!box.IsValid() || box.tag() == expected_tag) {
+      return box;
+    }
+    offset = box.next_box_offset();
+  }
+}
+
+// Returns the width, height, and content type from the CRAW box.
+bool ProcessCrawBox(StreamInterface* stream, const Box& craw_box,
+                    uint16_t* width, uint16_t* height, uint16_t* content_type) {
+  constexpr size_t kWidthOffset = 32;
+  if (!Get16u(stream, craw_box.offset() + kWidthOffset, width)) {
+    return false;
+  }
+
+  constexpr size_t kHeightOffset = 34;
+  if (!Get16u(stream, craw_box.offset() + kHeightOffset, height)) {
+    return false;
+  }
+
+  constexpr size_t kTypeOffset = 86;
+  if (!Get16u(stream, craw_box.offset() + kTypeOffset, content_type)) {
+    return false;
+  }
+  return true;
+}
+
+// stsz box offset:
+//        Byte Offset Type     Meaning
+//                  0 [long]   size of box
+//                  4 [char[]] box tag
+//                  8 [long]   version/flags
+//                 12 [long]   sample size
+//                 16 [long]   number of entries in sample table
+//                 20 [long[]] sample table if samples size is 0
+bool ProcessStszBox(StreamInterface* stream, const Box& stsz_box,
+                    uint32_t* image_size) {
+  uint32_t sample_size;
+  if (!Get32u(stream, stsz_box.offset() + 12, &sample_size)) {
+    return false;
+  }
+  if (sample_size > 0) {
+    *image_size = sample_size;
+    return true;
+  }
+  // sample_size of 0 implies the data is in the sample table. We expect only
+  // one entry. This is true of Canon EOS RP Cr3 files.
+  uint32_t count;
+  if (!Get32u(stream, stsz_box.offset() + 16, &count)) {
+    return false;
+  }
+  if (count != 1) {
+    // Expect at most one entry in the table.
+    return false;
+  }
+  return Get32u(stream, stsz_box.offset() + 20, image_size);
+}
+
+// co64 box offsets:
+//        Byte Offset Type     Meaning
+//                  0 [long]   size of box
+//                  4 [char[]] box tag
+//                  8 [long]   version
+//                 12 [long]   count (expect to be value 1)
+//                 16 [long]   offset of image data in mdat
+bool ProcessCo64(StreamInterface* stream, const Box& co64_box,
+                 uint32_t* image_offset) {
+  uint32_t count = 0;
+  if (!Get32u(stream, co64_box.header_offset() + 4, &count)) {
+    return false;
+  }
+  if (count != 1) {
+    return false;
+  }
+  return Get32u(stream, co64_box.header_offset() + 8, image_offset);
+}
+
+// Process the stbl box. Expected box layout:
+// stbl
+//   stsd
+//     CRAW  (embedded image (JPEG) information)
+//   (0 or more skipped boxes)
+//   stsz (embedded image byte size)
+//   (0 or more skipped boxes)
+//   co64 (offset of embedded image, relative to mdat box)
+bool ProcessStblBox(StreamInterface* stream, const Box& stbl_box,
+                    ProcessData* data) {
+  Box stsd_box = GetNextBoxWithTag(stream, stbl_box.header_offset(), kStsdTag);
+  if (!stsd_box.IsValid()) {
+    return false;
+  }
+  // This is either CRAW or CTMD. Skip when CTMD.
+  Box craw_box = GetNextBox(stream, stsd_box.header_offset() + 8);
+  if (!craw_box.IsValid()) {
+    return false;
+  }
+  if (craw_box.tag() != kCrawTag) {
+    return true;
+  }
+  // CRAW contains info about the full-size image embedded in the mdat box.
+  // The image is either JPEG or HEVC.
+  uint16_t image_width = 0;
+  uint16_t image_height = 0;
+  uint16_t content_type = 0;
+  if (!ProcessCrawBox(stream, craw_box, &image_width, &image_height,
+                      &content_type)) {
+    return false;
+  }
+  // Only continue if JPEG or HEVC content.
+  constexpr uint16_t kJpegContentType = 3;
+  constexpr uint16_t kHevcContentType = 4;
+  if (content_type != kJpegContentType && content_type != kHevcContentType) {
+    return true;
+  }
+
+  // Skip until we find stsz, contains the size (# of bytes) of image data.
+  Box stsz_box =
+      GetNextBoxWithTag(stream, stsd_box.next_box_offset(), kStszTag);
+  if (!stsz_box.IsValid()) {
+    return false;
+  }
+  uint32_t image_size;
+  if (!ProcessStszBox(stream, stsz_box, &image_size)) {
+    return false;
+  }
+
+  // Skip until we find co64, contains the offset of image data.
+  Box co64_box =
+      GetNextBoxWithTag(stream, stsz_box.next_box_offset(), kCo64Tag);
+  if (!co64_box.IsValid()) {
+    return false;
+  }
+
+  uint32_t image_offset = 0;
+  if (!ProcessCo64(stream, co64_box, &image_offset)) {
+    return false;
+  }
+
+  data->mdat_image.format = content_type == kJpegContentType
+                                ? Image::kJpegCompressed
+                                : Image::kHevcCompressed;
+  data->mdat_image.width = image_width;
+  data->mdat_image.height = image_height;
+  data->mdat_image.length = image_size;
+  // This offset is relative to the position of the mdat box. The value will
+  // be updated once mdat's offset is found.
+  data->mdat_image.offset = image_offset;
+  return true;
+}
+
+// Returns true if we should parse the children of the box.
+bool DoProcessChildren(const BoxTag& tag) {
+  static const std::set<BoxTag> kTags = {NewTag("trak"), NewTag("moov"),
+                                         NewTag("mdia"), NewTag("minf")};
+  return kTags.find(tag) != kTags.end();
+}
+
+// Processes box and returns offset of the next box to process.
+// A return value of 0 indicates an error.
+//
+// Outline of hierarchy and important boxes:
+// ftyp
+// moov
+//   uuid (id is kUuidMoov)
+//     ... boxes we skip ...
+//     CMT1 (EXIF data)
+//     CMT2 (EXIF data)
+//     ... boxes we skip ...
+//     THMB (160x120 JPEG thumbnail, embedded in this box)
+//   trak
+//     tkhd
+//     mdia
+//     ... boxes we skip ...
+//     minf
+//       ... boxes we skip ...
+//       stbl
+//         stsd
+//           CRAW (Full image preview, type (JPEG or HEVC), width, height. The
+//                 image data is found in mdat box, below.)
+//       ... boxes we skip ...
+//       stsz (Size of preview, in bytes)
+//       ... boxes we skip ...
+//       co64 (Location/offset of full preview data in mdat)
+//   .. boxes we skip ...
+// uuid (id is kUuidPrvw)
+//   PRVW (1620x1080 JPEG preview, embedded in this box)
+// mdat
+//   Full image preview (JPEG or HEVC)
+//   ... RAW image data ...
+size_t ProcessBox(StreamInterface* stream, const Box& box, ProcessData* data) {
+  // Parse child boxes.
+  if (box.tag() == kUuidTag) {
+    // Uuid box have extended box types.
+    Uuid uuid;
+    if (stream->GetData(box.header_offset(), uuid.size(), uuid.data()) != kOk) {
+      return 0;
+    }
+    if (uuid == kUuidPrvw) {
+      return box.header_offset() + uuid.size() + 8;
+    } else if (uuid == kUuidMoov) {
+      return box.header_offset() + uuid.size();
+    }  // else skip the box, below.
+  } else if (DoProcessChildren(box.tag())) {
+    return box.header_offset();
+  }
+
+  // Potentially process the data contained in the box.
+  bool success;
+  if (box.tag() == kMdatTag) {
+    // mdat_image.offset is relative to mdat's header, update it to be absolute
+    // offset to the image data.
+    data->mdat_image.offset += box.header_offset();
+    success = true;
+  } else if (box.tag() == kStblTag) {
+    success = ProcessStblBox(stream, box, data);
+  } else if (box.tag() == kPrvwTag) {
+    // Preview jpeg. 1620x1080 for EOS R.
+    success = ProcessJpegBox(stream, box, &data->prvw_image);
+  } else if (box.tag() == kThmbTag) {
+    // Thumbnail jpeg. 160x120 for EOS R.
+    success = ProcessJpegBox(stream, box, &data->preview_image_data->thumbnail);
+  } else if (box.tag() == kCmt1Tag) {
+    success =
+        ParseExifIfd0(stream, box.header_offset(), data->preview_image_data);
+  } else if (box.tag() == kCmt2Tag) {
+    success =
+        ParseExifExifIfd(stream, box.header_offset(), data->preview_image_data);
+  } else {
+    // This box isn't interesting, skip it.
+    success = true;
+  }
+  return success ? box.next_box_offset() : 0;
+}
+
+bool ProcessStream(StreamInterface* stream, const BoxTag& last_chunk,
+                   ProcessData* data) {
+  size_t offset = 0;
+  while (true) {
+    Box box = GetNextBox(stream, offset);
+    if (!box.IsValid()) {
+      return false;
+    }
+    size_t new_offset = ProcessBox(stream, box, data);
+    if (new_offset <= offset) {
+      return false;
+    }
+    if (box.tag() == last_chunk) {
+      return true;
+    }
+    offset = new_offset;
+  }
+}
+
+bool IsImage(StreamInterface* stream, const Image& image) {
+  if (image.format != Image::kJpegCompressed) {
+    // Pass responsibility to the caller.
+    return true;
+  }
+  // Check for JPEG magic number at start. This could be HEVC data.
+  constexpr std::array<uint8_t, 3> kJpegMagicNumber = {0xFF, 0xD8, 0xFF};
+  std::array<uint8_t, 3> magic_number;
+  if (stream->GetData(image.offset, magic_number.size(), magic_number.data()) !=
+      kOk) {
+    return false;
+  }
+  return magic_number == kJpegMagicNumber;
+}
+
+}  // namespace
+
+Error Cr3GetPreviewData(StreamInterface* stream,
+                        PreviewImageData* preview_image_data) {
+  ProcessData data{.preview_image_data = preview_image_data};
+  if (!ProcessStream(stream, kMdatTag, &data)) {
+    return kFail;
+  }
+  // Prefer image in mdata box, as spec ensures it is the largest image.
+  if (data.mdat_image.length > 0 && IsImage(stream, data.mdat_image)) {
+    preview_image_data->preview = data.mdat_image;
+  } else if (data.prvw_image.length > 0 && IsImage(stream, data.prvw_image)) {
+    preview_image_data->preview = data.prvw_image;
+  } else {
+    return kFail;
+  }
+  return kOk;
+}
+
+bool Cr3GetOrientation(StreamInterface* stream, std::uint32_t* orientation) {
+  PreviewImageData preview_image_data;
+  ProcessData data{.preview_image_data = &preview_image_data};
+  if (ProcessStream(stream, kCmt1Tag, &data)) {
+    *orientation = preview_image_data.exif_orientation;
+    return true;
+  }
+  return false;
+}
+
+}  // namespace piex
diff --git a/src/piex_cr3.h b/src/piex_cr3.h
new file mode 100644
index 0000000..3108503
--- /dev/null
+++ b/src/piex_cr3.h
@@ -0,0 +1,43 @@
+// Copyright 2020 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#include "src/piex_types.h"
+
+#ifndef PIEX_PIEX_CR3_H_
+#define PIEX_PIEX_CR3_H_
+
+namespace piex {
+
+// Gets the EXIF orientation of a CR3 stream, returning true on success.
+bool Cr3GetOrientation(StreamInterface* stream, std::uint32_t* orientation);
+
+// Gets preview images of a CR3 stream, returning kOk on success. Assumes the
+// stream is a CR3 stream.
+//
+// Canon's CR3 is based on ISO/IEC 14496-12: ISO base media file format. (CR2 is
+// TIFF based.) A Canon CR3 contains multiple embedded images. Most cameras
+// output CR3 files that contain a full-size JPEG, a 1620x1080 preview JPEG, and
+// a 160x120 thumbnail JPEG.
+// The Canon EOS 1D X Mark III, though, contains a full-size HEVC image, a
+// 1620x1080 preview JPEG, and a 160x120 thumbnail JPEG.
+// Until support for HEVC is added, this method returns the largest embedded
+// JPEG in preview_image_data->preview.
+//
+Error Cr3GetPreviewData(StreamInterface* stream,
+                        PreviewImageData* preview_image_data);
+}  // namespace piex
+
+#endif  // PIEX_PIEX_CR3_H_
diff --git a/src/piex_types.h b/src/piex_types.h
index 4fdb7c2..2062136 100644
--- a/src/piex_types.h
+++ b/src/piex_types.h
@@ -37,6 +37,7 @@
   enum Format {
     kJpegCompressed,
     kUncompressedRgb,
+    kHevcCompressed,
   };
 
   std::uint16_t width = 0;
diff --git a/src/tiff_parser.cc b/src/tiff_parser.cc
index f36c5ba..3ceaa75 100644
--- a/src/tiff_parser.cc
+++ b/src/tiff_parser.cc
@@ -27,7 +27,6 @@
 
 using tiff_directory::Endian;
 using tiff_directory::Rational;
-using tiff_directory::SRational;
 using tiff_directory::SizeOfType;
 using tiff_directory::TIFF_TYPE_LONG;
 using tiff_directory::TIFF_TYPE_UNDEFINED;
@@ -54,20 +53,6 @@
   return true;
 }
 
-bool GetRational(const TiffDirectory::Tag& tag, const TiffDirectory& directory,
-                 const int data_size, PreviewImageData::Rational* data) {
-  std::vector<Rational> value;
-  if (directory.Get(tag, &value) &&
-      value.size() == static_cast<size_t>(data_size)) {
-    for (size_t i = 0; i < value.size(); ++i) {
-      data[i].numerator = value[i].numerator;
-      data[i].denominator = value[i].denominator;
-    }
-    return true;
-  }
-  return false;
-}
-
 void FillGpsPreviewImageData(const TiffDirectory& gps_directory,
                              PreviewImageData* preview_image_data) {
   if (gps_directory.Has(kGpsTagLatitudeRef) &&
@@ -410,8 +395,8 @@
       default:
         return false;
     }
-    length = static_cast<std::uint32_t>(
-        std::accumulate(strip_byte_counts.begin(), strip_byte_counts.end(), 0));
+    length = static_cast<std::uint32_t>(std::accumulate(
+        strip_byte_counts.begin(), strip_byte_counts.end(), 0U));
     offset = strip_offsets[0];
   } else if (tiff_directory.Has(kPanaTagJpegImage)) {
     if (!tiff_directory.GetOffsetAndLength(
@@ -463,6 +448,20 @@
   return false;
 }
 
+bool GetRational(const TiffDirectory::Tag& tag, const TiffDirectory& directory,
+                 const int data_size, PreviewImageData::Rational* data) {
+  std::vector<Rational> value;
+  if (directory.Get(tag, &value) &&
+      value.size() == static_cast<size_t>(data_size)) {
+    for (size_t i = 0; i < value.size(); ++i) {
+      data[i].numerator = value[i].numerator;
+      data[i].denominator = value[i].denominator;
+    }
+    return true;
+  }
+  return false;
+}
+
 bool IsThumbnail(const Image& image, const int max_dimension) {
   return image.width <= max_dimension && image.height <= max_dimension;
 }
@@ -715,14 +714,14 @@
   return true;
 }
 
-bool TiffParser::ParseIfd(const std::uint32_t offset_to_ifd,
+bool TiffParser::ParseIfd(const std::uint32_t ifd_offset,
                           const TagSet& desired_tags,
                           const std::uint16_t max_number_ifds,
                           IfdVector* tiff_directory) {
   std::uint32_t next_ifd_offset;
   TiffDirectory tiff_ifd(static_cast<Endian>(endian_));
-  if (!ParseDirectory(tiff_offset_, offset_to_ifd, endian_, desired_tags,
-                      stream_, &tiff_ifd, &next_ifd_offset) ||
+  if (!ParseDirectory(tiff_offset_, ifd_offset, endian_, desired_tags, stream_,
+                      &tiff_ifd, &next_ifd_offset) ||
       !ParseSubIfds(tiff_offset_, desired_tags, max_number_ifds, endian_,
                     stream_, &tiff_ifd)) {
     return false;
diff --git a/src/tiff_parser.h b/src/tiff_parser.h
index e809274..e19dea2 100644
--- a/src/tiff_parser.h
+++ b/src/tiff_parser.h
@@ -167,6 +167,12 @@
 bool GetFullCropDimension(const tiff_directory::TiffDirectory& tiff_directory,
                           std::uint32_t* width, std::uint32_t* height);
 
+// Reads 1 or more rational values for a tag and stores results into data.
+// Returns false if an error occurred.
+bool GetRational(const tiff_directory::TiffDirectory::Tag& tag,
+                 const tiff_directory::TiffDirectory& directory,
+                 const int data_size, PreviewImageData::Rational* data);
+
 // Enables us to parse through data that complies to the Tiff/EP specification.
 class TiffParser {
  public:
@@ -178,7 +184,7 @@
   // Runs over the Tiff IFD, Exif IFD and subIFDs to get the preview image data.
   // Returns false if something with the Tiff tags is wrong.
   bool GetPreviewImageData(const TiffContent& tiff_content,
-                           PreviewImageData* image_metadata);
+                           PreviewImageData* preview_image_data);
 
   // Returns false if called more that once or something with the Tiff data is
   // wrong.