diff options
Diffstat (limited to 'src/jpeg')
-rw-r--r-- | src/jpeg/jpeg_apple_depth_builder.cc | 252 | ||||
-rw-r--r-- | src/jpeg/jpeg_image_extractor.cc | 110 | ||||
-rw-r--r-- | src/jpeg/jpeg_info_builder.cc | 232 | ||||
-rw-r--r-- | src/jpeg/jpeg_marker.cc | 98 | ||||
-rw-r--r-- | src/jpeg/jpeg_scanner.cc | 120 | ||||
-rw-r--r-- | src/jpeg/jpeg_segment.cc | 174 | ||||
-rw-r--r-- | src/jpeg/jpeg_segment_builder.cc | 160 | ||||
-rw-r--r-- | src/jpeg/jpeg_segment_lister.cc | 158 | ||||
-rw-r--r-- | src/jpeg/jpeg_xmp_data_extractor.cc | 103 | ||||
-rw-r--r-- | src/jpeg/jpeg_xmp_info.cc | 48 | ||||
-rw-r--r-- | src/jpeg/jpeg_xmp_info_builder.cc | 42 |
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 |