blob: a45837ef978b4a46e94ee7e3cbcfb00ea42be248 [file] [log] [blame]
#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.
/// @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<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;
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