#include "image_io/jpeg/jpeg_apple_depth_builder.h" #include #include #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. /// @param message_handler For use when reporting messages. /// @return Whether the info was obtained successfully or not. bool GetJpegInfo(int image_limit, DataSource* data_source, JpegInfo* info, MessageHandler* message_handler) { JpegInfoBuilder info_builder; info_builder.SetImageLimit(image_limit); info_builder.SetCaptureSegmentBytes(kJfif); JpegScanner scanner(message_handler); 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()) { if (message_handler_) { message_handler_->ReportMessage(Message::kDecodingError, "Primary image data"); } return false; } if (!GetDepthImageData()) { if (message_handler_) { message_handler_->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, message_handler_)) { 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, message_handler_)) { 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 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; if (message_handler_) { std::stringstream ss; ss << "JpegAppleDepthBuilder:data source transferred " << bytes_transferred << " bytes instead of " << data_range.GetLength(); message_handler_->ReportMessage(Message::kInternalError, ss.str()); } } } return result == DataSource::kTransferDataSuccess; } } // namespace image_io } // namespace photos_editing_formats