summaryrefslogtreecommitdiff
path: root/src/jpeg
diff options
context:
space:
mode:
Diffstat (limited to 'src/jpeg')
-rw-r--r--src/jpeg/jpeg_apple_depth_builder.cc252
-rw-r--r--src/jpeg/jpeg_image_extractor.cc110
-rw-r--r--src/jpeg/jpeg_info_builder.cc232
-rw-r--r--src/jpeg/jpeg_marker.cc98
-rw-r--r--src/jpeg/jpeg_scanner.cc120
-rw-r--r--src/jpeg/jpeg_segment.cc174
-rw-r--r--src/jpeg/jpeg_segment_builder.cc160
-rw-r--r--src/jpeg/jpeg_segment_lister.cc158
-rw-r--r--src/jpeg/jpeg_xmp_data_extractor.cc103
-rw-r--r--src/jpeg/jpeg_xmp_info.cc48
-rw-r--r--src/jpeg/jpeg_xmp_info_builder.cc42
11 files changed, 1497 insertions, 0 deletions
diff --git a/src/jpeg/jpeg_apple_depth_builder.cc b/src/jpeg/jpeg_apple_depth_builder.cc
new file mode 100644
index 0000000..ce83f9f
--- /dev/null
+++ b/src/jpeg/jpeg_apple_depth_builder.cc
@@ -0,0 +1,252 @@
+#include "image_io/jpeg/jpeg_apple_depth_builder.h"
+
+#include <cstring>
+#include <sstream>
+
+#include "image_io/base/byte_buffer.h"
+#include "image_io/base/data_segment_data_source.h"
+#include "image_io/base/message.h"
+#include "image_io/base/message_handler.h"
+#include "image_io/jpeg/jpeg_info.h"
+#include "image_io/jpeg/jpeg_info_builder.h"
+#include "image_io/jpeg/jpeg_scanner.h"
+#include "image_io/jpeg/jpeg_segment_info.h"
+
+namespace photos_editing_formats {
+namespace image_io {
+
+using std::string;
+using std::vector;
+
+namespace {
+
+/// The special Apple depth JFIF segment suffix and length. The -1 in the
+/// kAmpfLength compuration is because the size of kAmpf is 5 bytes, including
+/// the terminating null character, but the kAmpfLength should be 4. Can't use
+/// strlen (which would be better) because it is not constexpr-able.
+const char kAmpf[] = "AMPF";
+constexpr size_t kAmpfLength = sizeof(kAmpf) - 1;
+
+/// The contents of the MPF segment length and value in three parts. For more
+/// information, see go/photos-image-io-phase2-summary.
+const size_t kMpfSegmentLength = 0x5A;
+const char kMpfHex0[] =
+ "FFE200584D5046004D4D002A000000080003B00000070000000430313030B0010004000000"
+ "0100000002B002000700000020000000320000000000030000";
+// Four byte primary image length value
+const char kMpfHex1[] = "000000000000000000000000";
+// Four byte depth image length value
+// Four byte depth image offset value
+const char kMpfHex2[] = "00000000";
+
+/// The optimum size to use for the DataSource::TransferData() function.
+constexpr size_t kBestDataSize = 0x10000;
+
+/// @param image_limit The limit on the number of images to get info of.
+/// @param data_source The data source from which to get info.
+/// @param info A pointer to the jpeg_info object to receive the info.
+/// @return Whether the info was obtained successfully or not.
+bool GetJpegInfo(int image_limit, DataSource* data_source, JpegInfo* info) {
+ JpegInfoBuilder info_builder;
+ info_builder.SetImageLimit(image_limit);
+ info_builder.SetCaptureSegmentBytes(kJfif);
+ JpegScanner scanner;
+ scanner.Run(data_source, &info_builder);
+ if (scanner.HasError()) {
+ return false;
+ }
+ *info = info_builder.GetInfo();
+ return true;
+}
+
+} // namespace
+
+bool JpegAppleDepthBuilder::Run(DataSource* primary_image_data_source,
+ DataSource* depth_image_data_source,
+ DataDestination* data_destination) {
+ primary_image_data_source_ = primary_image_data_source;
+ depth_image_data_source_ = depth_image_data_source;
+ data_destination_ = data_destination;
+ if (!GetPrimaryImageData()) {
+ MessageHandler::Get()->ReportMessage(Message::kDecodingError,
+ "Primary image data");
+ return false;
+ }
+ if (!GetDepthImageData()) {
+ MessageHandler::Get()->ReportMessage(Message::kDecodingError,
+ "Depth image data");
+ return false;
+ }
+ data_destination->StartTransfer();
+ bool status = TransferPrimaryImage();
+ if (status) {
+ status = TransferDepthImage();
+ }
+ data_destination->FinishTransfer();
+ return status;
+}
+
+bool JpegAppleDepthBuilder::GetPrimaryImageData() {
+ JpegInfo info;
+ if (!GetJpegInfo(1, primary_image_data_source_, &info)) {
+ return false;
+ }
+ if (info.GetImageRanges().empty()) {
+ return false;
+ }
+ primary_image_range_ = info.GetImageRanges()[0];
+ JpegSegmentInfo jfif_segment_info = info.GetSegmentInfo(0, kJfif);
+ if (!jfif_segment_info.IsValid() ||
+ jfif_segment_info.GetBytes().size() < kAmpfLength) {
+ return false;
+ }
+ primary_image_jfif_segment_range_ = jfif_segment_info.GetDataRange();
+ primary_image_jfif_segment_bytes_ = jfif_segment_info.GetBytes();
+
+ JpegSegmentInfo exif_info = info.GetSegmentInfo(0, kExif);
+ if (!exif_info.IsValid()) {
+ return false;
+ }
+ JpegSegmentInfo mpf_info = info.GetSegmentInfo(0, kMpf);
+ if (mpf_info.IsValid()) {
+ primary_image_mpf_segment_range_ = mpf_info.GetDataRange();
+ } else {
+ size_t exif_end = exif_info.GetDataRange().GetEnd();
+ primary_image_mpf_segment_range_ = DataRange(exif_end, exif_end);
+ }
+ return true;
+}
+
+bool JpegAppleDepthBuilder::GetDepthImageData() {
+ JpegInfo info;
+ if (!GetJpegInfo(2, depth_image_data_source_, &info)) {
+ return false;
+ }
+ if (!info.HasAppleDepth()) {
+ return false;
+ }
+ depth_image_range_ = info.GetAppleDepthImageRange();
+ return true;
+}
+
+bool JpegAppleDepthBuilder::TransferPrimaryImage() {
+ // The first move involves all from the start of the data source to the
+ // mpf location or the beginning of the jfif segment, which ever comes first.
+ size_t first_end = std::min(primary_image_jfif_segment_range_.GetBegin(),
+ primary_image_mpf_segment_range_.GetBegin());
+ DataRange first_range(0, first_end);
+ if (!TransferData(primary_image_data_source_, first_range)) {
+ return false;
+ }
+
+ // Move the new Jfif segment. If the primary image jfif came right after the
+ // SOI then the first_end is positioned at the start of the jfif segment. So
+ // move it to the end so that the original jfif segment does not get copied
+ // to the output destination.
+ size_t jfif_length_delta = 0;
+ if (!TransferNewJfifSegment(&jfif_length_delta)) {
+ return false;
+ }
+ if (first_end == primary_image_jfif_segment_range_.GetBegin()) {
+ first_end = primary_image_jfif_segment_range_.GetEnd();
+ }
+
+ // The second move is from the end of the first move or the end of the jfif
+ // segment, which ever comes first to the mpf location.
+ size_t second_begin =
+ std::min(first_end, primary_image_jfif_segment_range_.GetEnd());
+ DataRange second_range(second_begin,
+ primary_image_mpf_segment_range_.GetBegin());
+ if (second_range.IsValid()) {
+ if (!TransferData(primary_image_data_source_, second_range)) {
+ return false;
+ }
+ }
+
+ // Move the new Mpf segment.
+ if (!TransferNewMpfSegment(jfif_length_delta)) {
+ return false;
+ }
+
+ // The third move is from from the end of the mpf to the end of the image.
+ DataRange mpf_eoi_range(primary_image_mpf_segment_range_.GetEnd(),
+ primary_image_range_.GetEnd());
+ if (!mpf_eoi_range.IsValid()) {
+ return false;
+ }
+ return TransferData(primary_image_data_source_, mpf_eoi_range);
+}
+
+bool JpegAppleDepthBuilder::TransferNewJfifSegment(size_t* jfif_length_delta) {
+ *jfif_length_delta = 0;
+ size_t jfif_size = primary_image_jfif_segment_bytes_.size();
+ Byte* jfif_bytes = new Byte[jfif_size + kAmpfLength];
+ memcpy(jfif_bytes, primary_image_jfif_segment_bytes_.data(), jfif_size);
+ if (memcmp(jfif_bytes + jfif_size - kAmpfLength, kAmpf, kAmpfLength) != 0) {
+ memcpy(jfif_bytes + jfif_size, kAmpf, kAmpfLength);
+ *jfif_length_delta = kAmpfLength;
+ jfif_size += kAmpfLength;
+ size_t jfif_data_length = jfif_size - 2;
+ jfif_bytes[2] = ((jfif_data_length >> 8) & 0xFF);
+ jfif_bytes[3] = (jfif_data_length & 0xFF);
+ }
+ DataRange jfif_range(0, jfif_size);
+ auto jfif_segment = DataSegment::Create(jfif_range, jfif_bytes);
+ DataSegmentDataSource jfif_data_source(jfif_segment);
+ return TransferData(&jfif_data_source, jfif_range);
+}
+
+bool JpegAppleDepthBuilder::TransferNewMpfSegment(size_t jfif_length_delta) {
+ size_t primary_image_length =
+ primary_image_range_.GetLength() + jfif_length_delta -
+ primary_image_mpf_segment_range_.GetLength() + kMpfSegmentLength;
+ size_t depth_image_length = depth_image_range_.GetLength();
+ size_t depth_image_offset =
+ primary_image_length - primary_image_mpf_segment_range_.GetBegin() - 8;
+ vector<ByteData> mpf_bytes;
+ mpf_bytes.reserve(5);
+ mpf_bytes.emplace_back(ByteData::kHex, kMpfHex0);
+ mpf_bytes.emplace_back(ByteData::kHex,
+ ByteData::Size2BigEndianHex(primary_image_length));
+ mpf_bytes.emplace_back(ByteData::kHex, kMpfHex1);
+ mpf_bytes.emplace_back(ByteData::kHex,
+ ByteData::Size2BigEndianHex(depth_image_length));
+ mpf_bytes.emplace_back(ByteData::kHex,
+ ByteData::Size2BigEndianHex(depth_image_offset));
+ mpf_bytes.emplace_back(ByteData::kHex, kMpfHex2);
+ ByteBuffer mpf_byte_buffer(mpf_bytes);
+ size_t mpf_segment_size = mpf_byte_buffer.GetSize();
+ if (!mpf_byte_buffer.IsValid() || mpf_segment_size != kMpfSegmentLength) {
+ return false;
+ }
+ DataRange mpf_range(0, mpf_segment_size);
+ auto mpf_segment = DataSegment::Create(mpf_range, mpf_byte_buffer.Release());
+ DataSegmentDataSource mpf_data_source(mpf_segment);
+ return TransferData(&mpf_data_source, mpf_range);
+}
+
+bool JpegAppleDepthBuilder::TransferDepthImage() {
+ return TransferData(depth_image_data_source_, depth_image_range_);
+}
+
+bool JpegAppleDepthBuilder::TransferData(DataSource* data_source,
+ const DataRange& data_range) {
+ size_t old_byte_count = data_destination_->GetBytesTransferred();
+ DataSource::TransferDataResult result =
+ data_source->TransferData(data_range, kBestDataSize, data_destination_);
+ if (result == DataSource::kTransferDataSuccess) {
+ size_t bytes_transferred =
+ data_destination_->GetBytesTransferred() - old_byte_count;
+ if (bytes_transferred != data_range.GetLength()) {
+ result = DataSource::kTransferDataError;
+ std::stringstream ss;
+ ss << "JpegAppleDepthBuilder:data source transferred "
+ << bytes_transferred << " bytes instead of " << data_range.GetLength();
+ MessageHandler::Get()->ReportMessage(Message::kInternalError, ss.str());
+ }
+ }
+ return result == DataSource::kTransferDataSuccess;
+}
+
+} // namespace image_io
+} // namespace photos_editing_formats
diff --git a/src/jpeg/jpeg_image_extractor.cc b/src/jpeg/jpeg_image_extractor.cc
new file mode 100644
index 0000000..82f8fce
--- /dev/null
+++ b/src/jpeg/jpeg_image_extractor.cc
@@ -0,0 +1,110 @@
+#include "image_io/jpeg/jpeg_image_extractor.h"
+
+#include <sstream>
+
+#include "image_io/base/data_range_tracking_destination.h"
+#include "image_io/base/message_handler.h"
+#include "image_io/extras/base64_decoder_data_destination.h"
+#include "image_io/jpeg/jpeg_segment.h"
+#include "image_io/jpeg/jpeg_xmp_data_extractor.h"
+
+/// Set this macro to 1 for debug output.
+#define PHOTOS_EDITING_FORMATS_IMAGE_IO_JPEG_JPEG_IMAGE_EXTRACTOR_DEBUG 0
+
+namespace photos_editing_formats {
+namespace image_io {
+
+using std::vector;
+
+namespace {
+
+/// The optimim size to use for the DataSource::TransferData() function.
+constexpr size_t kBestDataSize = 0x10000;
+
+} // namespace
+
+bool JpegImageExtractor::ExtractAppleDepthImage(
+ DataDestination* image_destination) {
+ bool succeeded =
+ ExtractImage(jpeg_info_.GetAppleDepthImageRange(), image_destination);
+ return jpeg_info_.HasAppleDepth() && succeeded;
+}
+
+bool JpegImageExtractor::ExtractAppleMatteImage(
+ DataDestination* image_destination) {
+ bool succeeded =
+ ExtractImage(jpeg_info_.GetAppleMatteImageRange(), image_destination);
+ return jpeg_info_.HasAppleMatte() && succeeded;
+}
+
+bool JpegImageExtractor::ExtractImage(const DataRange& image_range,
+ DataDestination* image_destination) {
+ DataRangeTrackingDestination data_range_destination(image_destination);
+ bool has_errors = false;
+ data_range_destination.StartTransfer();
+ if (image_range.IsValid()) {
+ DataSource::TransferDataResult result = data_source_->TransferData(
+ image_range, kBestDataSize, &data_range_destination);
+ if (result == DataSource::kTransferDataError) {
+ has_errors = true;
+ } else if (result == DataSource::kTransferDataNone ||
+ data_range_destination.HasDisjointTransferRanges() ||
+ data_range_destination.GetTrackedDataRange() != image_range) {
+ has_errors = true;
+ MessageHandler::Get()->ReportMessage(Message::kPrematureEndOfDataError,
+ "");
+ }
+ }
+ data_range_destination.FinishTransfer();
+ return !has_errors;
+}
+
+bool JpegImageExtractor::ExtractGDepthImage(
+ DataDestination* image_destination) {
+ return ExtractImage(JpegXmpInfo::kGDepthInfoType, image_destination);
+}
+
+bool JpegImageExtractor::ExtractGImageImage(
+ DataDestination* image_destination) {
+ return ExtractImage(JpegXmpInfo::kGImageInfoType, image_destination);
+}
+
+bool JpegImageExtractor::ExtractImage(JpegXmpInfo::Type xmp_info_type,
+ DataDestination* image_destination) {
+ bool has_errors = false;
+ const bool has_image = jpeg_info_.HasImage(xmp_info_type);
+ Base64DecoderDataDestination base64_decoder(image_destination);
+ const vector<DataRange>& data_ranges =
+ jpeg_info_.GetSegmentDataRanges(xmp_info_type);
+ size_t data_ranges_count = data_ranges.size();
+ JpegXmpDataExtractor xmp_data_extractor(xmp_info_type, data_ranges_count,
+ &base64_decoder);
+ xmp_data_extractor.StartTransfer();
+ if (has_image) {
+ for (size_t index = 0; index < data_ranges_count; ++index) {
+ const DataRange& data_range = data_ranges[index];
+ xmp_data_extractor.SetSegmentIndex(index);
+#if PHOTOS_EDITING_FORMATS_IMAGE_IO_JPEG_JPEG_IMAGE_EXTRACTOR_DEBUG
+ std::stringstream sstream;
+ sstream << "Segment " << index << " from " << data_range.GetBegin()
+ << " to " << data_range.GetEnd();
+ MessageHandler::Get()->ReportMessage(Message::kStatus, sstream.str());
+#endif // PHOTOS_EDITING_FORMATS_IMAGE_IO_JPEG_JPEG_IMAGE_EXTRACTOR_DEBUG
+ DataSource::TransferDataResult result = data_source_->TransferData(
+ data_range, kBestDataSize, &xmp_data_extractor);
+ if (result == DataSource::kTransferDataError) {
+ has_errors = true;
+ break;
+ } else if (result == DataSource::kTransferDataNone) {
+ has_errors = true;
+ MessageHandler::Get()->ReportMessage(Message::kPrematureEndOfDataError,
+ "");
+ }
+ }
+ }
+ xmp_data_extractor.FinishTransfer();
+ return has_image && !has_errors;
+}
+
+} // namespace image_io
+} // namespace photos_editing_formats
diff --git a/src/jpeg/jpeg_info_builder.cc b/src/jpeg/jpeg_info_builder.cc
new file mode 100644
index 0000000..29ad8bc
--- /dev/null
+++ b/src/jpeg/jpeg_info_builder.cc
@@ -0,0 +1,232 @@
+#include "image_io/jpeg/jpeg_info_builder.h"
+
+#include <sstream>
+#include <string>
+
+#include "image_io/base/message_handler.h"
+#include "image_io/jpeg/jpeg_marker.h"
+#include "image_io/jpeg/jpeg_scanner.h"
+#include "image_io/jpeg/jpeg_segment.h"
+
+namespace photos_editing_formats {
+namespace image_io {
+
+using std::string;
+using std::stringstream;
+using std::vector;
+
+JpegInfoBuilder::JpegInfoBuilder()
+ : image_limit_(std::numeric_limits<int>::max()), image_count_(0),
+ gdepth_info_builder_(JpegXmpInfo::kGDepthInfoType),
+ gimage_info_builder_(JpegXmpInfo::kGImageInfoType) {}
+
+void JpegInfoBuilder::SetCaptureSegmentBytes(
+ const std::string& segment_info_type) {
+ capture_segment_bytes_types_.insert(segment_info_type);
+}
+
+void JpegInfoBuilder::Start(JpegScanner* scanner) {
+ JpegMarker::Flags marker_flags;
+ marker_flags[JpegMarker::kSOI] = true;
+ marker_flags[JpegMarker::kEOI] = true;
+ marker_flags[JpegMarker::kAPP0] = true;
+ marker_flags[JpegMarker::kAPP1] = true;
+ marker_flags[JpegMarker::kAPP2] = true;
+ scanner->UpdateInterestingMarkerFlags(marker_flags);
+}
+
+void JpegInfoBuilder::Process(JpegScanner* scanner,
+ const JpegSegment& segment) {
+ // SOI segments are used to track of the number of images in the JPEG file.
+ // Apple depth images start with a SOI marker, so store its range for later.
+ JpegMarker marker = segment.GetMarker();
+ if (marker.GetType() == JpegMarker::kSOI) {
+ image_count_++;
+ image_mpf_count_.push_back(0);
+ image_xmp_apple_depth_count_.push_back(0);
+ image_xmp_apple_matte_count_.push_back(0);
+ most_recent_soi_marker_range_ =
+ DataRange(segment.GetBegin(), segment.GetBegin() + JpegMarker::kLength);
+ } else if (marker.GetType() == JpegMarker::kEOI) {
+ if (most_recent_soi_marker_range_.IsValid()) {
+ DataRange image_range(most_recent_soi_marker_range_.GetBegin(),
+ segment.GetBegin() + JpegMarker::kLength);
+ jpeg_info_.AddImageRange(image_range);
+ // This image range might represent the Apple depth or matte image if
+ // other info indicates such an image is in progress and the apple image
+ // range has not yet been set.
+ if (HasAppleDepth() && !jpeg_info_.GetAppleDepthImageRange().IsValid()) {
+ jpeg_info_.SetAppleDepthImageRange(image_range);
+ }
+ if (HasAppleMatte() && !jpeg_info_.GetAppleMatteImageRange().IsValid()) {
+ jpeg_info_.SetAppleMatteImageRange(image_range);
+ }
+ if (image_count_ >= image_limit_) {
+ scanner->SetDone();
+ }
+ }
+ } else if (marker.GetType() == JpegMarker::kAPP0) {
+ // APP0/JFIF segments are interesting.
+ if (image_count_ > 0 && IsJfifSegment(segment)) {
+ const auto& data_range = segment.GetDataRange();
+ JpegSegmentInfo segment_info(image_count_ - 1, data_range, kJfif);
+ MaybeCaptureSegmentBytes(kJfif, segment, segment_info.GetMutableBytes());
+ jpeg_info_.AddSegmentInfo(segment_info);
+ }
+ } else if (marker.GetType() == JpegMarker::kAPP2) {
+ // APP2/MPF segments. JPEG files with Apple depth information have this
+ // segment in the primary (first) image of the file, but note their presence
+ // where ever they are found.
+ if (image_count_ > 0 && IsMpfSegment(segment)) {
+ ++image_mpf_count_[image_count_ - 1];
+ const auto& data_range = segment.GetDataRange();
+ JpegSegmentInfo segment_info(image_count_ - 1, data_range, kMpf);
+ MaybeCaptureSegmentBytes(kMpf, segment, segment_info.GetMutableBytes());
+ jpeg_info_.AddSegmentInfo(segment_info);
+ }
+ } else if (marker.GetType() == JpegMarker::kAPP1) {
+ // APP1/XMP segments. Both Apple depth and GDepthV1 image formats have
+ // APP1/XMP segments with important information in them. There are two types
+ // of XMP segments, a primary one (that starts with kXmpId) and an extended
+ // one (that starts with kExtendedXmpId). Apple depth information is only in
+ // the former, while GDepthV1/GImageV1 information is in both.
+ if (IsPrimaryXmpSegment(segment)) {
+ // The primary XMP segment in a non-primary image (i.e., not the first
+ // image in the file) may contain Apple depth/matte information.
+ if (image_count_ > 1 && HasId(segment, kXmpAppleDepthId)) {
+ ++image_xmp_apple_depth_count_[image_count_ - 1];
+ } else if (image_count_ > 1 && HasId(segment, kXmpAppleMatteId)) {
+ ++image_xmp_apple_matte_count_[image_count_ - 1];
+ } else if (image_count_ == 1 && (HasId(segment, kXmpGDepthV1Id) ||
+ HasId(segment, kXmpGImageV1Id))) {
+ // The primary XMP segment in the primary image may contain GDepthV1
+ // and/or GImageV1 data.
+ SetPrimaryXmpGuid(segment);
+ SetXmpMimeType(segment, JpegXmpInfo::kGDepthInfoType);
+ SetXmpMimeType(segment, JpegXmpInfo::kGImageInfoType);
+ }
+ } else if (image_count_ == 1 && IsExtendedXmpSegment(segment)) {
+ // The extended XMP segment in the primary image may contain GDepth and/or
+ // GImage data.
+ if (HasMatchingExtendedXmpGuid(segment)) {
+ gdepth_info_builder_.ProcessSegment(segment);
+ gimage_info_builder_.ProcessSegment(segment);
+ }
+ } else if (image_count_ > 0 && IsExifSegment(segment)) {
+ const auto& data_range = segment.GetDataRange();
+ JpegSegmentInfo segment_info(image_count_ - 1, data_range, kExif);
+ MaybeCaptureSegmentBytes(kExif, segment, segment_info.GetMutableBytes());
+ jpeg_info_.AddSegmentInfo(segment_info);
+ }
+ }
+}
+
+void JpegInfoBuilder::Finish(JpegScanner* scanner) {
+ jpeg_info_.SetSegmentDataRanges(
+ JpegXmpInfo::kGDepthInfoType,
+ gdepth_info_builder_.GetPropertySegmentRanges());
+ jpeg_info_.SetSegmentDataRanges(
+ JpegXmpInfo::kGImageInfoType,
+ gimage_info_builder_.GetPropertySegmentRanges());
+}
+
+bool JpegInfoBuilder::HasAppleDepth() const {
+ if (image_count_ > 1 && image_mpf_count_[0]) {
+ for (size_t image = 1; image < image_xmp_apple_depth_count_.size();
+ ++image) {
+ if (image_xmp_apple_depth_count_[image]) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+bool JpegInfoBuilder::HasAppleMatte() const {
+ if (image_count_ > 1 && image_mpf_count_[0]) {
+ for (size_t image = 1; image < image_xmp_apple_matte_count_.size();
+ ++image) {
+ if (image_xmp_apple_matte_count_[image]) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+bool JpegInfoBuilder::IsPrimaryXmpSegment(const JpegSegment& segment) const {
+ size_t location = segment.GetPayloadDataLocation();
+ return segment.BytesAtLocationStartWith(location, kXmpId);
+}
+
+bool JpegInfoBuilder::IsExtendedXmpSegment(const JpegSegment& segment) const {
+ size_t location = segment.GetPayloadDataLocation();
+ return segment.BytesAtLocationStartWith(location, kXmpExtendedId);
+}
+
+bool JpegInfoBuilder::IsMpfSegment(const JpegSegment& segment) const {
+ size_t payload_data_location = segment.GetPayloadDataLocation();
+ return segment.BytesAtLocationStartWith(payload_data_location, kMpf);
+}
+
+bool JpegInfoBuilder::IsExifSegment(const JpegSegment& segment) const {
+ size_t payload_data_location = segment.GetPayloadDataLocation();
+ return segment.BytesAtLocationStartWith(payload_data_location, kExif);
+}
+
+bool JpegInfoBuilder::IsJfifSegment(const JpegSegment& segment) const {
+ size_t payload_data_location = segment.GetPayloadDataLocation();
+ return segment.BytesAtLocationStartWith(payload_data_location, kJfif);
+}
+
+void JpegInfoBuilder::MaybeCaptureSegmentBytes(const std::string& type,
+ const JpegSegment& segment,
+ std::vector<Byte>* bytes) const {
+ if (capture_segment_bytes_types_.count(type) == 0) {
+ return;
+ }
+ bytes->clear();
+ bytes->reserve(segment.GetLength());
+ size_t segment_begin = segment.GetBegin();
+ size_t segment_end = segment.GetEnd();
+ for (size_t location = segment_begin; location < segment_end; ++location) {
+ ValidatedByte validated_byte = segment.GetValidatedByte(location);
+ if (!validated_byte.is_valid) {
+ bytes->clear();
+ return;
+ }
+ bytes->emplace_back(validated_byte.value);
+ }
+}
+
+bool JpegInfoBuilder::HasMatchingExtendedXmpGuid(
+ const JpegSegment& segment) const {
+ if (primary_xmp_guid_.empty()) {
+ return false;
+ }
+ if (segment.GetLength() <= kXmpExtendedHeaderSize) {
+ return false;
+ }
+ size_t start = segment.GetPayloadDataLocation() + sizeof(kXmpExtendedId);
+ return segment.BytesAtLocationStartWith(start, primary_xmp_guid_.c_str());
+}
+
+bool JpegInfoBuilder::HasId(const JpegSegment& segment, const char* id) const {
+ return segment.BytesAtLocationContain(segment.GetPayloadDataLocation(), id);
+}
+
+void JpegInfoBuilder::SetPrimaryXmpGuid(const JpegSegment& segment) {
+ primary_xmp_guid_ = segment.ExtractXmpPropertyValue(
+ segment.GetPayloadDataLocation(), kXmpHasExtendedId);
+}
+
+void JpegInfoBuilder::SetXmpMimeType(const JpegSegment& segment,
+ JpegXmpInfo::Type xmp_info_type) {
+ string property_name = JpegXmpInfo::GetMimePropertyName(xmp_info_type);
+ jpeg_info_.SetMimeType(xmp_info_type, segment.ExtractXmpPropertyValue(
+ segment.GetPayloadDataLocation(),
+ property_name.c_str()));
+}
+
+} // namespace image_io
+} // namespace photos_editing_formats
diff --git a/src/jpeg/jpeg_marker.cc b/src/jpeg/jpeg_marker.cc
new file mode 100644
index 0000000..1afe5d9
--- /dev/null
+++ b/src/jpeg/jpeg_marker.cc
@@ -0,0 +1,98 @@
+#include "image_io/jpeg/jpeg_marker.h"
+
+#include <iomanip>
+#include <limits>
+#include <sstream>
+#include <string>
+#include <vector>
+
+namespace photos_editing_formats {
+namespace image_io {
+
+using std::string;
+using std::stringstream;
+using std::vector;
+
+// Storage for class (static) data members.
+const size_t JpegMarker::kLength; // = 2;
+const size_t JpegMarker::kTypeOffset; // = 1;
+const Byte JpegMarker::kZERO; // = 0x00;
+const Byte JpegMarker::kStart; // = 0xFF;
+const Byte JpegMarker::kSOS; // = 0xDA;
+const Byte JpegMarker::kSOI; // = 0xD8;
+const Byte JpegMarker::kEOI; // = 0xD9;
+const Byte JpegMarker::kAPP0; // = 0xE0;
+const Byte JpegMarker::kAPP1; // = 0xE1;
+const Byte JpegMarker::kAPP2; // = 0xE2;
+const Byte JpegMarker::kFILL; // = 0xFF;
+
+const std::string JpegMarker::GetName() const {
+ switch (type_) {
+ case 0x01:
+ return "TEM";
+ case 0xC4:
+ return "DHT";
+ case 0xC8:
+ return "JPG";
+ case 0xCC:
+ return "DAC";
+ case JpegMarker::kSOI:
+ return"SOI";
+ case JpegMarker::kEOI:
+ return "EOI";
+ case JpegMarker::kSOS:
+ return "SOS";
+ case 0xDB:
+ return "DQT";
+ case 0xDC:
+ return "DNL";
+ case 0xDD:
+ return "DRI";
+ case 0xDE:
+ return "DHP";
+ case 0xDF:
+ return "EXP";
+ case 0xFE:
+ return "COM";
+ }
+
+ stringstream name_stream;
+
+ if (0xC0 <= type_ && type_ <= 0xC0+15) {
+ name_stream << "SOF" << type_-0xC0;
+ return name_stream.str();
+ }
+ if (0xD0 <= type_ && type_ <= 0xD0+7) {
+ name_stream << "RST" << type_-0xD0;
+ return name_stream.str();
+ }
+ if (JpegMarker::kAPP0 <= type_ && type_ <= JpegMarker::kAPP0+15) {
+ name_stream << "APP" << type_-JpegMarker::kAPP0;
+ return name_stream.str();
+ }
+ if (0xF0 <= type_ && type_ <= 0xF0+13) {
+ name_stream << "JPG" << type_-0xF0;
+ return name_stream.str();
+ }
+ return GetHexString("0x");
+}
+
+const std::string JpegMarker::GetHexString(const std::string& prefix) const {
+ stringstream name_stream;
+ name_stream << prefix << std::hex << std::uppercase << std::setfill('0')
+ << std::setw(2) << static_cast<int>(type_);
+ return name_stream.str();
+}
+
+bool JpegMarker::HasVariablePayloadSize() const {
+ return type_ != 0x00 && type_ != 0x01 && (type_ < 0xD0 || type_ > 0xD7) &&
+ type_ != JpegMarker::kSOI && type_ != JpegMarker::kEOI &&
+ type_ != 0xFF;
+}
+
+bool JpegMarker::IsEntropySegmentDelimiter() const {
+ return (type_ == kSOS || (type_ >= 0xD0 && type_ <= 0xD7));
+}
+
+} // namespace image_io
+} // namespace photos_editing_formats
diff --git a/src/jpeg/jpeg_scanner.cc b/src/jpeg/jpeg_scanner.cc
new file mode 100644
index 0000000..85426b2
--- /dev/null
+++ b/src/jpeg/jpeg_scanner.cc
@@ -0,0 +1,120 @@
+#include "image_io/jpeg/jpeg_scanner.h"
+
+#include <sstream>
+
+#include "image_io/base/message_handler.h"
+#include "image_io/jpeg/jpeg_segment.h"
+
+namespace photos_editing_formats {
+namespace image_io {
+
+using std::stringstream;
+
+/// The minimum size for the DataSegments requested from the DataSource. Using
+/// this value will guarentee that a JpegSegment will occupy at most two
+/// DataSegments.
+const size_t kMinBufferDataRequestSize = 0x10000;
+
+void JpegScanner::Run(DataSource* data_source,
+ JpegSegmentProcessor* segment_processor) {
+ if (data_source_) {
+ // The Run() function is already active.
+ return;
+ }
+ data_source_ = data_source;
+ segment_processor_ = segment_processor;
+ current_location_ = 0;
+ done_ = false;
+ has_error_ = false;
+ data_source_->Reset();
+ current_segment_ = data_source_->GetDataSegment(current_location_,
+ kMinBufferDataRequestSize);
+ segment_processor_->Start(this);
+ FindAndProcessSegments();
+ segment_processor_->Finish(this);
+ data_source_ = nullptr;
+ segment_processor_ = nullptr;
+ current_segment_.reset();
+ next_segment_.reset();
+}
+
+void JpegScanner::FindAndProcessSegments() {
+ while (!IsDone() && !HasError()) {
+ size_t begin_segment_location =
+ current_segment_->Find(current_location_, JpegMarker::kStart);
+ if (begin_segment_location == current_segment_->GetEnd()) {
+ GetNextSegment();
+ if (next_segment_) {
+ current_location_ =
+ std::max(current_location_, next_segment_->GetBegin());
+ current_segment_ = next_segment_;
+ next_segment_.reset();
+ continue;
+ }
+ SetDone();
+ break;
+ }
+ size_t payload_size = 0;
+ JpegMarker marker(
+ GetByte(begin_segment_location + JpegMarker::kTypeOffset));
+ if (marker.IsValid() && !HasError()) {
+ payload_size = GetPayloadSize(marker, begin_segment_location);
+ if (marker.IsValid() && interesting_marker_flags_[marker.GetType()]) {
+ size_t end_segment_location =
+ begin_segment_location + JpegMarker::kLength + payload_size;
+ GetByte(end_segment_location - 1);
+ if (!HasError()) {
+ JpegSegment segment(begin_segment_location, end_segment_location,
+ current_segment_.get(), next_segment_.get());
+ segment_processor_->Process(this, segment);
+ }
+ }
+ }
+ current_location_ =
+ begin_segment_location + JpegMarker::kLength + payload_size;
+ }
+}
+
+size_t JpegScanner::GetPayloadSize(const JpegMarker& marker,
+ size_t begin_location) {
+ if (marker.HasVariablePayloadSize()) {
+ return (GetByte(begin_location + JpegMarker::kLength) << 8) |
+ GetByte(begin_location + JpegMarker::kLength + 1);
+ } else {
+ return 0;
+ }
+}
+
+ValidatedByte JpegScanner::GetValidatedByte(size_t location) {
+ if (current_segment_->Contains(location)) {
+ return current_segment_->GetValidatedByte(location);
+ }
+ GetNextSegment();
+ if (next_segment_ && next_segment_->Contains(location)) {
+ return next_segment_->GetValidatedByte(location);
+ }
+ stringstream sstream;
+ sstream << location;
+ MessageHandler::Get()->ReportMessage(Message::kPrematureEndOfDataError,
+ sstream.str());
+ return InvalidByte();
+}
+
+Byte JpegScanner::GetByte(size_t location) {
+ ValidatedByte validated_byte = GetValidatedByte(location);
+ if (validated_byte.is_valid) {
+ return validated_byte.value;
+ }
+ has_error_ = true;
+ return 0;
+}
+
+void JpegScanner::GetNextSegment() {
+ if (!next_segment_ && current_segment_) {
+ next_segment_ = data_source_->GetDataSegment(current_segment_->GetEnd(),
+ kMinBufferDataRequestSize);
+ }
+}
+
+} // namespace image_io
+} // namespace photos_editing_formats
diff --git a/src/jpeg/jpeg_segment.cc b/src/jpeg/jpeg_segment.cc
new file mode 100644
index 0000000..61a9c97
--- /dev/null
+++ b/src/jpeg/jpeg_segment.cc
@@ -0,0 +1,174 @@
+#include "image_io/jpeg/jpeg_segment.h"
+
+#include <cctype>
+#include <iomanip>
+#include <sstream>
+#include <string>
+
+namespace photos_editing_formats {
+namespace image_io {
+
+using std::string;
+using std::stringstream;
+
+/// Finds the character allowing it to be preceded by whitespace characters.
+/// @param segment The segment in which to look for the character.
+/// @param start_location The location at which to start looking.
+/// @param value The character value to look for.
+/// @return The location of the character or segment.GetEnd() if not found,
+/// of non whitespace characters are found first.
+static size_t SkipWhiteSpaceFindChar(const JpegSegment& segment,
+ size_t start_location, char value) {
+ for (size_t location = start_location; location < segment.GetEnd();
+ ++location) {
+ ValidatedByte validated_byte = segment.GetValidatedByte(location);
+ if (!validated_byte.is_valid) {
+ return segment.GetEnd();
+ }
+ if (validated_byte.value == Byte(value)) {
+ return location;
+ }
+ if (!std::isspace(validated_byte.value)) {
+ return segment.GetEnd();
+ }
+ }
+ return segment.GetEnd();
+}
+
+size_t JpegSegment::GetVariablePayloadSize() const {
+ if (!GetMarker().HasVariablePayloadSize()) {
+ return 0;
+ }
+ size_t payload_location = GetPayloadLocation();
+ ValidatedByte hi = GetValidatedByte(payload_location);
+ ValidatedByte lo = GetValidatedByte(payload_location + 1);
+ if (!hi.is_valid || !lo.is_valid) {
+ return 0;
+ }
+ return static_cast<size_t>(hi.value) << 8 | static_cast<size_t>(lo.value);
+}
+
+bool JpegSegment::BytesAtLocationStartWith(size_t location,
+ const char* str) const {
+ while (*str && Contains(location)) {
+ ValidatedByte validated_byte = GetValidatedByte(location++);
+ if (!validated_byte.is_valid || Byte(*str++) != validated_byte.value) {
+ return false;
+ }
+ }
+ return *str == 0;
+}
+
+bool JpegSegment::BytesAtLocationContain(size_t location,
+ const char* str) const {
+ return Find(location, str) != GetEnd();
+}
+
+size_t JpegSegment::Find(size_t location, const char* str) const {
+ Byte byte0 = static_cast<Byte>(*str);
+ while ((location = Find(location, byte0)) < GetEnd()) {
+ if (BytesAtLocationStartWith(location, str)) {
+ return location;
+ }
+ ++location;
+ }
+ return GetEnd();
+}
+
+size_t JpegSegment::Find(size_t start_location, Byte value) const {
+ if (!begin_segment_ && !end_segment_) {
+ return GetEnd();
+ }
+ size_t value_location = GetEnd();
+ if (begin_segment_ && !end_segment_) {
+ value_location = begin_segment_->Find(start_location, value);
+ } else {
+ value_location =
+ DataSegment::Find(start_location, value, begin_segment_, end_segment_);
+ }
+ return Contains(value_location) ? value_location : GetEnd();
+}
+
+std::string JpegSegment::ExtractXmpPropertyValue(
+ size_t start_location, const char* property_name) const {
+ size_t begin_value_location =
+ FindXmpPropertyValueBegin(start_location, property_name);
+ if (begin_value_location != GetEnd()) {
+ size_t end_value_location = FindXmpPropertyValueEnd(begin_value_location);
+ if (end_value_location != GetEnd()) {
+ DataRange data_range(begin_value_location, end_value_location);
+ return ExtractString(data_range);
+ }
+ }
+ return "";
+}
+
+size_t JpegSegment::FindXmpPropertyValueBegin(size_t start_location,
+ const char* property_name) const {
+ size_t property_location = Find(start_location, property_name);
+ if (property_location != GetEnd()) {
+ size_t equal_location = SkipWhiteSpaceFindChar(
+ *this, property_location + strlen(property_name), '=');
+ if (equal_location != GetEnd()) {
+ size_t quote_location =
+ SkipWhiteSpaceFindChar(*this, equal_location + 1, '"');
+ if (quote_location != GetEnd()) {
+ return quote_location + 1;
+ }
+ }
+ }
+ return GetEnd();
+}
+
+size_t JpegSegment::FindXmpPropertyValueEnd(size_t start_location) const {
+ return Find(start_location, Byte('"'));
+}
+
+std::string JpegSegment::ExtractString(const DataRange& data_range) const {
+ std::string value;
+ if (Contains(data_range.GetBegin()) && data_range.GetEnd() <= GetEnd()) {
+ size_t start_location = data_range.GetBegin();
+ size_t length = data_range.GetLength();
+ value.resize(length, ' ');
+ for (size_t index = 0; index < length; ++index) {
+ ValidatedByte validated_byte = GetValidatedByte(start_location + index);
+ if (!validated_byte.value) { // Invalid bytes have a zero value.
+ value.resize(0);
+ break;
+ }
+ value[index] = static_cast<char>(validated_byte.value);
+ }
+ }
+ return value;
+}
+
+void JpegSegment::GetPayloadHexDumpStrings(size_t byte_count,
+ std::string* hex_string,
+ std::string* ascii_string) const {
+ stringstream ascii_stream;
+ stringstream hex_stream;
+ hex_stream << std::hex << std::uppercase;
+
+ size_t dump_count = GetMarker().IsEntropySegmentDelimiter()
+ ? byte_count
+ : std::min(byte_count, GetLength() - 2);
+ for (size_t index = 0; index < dump_count; ++index) {
+ ValidatedByte payload_byte = GetValidatedByte(GetPayloadLocation() + index);
+ if (!payload_byte.is_valid) {
+ break;
+ }
+ Byte value = payload_byte.value;
+ hex_stream << std::setfill('0') << std::setw(2) << static_cast<int>(value);
+ ascii_stream << (isprint(value) ? static_cast<char>(value) : '.');
+ }
+ size_t current_count = ascii_stream.str().length();
+ for (size_t index = current_count; index < byte_count; ++index) {
+ hex_stream << " ";
+ ascii_stream << ".";
+ }
+ *hex_string = hex_stream.str();
+ *ascii_string = ascii_stream.str();
+}
+
+} // namespace image_io
+} // namespace photos_editing_formats
diff --git a/src/jpeg/jpeg_segment_builder.cc b/src/jpeg/jpeg_segment_builder.cc
new file mode 100644
index 0000000..aafb525
--- /dev/null
+++ b/src/jpeg/jpeg_segment_builder.cc
@@ -0,0 +1,160 @@
+#include "image_io/jpeg/jpeg_segment_builder.h"
+
+#include "image_io/jpeg/jpeg_marker.h"
+
+namespace photos_editing_formats {
+namespace image_io {
+
+using std::string;
+
+// The strings needed to build the xml data associated with XMP data. See
+// https://wwwimages2.adobe.com/content/dam/acom/en/devnet/xmp/pdfs/
+// XMP%20SDK%20Release%20cc-2016-08/XMPSpecificationPart1.pdf
+const char kXmpMetaPrefix[] = "<x:xmpmeta xmlns:x=\"adobe:ns:meta/\">";
+const char kXmpMetaSuffix[] = "</x:xmpmeta>";
+const char kRdfPrefix[] =
+ "<rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\""
+ "xmlns:xmp=\"http://ns.adobe.com/xap/1.0/\">";
+const char kRdfSuffix[] = "</rdf:RDF>";
+const char kRdfDescriptionPrefix[] = "<rdf:Description rdf:about=\"\"";
+const char kRdfDescriptionSuffix[] = "/>";
+
+bool JpegSegmentBuilder::SetPayloadSize(ByteBuffer* byte_buffer) {
+ std::uint16_t size = byte_buffer->GetSize();
+ if (size == byte_buffer->GetSize() && size >= 4) {
+ return byte_buffer->SetBigEndianValue(2, size - 2);
+ }
+ return false;
+}
+
+string JpegSegmentBuilder::GetByteDataValues() const {
+ string values;
+ for (const auto& byte_datum : byte_data_) {
+ if (!byte_datum.IsValid()) {
+ return "";
+ }
+ values += byte_datum.GetValue();
+ if (byte_datum.GetType() == ByteData::kAscii0) {
+ values.append(1, 0);
+ }
+ }
+ return values;
+}
+
+void JpegSegmentBuilder::AddMarkerAndSize(Byte marker_type, size_t size) {
+ JpegMarker marker(marker_type);
+ string hex_string = marker.GetHexString("FF");
+ if (marker.HasVariablePayloadSize()) {
+ hex_string += ByteData::Byte2Hex((size >> 8) & 0xFF);
+ hex_string += ByteData::Byte2Hex(size & 0xFF);
+ }
+ byte_data_.emplace_back(ByteData::kHex, hex_string);
+}
+
+size_t JpegSegmentBuilder::AddMarkerAndSizePlaceholder(Byte marker_type) {
+ JpegMarker marker(marker_type);
+ string hex_string = marker.GetHexString("FF");
+ if (marker.HasVariablePayloadSize()) {
+ hex_string += "0000";
+ }
+ byte_data_.emplace_back(ByteData::kHex, hex_string);
+ return byte_data_.size() - 1;
+}
+
+bool JpegSegmentBuilder::ReplaceSizePlaceholder(size_t index, size_t size) {
+ if (index >= byte_data_.size() || size < 2 || size > 0xFFFF) {
+ return false;
+ }
+ const ByteData& byte_datum = byte_data_[index];
+ if (byte_datum.GetType() != ByteData::kHex) {
+ return false;
+ }
+ string value = byte_datum.GetValue();
+ if (value.length() < 4) {
+ return false;
+ }
+ Byte flag, type;
+ if (!ByteData::Hex2Byte(value[0], value[1], &flag) ||
+ !ByteData::Hex2Byte(value[2], value[3], &type)) {
+ return false;
+ }
+ JpegMarker marker(type);
+ if (flag != JpegMarker::kStart || !marker.IsValid() ||
+ !marker.HasVariablePayloadSize()) {
+ return false;
+ }
+ value.replace(2, 2, ByteData::Byte2Hex((size >> 8) & 0xFF));
+ value.replace(4, 2, ByteData::Byte2Hex(size & 0xFF));
+ byte_data_[index] = ByteData(ByteData::kHex, value);
+ return true;
+}
+
+void JpegSegmentBuilder::AddExtendedXmpHeader(const std::string& xmp_guid) {
+ string guid_value(xmp_guid);
+ guid_value.resize(kXmpGuidSize, '0');
+ byte_data_.emplace_back(ByteData::kAscii0, kXmpExtendedId);
+ byte_data_.emplace_back(ByteData::kAscii, guid_value);
+ byte_data_.emplace_back(ByteData::kAscii, string(8, '0'));
+}
+
+void JpegSegmentBuilder::AddXmpMetaPrefix() {
+ byte_data_.emplace_back(ByteData::kAscii, kXmpMetaPrefix);
+}
+
+void JpegSegmentBuilder::AddXmpMetaSuffix() {
+ byte_data_.emplace_back(ByteData::kAscii, kXmpMetaSuffix);
+}
+
+void JpegSegmentBuilder::AddRdfPrefix() {
+ byte_data_.emplace_back(ByteData::kAscii, kRdfPrefix);
+}
+
+void JpegSegmentBuilder::AddRdfSuffix() {
+ byte_data_.emplace_back(ByteData::kAscii, kRdfSuffix);
+}
+
+void JpegSegmentBuilder::AddRdfDescriptionPrefix() {
+ byte_data_.emplace_back(ByteData::kAscii, kRdfDescriptionPrefix);
+}
+
+void JpegSegmentBuilder::AddRdfDescriptionSuffix() {
+ byte_data_.emplace_back(ByteData::kAscii, kRdfDescriptionSuffix);
+}
+
+void JpegSegmentBuilder::AddXmpPropertyPrefix(
+ const std::string& property_name) {
+ string property_name_equals_quote = property_name + "=\"";
+ byte_data_.emplace_back(ByteData::kAscii, property_name_equals_quote);
+}
+
+void JpegSegmentBuilder::AddXmpPropertySuffix() {
+ byte_data_.emplace_back(ByteData::kAscii, "\"");
+}
+
+void JpegSegmentBuilder::AddXmpPropertyNameAndValue(
+ const std::string& property_name, const std::string& property_value) {
+ AddXmpPropertyPrefix(property_name);
+ byte_data_.emplace_back(ByteData::kAscii, property_value);
+ AddXmpPropertySuffix();
+}
+
+void JpegSegmentBuilder::AddApp1XmpMarkerAndXmpExtendedHeader(
+ const std::string& xmp_guid) {
+ AddMarkerAndSizePlaceholder(JpegMarker::kAPP1);
+ AddExtendedXmpHeader(xmp_guid);
+}
+
+void JpegSegmentBuilder::AddXmpAndRdfPrefixes() {
+ AddXmpMetaPrefix();
+ AddRdfPrefix();
+ AddRdfDescriptionPrefix();
+}
+
+void JpegSegmentBuilder::AddXmpAndRdfSuffixes() {
+ AddRdfDescriptionSuffix();
+ AddRdfSuffix();
+ AddXmpMetaSuffix();
+}
+
+} // namespace image_io
+} // namespace photos_editing_formats
diff --git a/src/jpeg/jpeg_segment_lister.cc b/src/jpeg/jpeg_segment_lister.cc
new file mode 100644
index 0000000..ffdd3f2
--- /dev/null
+++ b/src/jpeg/jpeg_segment_lister.cc
@@ -0,0 +1,158 @@
+#include "image_io/jpeg/jpeg_segment_lister.h"
+
+#include <iomanip>
+#include <sstream>
+#include <string>
+
+#include "image_io/jpeg/jpeg_marker.h"
+#include "image_io/jpeg/jpeg_scanner.h"
+#include "image_io/jpeg/jpeg_segment.h"
+
+namespace photos_editing_formats {
+namespace image_io {
+
+/// The width of the type column.
+constexpr size_t kTypeWidth = 5;
+
+/// The width of the number columns.
+constexpr size_t kNumWidth = 12;
+
+/// The number of bytes to dump from each segment.
+constexpr size_t kDumpCount = 16;
+
+/// The width of the ascii dump column, including the surrounding [] brackets.
+constexpr size_t kAscWidth = kDumpCount + 2;
+
+/// The width of the hex dump column, including the surrounding [] brackets.
+constexpr size_t kHexWidth = 2 * kDumpCount + 2;
+
+using std::string;
+using std::stringstream;
+
+namespace {
+
+/// @param value The value to convert to a string.
+/// @return The value paraemter as a string of length kNumWidth.
+string Size2String(size_t value) {
+ stringstream stream;
+ stream << std::setw(kNumWidth) << std::right << value;
+ return stream.str();
+}
+
+/// @param value The value to convert to a hex string.
+/// @return The value paraemter as a hex string of length kNumWidth.
+string Size2HexString(size_t value) {
+ stringstream stream;
+ stream << std::hex << std::uppercase << std::setw(kNumWidth) << std::right
+ << value;
+ return stream.str();
+}
+
+/// @param str The string to add brackets to.
+/// @return The str value enclosed by square brackets.
+string BracketedString(const string& str) {
+ stringstream stream;
+ stream << '[' << str << ']';
+ return stream.str();
+}
+
+/// @param str The string to center.
+/// @param width The width to center the string in.
+/// @return A string with leading/trailing spaces added so that it is centered.
+string CenteredString(const string& str, size_t width) {
+ if (str.length() >= width) {
+ return str;
+ }
+ size_t spacing = width - str.length();
+ size_t leading = spacing / 2;
+ size_t trailing = spacing - leading;
+ return string(leading, ' ') + str + string(trailing, ' ');
+}
+
+/// @param type The type value of the segment. If this value is empty, then a
+/// divider line with dashes is created.
+/// @param begin The begin value of the segment.
+/// @param count The count (size) of the segment.
+/// @param hex_string The hex dump string of the segment.
+/// @param asc_string The ascii dump string of the segment.
+/// @return A line with the various parameters properly spaced.
+string SegmentLine(string type, string begin, string count, string hex_string,
+ string asc_string) {
+ if (type.empty()) {
+ type = string(kTypeWidth, '-');
+ begin = count = string(kNumWidth, '-');
+ hex_string = string(kHexWidth, '-');
+ asc_string = string(kAscWidth, '-');
+ }
+ stringstream line_stream;
+ line_stream << std::setw(kTypeWidth) << std::left << type << " "
+ << std::setw(kNumWidth) << std::right << begin << " "
+ << std::setw(kNumWidth) << std::right << count << " "
+ << std::setw(kHexWidth) << std::right << hex_string << " "
+ << std::setw(kAscWidth) << std::right << asc_string;
+ return line_stream.str();
+}
+
+/// @param type The type value of the summary. If this value is empty, then a
+/// divider line with dashes is created.
+/// @param count The number of the segments of the given type.
+/// @return A line with the parameters properly spaced.
+string SummaryLine(string type, string count) {
+ if (type.empty()) {
+ type = string(kTypeWidth, '-');
+ count = string(kNumWidth, '-');
+ }
+ stringstream line_stream;
+ line_stream << std::setw(kTypeWidth) << std::left << type << " "
+ << std::setw(kNumWidth) << std::right << count;
+ return line_stream.str();
+}
+
+} // namespace
+
+JpegSegmentLister::JpegSegmentLister()
+ : marker_type_counts_(kJpegMarkerArraySize, 0) {}
+
+void JpegSegmentLister::Start(JpegScanner* scanner) {
+ scanner->UpdateInterestingMarkerFlags(JpegMarker::Flags().set());
+ string divider_line = SegmentLine("", "", "", "", "");
+ lines_.push_back(divider_line);
+ lines_.push_back(SegmentLine("Type", "Offset", "Payload Size",
+ CenteredString("Hex Payload", kHexWidth),
+ CenteredString("Ascii Payload", kAscWidth)));
+ lines_.push_back(divider_line);
+}
+
+void JpegSegmentLister::Process(JpegScanner* scanner,
+ const JpegSegment& segment) {
+ JpegMarker marker = segment.GetMarker();
+ string hex_payload, ascii_payload;
+ ++marker_type_counts_[marker.GetType()];
+ segment.GetPayloadHexDumpStrings(kDumpCount, &hex_payload, &ascii_payload);
+ lines_.push_back(SegmentLine(
+ marker.GetName(), Size2HexString(segment.GetBegin()),
+ Size2HexString(segment.GetEnd() - segment.GetBegin() - 2),
+ BracketedString(hex_payload), BracketedString(ascii_payload)));
+}
+
+void JpegSegmentLister::Finish(JpegScanner* scanner) {
+ lines_.push_back("");
+ string divider_line = SummaryLine("", "");
+ lines_.push_back(divider_line);
+ lines_.push_back(SummaryLine("Type", "Count"));
+ lines_.push_back(divider_line);
+ int total_segments = 0;
+ for (int type = 0; type < kJpegMarkerArraySize; ++type) {
+ int count = marker_type_counts_[type];
+ if (count) {
+ total_segments += count;
+ lines_.push_back(
+ SummaryLine(JpegMarker(type).GetName(), Size2String(count)));
+ }
+ }
+ lines_.push_back(divider_line);
+ lines_.push_back(SummaryLine("TOTAL", Size2String(total_segments)));
+}
+
+} // namespace image_io
+} // namespace photos_editing_formats
diff --git a/src/jpeg/jpeg_xmp_data_extractor.cc b/src/jpeg/jpeg_xmp_data_extractor.cc
new file mode 100644
index 0000000..f59dea5
--- /dev/null
+++ b/src/jpeg/jpeg_xmp_data_extractor.cc
@@ -0,0 +1,103 @@
+#include "image_io/jpeg/jpeg_xmp_data_extractor.h"
+
+#include <iomanip>
+#include <sstream>
+#include <string>
+
+#include "image_io/base/message_handler.h"
+#include "image_io/jpeg/jpeg_marker.h"
+#include "image_io/jpeg/jpeg_segment.h"
+
+/// Set this flag to 1 for debugging output.
+#define PHOTOS_EDITING_FORMATS_IMAGE_IO_JPEG_JPEG_XMP_DATA_EXTRACTOR_DEBUG 0
+
+namespace photos_editing_formats {
+namespace image_io {
+
+using std::string;
+using std::stringstream;
+
+void JpegXmpDataExtractor::StartTransfer() {
+ data_destination_->StartTransfer();
+}
+
+DataDestination::TransferStatus JpegXmpDataExtractor::Transfer(
+ const DataRange& transfer_range, const DataSegment& data_segment) {
+ if (HasError()) {
+ return kTransferError;
+ }
+#if PHOTOS_EDITING_FORMATS_IMAGE_IO_JPEG_JPEG_XMP_DATA_EXTRACTOR_DEBUG
+ stringstream sstream1;
+ sstream1 << "Segment " << segment_index_ << " of " << last_segment_index_
+ << " - data range from " << transfer_range.GetBegin() << " to "
+ << transfer_range.GetEnd();
+ MessageHandler::Get()->ReportMessage(Message::kStatus, sstream1.str());
+#endif // PHOTOS_EDITING_FORMATS_IMAGE_IO_JPEG_JPEG_XMP_DATA_EXTRACTOR_DEBUG
+ const size_t xmp_header_length = JpegMarker::kLength +
+ JpegSegment::kVariablePayloadDataOffset +
+ kXmpExtendedHeaderSize;
+ size_t encoded_data_begin = transfer_range.GetBegin() + xmp_header_length;
+ size_t xmp_data_begin = encoded_data_begin;
+ size_t xmp_data_end = transfer_range.GetEnd();
+ if (segment_index_ == 0) {
+ string property_name = JpegXmpInfo::GetDataPropertyName(xmp_info_type_);
+ size_t gdepth_data_location = data_segment.Find(
+ encoded_data_begin, property_name.c_str(), property_name.length());
+ if (gdepth_data_location != transfer_range.GetEnd()) {
+ size_t quote_location = data_segment.Find(gdepth_data_location, '"');
+ if (quote_location != transfer_range.GetEnd()) {
+ xmp_data_begin = quote_location + 1;
+ }
+ }
+ if (xmp_data_begin == encoded_data_begin) {
+ MessageHandler::Get()->ReportMessage(Message::kStringNotFoundError,
+ property_name + "=\"");
+ has_error_ = true;
+ return kTransferError;
+ }
+ }
+ if (segment_index_ == last_segment_index_) {
+ xmp_data_end = data_segment.Find(xmp_data_begin, '"');
+ if (xmp_data_end == transfer_range.GetEnd()) {
+ MessageHandler::Get()->ReportMessage(Message::kStringNotFoundError, "\"");
+ has_error_ = true;
+ return kTransferError;
+ }
+ }
+
+ DataRange xmp_data_range(xmp_data_begin, xmp_data_end);
+#if PHOTOS_EDITING_FORMATS_IMAGE_IO_JPEG_JPEG_XMP_DATA_EXTRACTOR_DEBUG
+ string strb((const char*)data_segment.GetBuffer(xmp_data_range.GetBegin()),
+ 50);
+ string stre((const char*)data_segment.GetBuffer(xmp_data_end - 50), 50);
+ stringstream sstream2;
+ sstream2 << " " << xmp_data_begin << ":" << xmp_data_end << " = "
+ << xmp_data_range.GetLength() << " bytes: [" << strb << "..." << stre
+ << "] - ";
+ MessageHandler::Get()->ReportMessage(Message::kStatus, sstream2.str());
+ for (size_t i = transfer_range.GetBegin(); i < data_segment.GetEnd();
+ i += 32) {
+ stringstream hex_stream, ascii_stream;
+ hex_stream << std::hex << std::setfill('0') << std::setw(2)
+ << std::uppercase;
+ for (size_t j = 0; j < 32 && (i + j) < data_segment.GetEnd(); ++j) {
+ Byte value = data_segment.GetValidatedByte(i + j).value;
+ hex_stream << " " << size_t(value);
+ ascii_stream << (isprint(value) ? static_cast<char>(value) : '.');
+ }
+ stringstream sstream3;
+ sstream3 << " * " << std::hex << std::setfill('0') << std::setw(8)
+ << std::uppercase << i;
+ sstream3 << ":" << hex_stream.str() << " [" << ascii_stream.str() << "]";
+ MessageHandler::Get()->ReportMessage(Message::kStatus, sstream3.str());
+ }
+#endif // PHOTOS_EDITING_FORMATS_IMAGE_IO_JPEG_JPEG_XMP_DATA_EXTRACTOR_DEBUG
+ return data_destination_->Transfer(xmp_data_range, data_segment);
+}
+
+void JpegXmpDataExtractor::FinishTransfer() {
+ data_destination_->FinishTransfer();
+}
+
+} // namespace image_io
+} // namespace photos_editing_formats
diff --git a/src/jpeg/jpeg_xmp_info.cc b/src/jpeg/jpeg_xmp_info.cc
new file mode 100644
index 0000000..e6ad8c6
--- /dev/null
+++ b/src/jpeg/jpeg_xmp_info.cc
@@ -0,0 +1,48 @@
+#include "image_io/jpeg/jpeg_xmp_info.h"
+
+namespace photos_editing_formats {
+namespace image_io {
+
+using std::string;
+using std::vector;
+
+const char kGDepthDataPropertyName[] = "GDepth:Data";
+const char kGImageDataPropertyName[] = "GImage:Data";
+const char kGDepthMimePropertyName[] = "GDepth:Mime";
+const char kGImageMimePropertyName[] = "GImage:Mime";
+
+void JpegXmpInfo::InitializeVector(vector<JpegXmpInfo>* xmp_info_vector) {
+ xmp_info_vector->clear();
+ xmp_info_vector->push_back(JpegXmpInfo(JpegXmpInfo::kGDepthInfoType));
+ xmp_info_vector->push_back(JpegXmpInfo(JpegXmpInfo::kGImageInfoType));
+}
+
+string JpegXmpInfo::GetIdentifier(Type jpeg_xmp_info_type) {
+ switch (jpeg_xmp_info_type) {
+ case kGDepthInfoType:
+ return kXmpGDepthV1Id;
+ case kGImageInfoType:
+ return kXmpGImageV1Id;
+ }
+}
+
+string JpegXmpInfo::GetDataPropertyName(Type jpeg_xmp_info_type) {
+ switch (jpeg_xmp_info_type) {
+ case kGDepthInfoType:
+ return kGDepthDataPropertyName;
+ case kGImageInfoType:
+ return kGImageDataPropertyName;
+ }
+}
+
+string JpegXmpInfo::GetMimePropertyName(Type jpeg_xmp_info_type) {
+ switch (jpeg_xmp_info_type) {
+ case kGDepthInfoType:
+ return kGDepthMimePropertyName;
+ case kGImageInfoType:
+ return kGImageMimePropertyName;
+ }
+}
+
+} // namespace image_io
+} // namespace photos_editing_formats
diff --git a/src/jpeg/jpeg_xmp_info_builder.cc b/src/jpeg/jpeg_xmp_info_builder.cc
new file mode 100644
index 0000000..c899f93
--- /dev/null
+++ b/src/jpeg/jpeg_xmp_info_builder.cc
@@ -0,0 +1,42 @@
+#include "image_io/jpeg/jpeg_xmp_info_builder.h"
+
+#include <string>
+
+namespace photos_editing_formats {
+namespace image_io {
+
+void JpegXmpInfoBuilder::ProcessSegment(const JpegSegment& segment) {
+ // If the property has not yet been found, look for it, and if found, add the
+ // segment's range to the vector of ranges.
+ size_t extended_xmp_data_begin =
+ segment.GetPayloadDataLocation() + kXmpExtendedHeaderSize;
+ size_t property_value_begin = extended_xmp_data_begin;
+ if (property_segment_ranges_.empty()) {
+ std::string property_name =
+ JpegXmpInfo::GetDataPropertyName(xmp_info_type_);
+ property_value_begin = segment.FindXmpPropertyValueBegin(
+ extended_xmp_data_begin, property_name.c_str());
+ if (property_value_begin != segment.GetEnd()) {
+ property_segment_ranges_.push_back(segment.GetDataRange());
+ }
+ } else if (!property_end_segment_range_.IsValid()) {
+ // The start of the property value was encountered in a previous segment -
+ // if the closing quote has not yet been found, then add the segment's range
+ // to the vector or ranges.
+ property_segment_ranges_.push_back(segment.GetDataRange());
+ }
+
+ // If the start of the property value has been found but the end has not, look
+ // for the end in this segment.
+ if (!property_segment_ranges_.empty() &&
+ !property_end_segment_range_.IsValid()) {
+ size_t property_value_end =
+ segment.FindXmpPropertyValueEnd(property_value_begin);
+ if (property_value_end != segment.GetEnd()) {
+ property_end_segment_range_ = segment.GetDataRange();
+ }
+ }
+}
+
+} // namespace image_io
+} // namespace photos_editing_formats