summaryrefslogtreecommitdiff
path: root/src/jpeg/jpeg_apple_depth_builder.cc
blob: ce83f9f8911faed05613547b499f8f040925de46 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
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