1 /* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
2
3 Licensed under the Apache License, Version 2.0 (the "License");
4 you may not use this file except in compliance with the License.
5 You may obtain a copy of the License at
6
7 http://www.apache.org/licenses/LICENSE-2.0
8
9 Unless required by applicable law or agreed to in writing, software
10 distributed under the License is distributed on an "AS IS" BASIS,
11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 See the License for the specific language governing permissions and
13 limitations under the License.
14 ==============================================================================*/
15
16 #include "tensorflow_lite_support/cc/task/vision/utils/frame_buffer_utils.h"
17
18 #include <algorithm>
19 #include <iterator>
20 #include <memory>
21 #include <string>
22 #include <utility>
23 #include <vector>
24
25 #include "absl/memory/memory.h"
26 #include "absl/status/status.h"
27 #include "absl/strings/str_format.h"
28 #include "tensorflow/lite/kernels/op_macros.h"
29 #include "tensorflow/lite/kernels/internal/compatibility.h"
30 #include "tensorflow_lite_support/cc/port/status_macros.h"
31 #include "tensorflow_lite_support/cc/task/vision/utils/frame_buffer_common_utils.h"
32 #include "tensorflow_lite_support/cc/task/vision/utils/libyuv_frame_buffer_utils.h"
33
34 namespace tflite {
35 namespace task {
36 namespace vision {
37
38 namespace {
39
40 // Exif grouping to help determine rotation and flipping neededs between
41 // different orientations.
42 constexpr int kExifGroup[] = {1, 6, 3, 8, 2, 5, 4, 7};
43 // Exif group size.
44 constexpr int kExifGroupSize = 4;
45
46 // Returns orientation position in Exif group.
GetOrientationIndex(FrameBuffer::Orientation orientation)47 static int GetOrientationIndex(FrameBuffer::Orientation orientation) {
48 const int* index = std::find(kExifGroup, kExifGroup + kExifGroupSize * 2,
49 static_cast<int>(orientation));
50 if (index < kExifGroup + kExifGroupSize * 2) {
51 return std::distance(kExifGroup, index);
52 }
53 return -1;
54 }
55
56 // Returns the coordinates of `box` respect to its containing image (dimension
57 // defined by `width` and `height`) orientation change. The `angle` is defined
58 // in counterclockwise degree in one of the values [0, 90, 180, 270].
59 //
60 // The below diagrams illustrate calling this method with 90 CCW degree.
61 //
62 // The [1]-[4] denotes image corners and 1 - 4 denotes the box corners. The *
63 // denotes the current origin.
64 //
65 // width
66 // [1]*----------------[2]
67 // | |
68 // | |
69 // | 1*-----2 | height
70 // | | box | |
71 // | 3------4 |
72 // [3]-----------------[4]
73 //
74 // When rotate the above image by 90 CCW degree, the origin also changes
75 // respects to its containing coordinate space.
76 //
77 // height
78 // [2]*----------[4]
79 // | |
80 // | 2*---4 |
81 // | |box | |
82 // | | | | width
83 // | 1----3 |
84 // | |
85 // | |
86 // | |
87 // [1]-----------[3]
88 //
89 // The origin is always defined by the top left corner. After rotation, the
90 // box origin changed from 1 to 2.
91 // The new box origin is (x:box.origin_y, y:width - (box.origin_x + box.width).
92 // The new box dimension is (w: box.height, h: box.width).
93 //
RotateBoundingBox(const BoundingBox & box,int angle,FrameBuffer::Dimension frame_dimension)94 static BoundingBox RotateBoundingBox(const BoundingBox& box, int angle,
95 FrameBuffer::Dimension frame_dimension) {
96 int rx = box.origin_x(), ry = box.origin_y(), rw = box.width(),
97 rh = box.height();
98 const int box_right_bound =
99 frame_dimension.width - (box.origin_x() + box.width());
100 const int box_bottom_bound =
101 frame_dimension.height - (box.origin_y() + box.height());
102 switch (angle) {
103 case 90:
104 rx = box.origin_y();
105 ry = box_right_bound;
106 using std::swap;
107 swap(rw, rh);
108 break;
109 case 180:
110 rx = box_right_bound;
111 ry = box_bottom_bound;
112 break;
113 case 270:
114 rx = box_bottom_bound;
115 ry = box.origin_x();
116 using std::swap;
117 swap(rw, rh);
118 break;
119 }
120 BoundingBox result;
121 result.set_origin_x(rx);
122 result.set_origin_y(ry);
123 result.set_width(rw);
124 result.set_height(rh);
125 return result;
126 }
127
128 // Returns the input coordinates with respect to its containing image (dimension
129 // defined by `width` and `height`) orientation change. The `angle` is defined
130 // in counterclockwise degree in one of the values [0, 90, 180, 270].
131 //
132 // See `RotateBoundingBox` above for more details.
RotateCoordinates(int from_x,int from_y,int angle,const FrameBuffer::Dimension & frame_dimension,int * to_x,int * to_y)133 static void RotateCoordinates(int from_x, int from_y, int angle,
134 const FrameBuffer::Dimension& frame_dimension,
135 int* to_x, int* to_y) {
136 switch (angle) {
137 case 0:
138 *to_x = from_x;
139 *to_y = from_y;
140 break;
141 case 90:
142 *to_x = from_y;
143 *to_y = frame_dimension.width - from_x - 1;
144 break;
145 case 180:
146 *to_x = frame_dimension.width - from_x - 1;
147 *to_y = frame_dimension.height - from_y - 1;
148 break;
149 case 270:
150 *to_x = frame_dimension.height - from_y - 1;
151 *to_y = from_x;
152 break;
153 }
154 }
155
156 } // namespace
157
GetBufferByteSize(FrameBuffer::Dimension dimension,FrameBuffer::Format format)158 int GetBufferByteSize(FrameBuffer::Dimension dimension,
159 FrameBuffer::Format format) {
160 return GetFrameBufferByteSize(dimension, format);
161 }
162
FrameBufferUtils(ProcessEngine engine)163 FrameBufferUtils::FrameBufferUtils(ProcessEngine engine) {
164 switch (engine) {
165 case ProcessEngine::kLibyuv:
166 utils_ = absl::make_unique<LibyuvFrameBufferUtils>();
167 break;
168 default:
169 TF_LITE_FATAL(
170 absl::StrFormat("Unexpected ProcessEngine: %d.", engine).c_str());
171 }
172 }
173
OrientBoundingBox(const BoundingBox & from_box,FrameBuffer::Orientation from_orientation,FrameBuffer::Orientation to_orientation,FrameBuffer::Dimension from_dimension)174 BoundingBox OrientBoundingBox(const BoundingBox& from_box,
175 FrameBuffer::Orientation from_orientation,
176 FrameBuffer::Orientation to_orientation,
177 FrameBuffer::Dimension from_dimension) {
178 BoundingBox to_box = from_box;
179 OrientParams params = GetOrientParams(from_orientation, to_orientation);
180 // First, rotate if needed.
181 if (params.rotation_angle_deg > 0) {
182 to_box =
183 RotateBoundingBox(to_box, params.rotation_angle_deg, from_dimension);
184 }
185 // Then perform horizontal or vertical flip if needed.
186 FrameBuffer::Dimension to_dimension = from_dimension;
187 if (params.rotation_angle_deg == 90 || params.rotation_angle_deg == 270) {
188 to_dimension.Swap();
189 }
190 if (params.flip == OrientParams::FlipType::kVertical) {
191 to_box.set_origin_y(to_dimension.height -
192 (to_box.origin_y() + to_box.height()));
193 }
194 if (params.flip == OrientParams::FlipType::kHorizontal) {
195 to_box.set_origin_x(to_dimension.width -
196 (to_box.origin_x() + to_box.width()));
197 }
198 return to_box;
199 }
200
OrientAndDenormalizeBoundingBox(float from_left,float from_top,float from_right,float from_bottom,FrameBuffer::Orientation from_orientation,FrameBuffer::Orientation to_orientation,FrameBuffer::Dimension from_dimension)201 BoundingBox OrientAndDenormalizeBoundingBox(
202 float from_left, float from_top, float from_right, float from_bottom,
203 FrameBuffer::Orientation from_orientation,
204 FrameBuffer::Orientation to_orientation,
205 FrameBuffer::Dimension from_dimension) {
206 BoundingBox from_box;
207 from_box.set_origin_x(from_left * from_dimension.width);
208 from_box.set_origin_y(from_top * from_dimension.height);
209 from_box.set_width(round(abs(from_right - from_left) * from_dimension.width));
210 from_box.set_height(
211 round(abs(from_bottom - from_top) * from_dimension.height));
212 BoundingBox to_box = OrientBoundingBox(from_box, from_orientation,
213 to_orientation, from_dimension);
214 return to_box;
215 }
216
OrientCoordinates(int from_x,int from_y,FrameBuffer::Orientation from_orientation,FrameBuffer::Orientation to_orientation,FrameBuffer::Dimension from_dimension,int * to_x,int * to_y)217 void OrientCoordinates(int from_x, int from_y,
218 FrameBuffer::Orientation from_orientation,
219 FrameBuffer::Orientation to_orientation,
220 FrameBuffer::Dimension from_dimension, int* to_x,
221 int* to_y) {
222 *to_x = from_x;
223 *to_y = from_y;
224 OrientParams params = GetOrientParams(from_orientation, to_orientation);
225 // First, rotate if needed.
226 if (params.rotation_angle_deg > 0) {
227 RotateCoordinates(from_x, from_y, params.rotation_angle_deg, from_dimension,
228 to_x, to_y);
229 }
230 // Then perform horizontal or vertical flip if needed.
231 FrameBuffer::Dimension to_dimension = from_dimension;
232 if (params.rotation_angle_deg == 90 || params.rotation_angle_deg == 270) {
233 to_dimension.Swap();
234 }
235 if (params.flip == OrientParams::FlipType::kVertical) {
236 *to_y = to_dimension.height - *to_y - 1;
237 }
238 if (params.flip == OrientParams::FlipType::kHorizontal) {
239 *to_x = to_dimension.width - *to_x - 1;
240 }
241 }
242
243 // The algorithm is based on grouping orientations into two groups with specific
244 // order. The two groups of orientation are {1, 6, 3, 8} and {2, 5, 4, 7}. See
245 // image (https://www.impulseadventure.com/photo/images/orient_flag.gif) for
246 // the visual grouping illustration.
247 //
248 // Each group contains elements can be transformed into one another by rotation.
249 // The elements order within a group is important such that the distance between
250 // the elements indicates the multiples of 90 degree needed to orient from one
251 // element to another. For example, to orient element 1 to element 6, a 90
252 // degree CCW rotation is needed.
253 //
254 // The corresponding order between the two groups is important such that the
255 // even index defined the need for horizontal flipping and the odd index defined
256 // the need for vertical flipping. For example, to orient element 1 to element 2
257 // (even index) a horizontal flipping is needed.
258 //
259 // The implementation determines the group and element index of from and to
260 // orientations. Based on the group and element index information, the above
261 // characteristic is used to calculate the rotation angle and the need for
262 // horizontal or vertical flipping.
GetOrientParams(FrameBuffer::Orientation from_orientation,FrameBuffer::Orientation to_orientation)263 OrientParams GetOrientParams(FrameBuffer::Orientation from_orientation,
264 FrameBuffer::Orientation to_orientation) {
265 int from_index = GetOrientationIndex(from_orientation);
266 int to_index = GetOrientationIndex(to_orientation);
267 int angle = 0;
268 absl::optional<OrientParams::FlipType> flip;
269
270 TFLITE_DCHECK(from_index > -1 && to_index > -1);
271
272 if ((from_index < kExifGroupSize && to_index < kExifGroupSize) ||
273 (from_index >= kExifGroupSize && to_index >= kExifGroupSize)) {
274 // Only needs rotation.
275
276 // The orientations' position differences translates to how many
277 // multiple of 90 degrees it needs for conversion. The position difference
278 // calculation within a group is circular.
279 angle = (kExifGroupSize - (from_index - to_index)) % kExifGroupSize * 90;
280 } else {
281 // Needs rotation and flipping.
282 int from_index_mod = from_index % kExifGroupSize;
283 int to_index_mod = to_index % kExifGroupSize;
284 angle = (kExifGroupSize - (from_index_mod - to_index_mod)) %
285 kExifGroupSize * 90;
286 if (to_index_mod % 2 == 1) {
287 flip = OrientParams::FlipType::kVertical;
288 } else {
289 flip = OrientParams::FlipType::kHorizontal;
290 }
291 }
292 return {angle, flip};
293 }
294
RequireDimensionSwap(FrameBuffer::Orientation from_orientation,FrameBuffer::Orientation to_orientation)295 bool RequireDimensionSwap(FrameBuffer::Orientation from_orientation,
296 FrameBuffer::Orientation to_orientation) {
297 OrientParams params = GetOrientParams(from_orientation, to_orientation);
298 return params.rotation_angle_deg == 90 || params.rotation_angle_deg == 270;
299 }
300
Crop(const FrameBuffer & buffer,int x0,int y0,int x1,int y1,FrameBuffer * output_buffer)301 absl::Status FrameBufferUtils::Crop(const FrameBuffer& buffer, int x0, int y0,
302 int x1, int y1,
303 FrameBuffer* output_buffer) {
304 TFLITE_DCHECK(utils_ != nullptr);
305 return utils_->Crop(buffer, x0, y0, x1, y1, output_buffer);
306 }
307
GetSize(const FrameBuffer & buffer,const FrameBufferOperation & operation)308 FrameBuffer::Dimension FrameBufferUtils::GetSize(
309 const FrameBuffer& buffer, const FrameBufferOperation& operation) {
310 FrameBuffer::Dimension dimension = buffer.dimension();
311 if (absl::holds_alternative<OrientOperation>(operation)) {
312 OrientParams params =
313 GetOrientParams(buffer.orientation(),
314 absl::get<OrientOperation>(operation).to_orientation);
315 if (params.rotation_angle_deg == 90 || params.rotation_angle_deg == 270) {
316 dimension.Swap();
317 }
318 } else if (absl::holds_alternative<CropResizeOperation>(operation)) {
319 const auto& crop_resize = absl::get<CropResizeOperation>(operation);
320 dimension = crop_resize.resize_dimension;
321 }
322 return dimension;
323 }
324
GetPlanes(const uint8 * buffer,FrameBuffer::Dimension dimension,FrameBuffer::Format format)325 std::vector<FrameBuffer::Plane> FrameBufferUtils::GetPlanes(
326 const uint8* buffer, FrameBuffer::Dimension dimension,
327 FrameBuffer::Format format) {
328 std::vector<FrameBuffer::Plane> planes;
329 switch (format) {
330 case FrameBuffer::Format::kGRAY:
331 planes.push_back({/*buffer=*/buffer,
332 /*stride=*/{/*row_stride_bytes=*/dimension.width * 1,
333 /*pixel_stride_bytes=*/1}});
334 break;
335 case FrameBuffer::Format::kRGB:
336 planes.push_back({/*buffer=*/buffer,
337 /*stride=*/{/*row_stride_bytes=*/dimension.width * 3,
338 /*pixel_stride_bytes=*/3}});
339 break;
340 case FrameBuffer::Format::kRGBA:
341 planes.push_back({/*buffer=*/buffer,
342 /*stride=*/{/*row_stride_bytes=*/dimension.width * 4,
343 /*pixel_stride_bytes=*/4}});
344 break;
345 case FrameBuffer::Format::kNV21:
346 case FrameBuffer::Format::kNV12: {
347 planes.push_back(
348 {buffer, /*stride=*/{/*row_stride_bytes=*/dimension.width,
349 /*pixel_stride_bytes=*/1}});
350 planes.push_back({buffer + (dimension.width * dimension.height),
351 /*stride=*/{/*row_stride_bytes=*/dimension.width,
352 /*pixel_stride_bytes=*/2}});
353 } break;
354 case FrameBuffer::Format::kYV12:
355 case FrameBuffer::Format::kYV21: {
356 const int y_buffer_size = dimension.width * dimension.height;
357 const int uv_row_stride = (dimension.width + 1) / 2;
358 const int uv_buffer_size = uv_row_stride * (dimension.height + 1) / 2;
359 planes.push_back(
360 {buffer, /*stride=*/{/*row_stride_bytes=*/dimension.width,
361 /*pixel_stride_bytes=*/1}});
362 planes.push_back(
363 {buffer + y_buffer_size, /*stride=*/{
364 /*row_stride_bytes=*/uv_row_stride, /*pixel_stride_bytes=*/1}});
365 planes.push_back(
366 {buffer + y_buffer_size + uv_buffer_size, /*stride=*/{
367 /*row_stride_bytes=*/uv_row_stride, /*pixel_stride_bytes=*/1}});
368 } break;
369 default:
370 break;
371 }
372 return planes;
373 }
374
GetOrientation(const FrameBuffer & buffer,const FrameBufferOperation & operation)375 FrameBuffer::Orientation FrameBufferUtils::GetOrientation(
376 const FrameBuffer& buffer, const FrameBufferOperation& operation) {
377 if (absl::holds_alternative<OrientOperation>(operation)) {
378 return absl::get<OrientOperation>(operation).to_orientation;
379 }
380 return buffer.orientation();
381 }
382
GetFormat(const FrameBuffer & buffer,const FrameBufferOperation & operation)383 FrameBuffer::Format FrameBufferUtils::GetFormat(
384 const FrameBuffer& buffer, const FrameBufferOperation& operation) {
385 if (absl::holds_alternative<ConvertOperation>(operation)) {
386 return absl::get<ConvertOperation>(operation).to_format;
387 }
388 return buffer.format();
389 }
390
Execute(const FrameBuffer & buffer,const FrameBufferOperation & operation,FrameBuffer * output_buffer)391 absl::Status FrameBufferUtils::Execute(const FrameBuffer& buffer,
392 const FrameBufferOperation& operation,
393 FrameBuffer* output_buffer) {
394 if (absl::holds_alternative<CropResizeOperation>(operation)) {
395 const auto& params = absl::get<CropResizeOperation>(operation);
396 RETURN_IF_ERROR(
397 Crop(buffer, params.crop_origin_x, params.crop_origin_y,
398 (params.crop_dimension.width + params.crop_origin_x - 1),
399 (params.crop_dimension.height + params.crop_origin_y - 1),
400 output_buffer));
401 } else if (absl::holds_alternative<ConvertOperation>(operation)) {
402 RETURN_IF_ERROR(Convert(buffer, output_buffer));
403 } else if (absl::holds_alternative<OrientOperation>(operation)) {
404 RETURN_IF_ERROR(Orient(buffer, output_buffer));
405 } else {
406 return absl::UnimplementedError(absl::StrFormat(
407 "FrameBufferOperation %i is not supported.", operation.index()));
408 }
409 return absl::OkStatus();
410 }
411
Resize(const FrameBuffer & buffer,FrameBuffer * output_buffer)412 absl::Status FrameBufferUtils::Resize(const FrameBuffer& buffer,
413 FrameBuffer* output_buffer) {
414 TFLITE_DCHECK(utils_ != nullptr);
415 return utils_->Resize(buffer, output_buffer);
416 }
417
Rotate(const FrameBuffer & buffer,RotationDegree rotation,FrameBuffer * output_buffer)418 absl::Status FrameBufferUtils::Rotate(const FrameBuffer& buffer,
419 RotationDegree rotation,
420 FrameBuffer* output_buffer) {
421 TFLITE_DCHECK(utils_ != nullptr);
422 return utils_->Rotate(buffer, 90 * static_cast<int>(rotation), output_buffer);
423 }
424
FlipHorizontally(const FrameBuffer & buffer,FrameBuffer * output_buffer)425 absl::Status FrameBufferUtils::FlipHorizontally(const FrameBuffer& buffer,
426 FrameBuffer* output_buffer) {
427 TFLITE_DCHECK(utils_ != nullptr);
428 return utils_->FlipHorizontally(buffer, output_buffer);
429 }
430
FlipVertically(const FrameBuffer & buffer,FrameBuffer * output_buffer)431 absl::Status FrameBufferUtils::FlipVertically(const FrameBuffer& buffer,
432 FrameBuffer* output_buffer) {
433 TFLITE_DCHECK(utils_ != nullptr);
434 return utils_->FlipVertically(buffer, output_buffer);
435 }
436
Convert(const FrameBuffer & buffer,FrameBuffer * output_buffer)437 absl::Status FrameBufferUtils::Convert(const FrameBuffer& buffer,
438 FrameBuffer* output_buffer) {
439 TFLITE_DCHECK(utils_ != nullptr);
440 return utils_->Convert(buffer, output_buffer);
441 }
442
Orient(const FrameBuffer & buffer,FrameBuffer * output_buffer)443 absl::Status FrameBufferUtils::Orient(const FrameBuffer& buffer,
444 FrameBuffer* output_buffer) {
445 TFLITE_DCHECK(utils_ != nullptr);
446
447 OrientParams params =
448 GetOrientParams(buffer.orientation(), output_buffer->orientation());
449 if (params.rotation_angle_deg == 0 && !params.flip.has_value()) {
450 // If no rotation or flip is needed, we will copy the buffer to
451 // output_buffer.
452 return utils_->Resize(buffer, output_buffer);
453 }
454
455 if (params.rotation_angle_deg == 0) {
456 // Only perform flip operation.
457 switch (*params.flip) {
458 case OrientParams::FlipType::kHorizontal:
459 return utils_->FlipHorizontally(buffer, output_buffer);
460 case OrientParams::FlipType::kVertical:
461 return utils_->FlipVertically(buffer, output_buffer);
462 }
463 }
464
465 if (!params.flip.has_value()) {
466 // Only perform rotation operation.
467 return utils_->Rotate(buffer, params.rotation_angle_deg, output_buffer);
468 }
469
470 // Perform rotation and flip operations.
471 // Create a temporary buffer to hold the rotation result.
472 auto tmp_buffer = absl::make_unique<uint8[]>(
473 GetBufferByteSize(output_buffer->dimension(), output_buffer->format()));
474 auto tmp_frame_buffer = FrameBuffer::Create(
475 GetPlanes(tmp_buffer.get(), output_buffer->dimension(),
476 output_buffer->format()),
477 output_buffer->dimension(), buffer.format(), buffer.orientation());
478
479 RETURN_IF_ERROR(utils_->Rotate(buffer, params.rotation_angle_deg,
480 tmp_frame_buffer.get()));
481 if (params.flip == OrientParams::FlipType::kHorizontal) {
482 return utils_->FlipHorizontally(*tmp_frame_buffer, output_buffer);
483 } else {
484 return utils_->FlipVertically(*tmp_frame_buffer, output_buffer);
485 }
486 }
487
Execute(const FrameBuffer & buffer,const std::vector<FrameBufferOperation> & operations,FrameBuffer * output_buffer)488 absl::Status FrameBufferUtils::Execute(
489 const FrameBuffer& buffer,
490 const std::vector<FrameBufferOperation>& operations,
491 FrameBuffer* output_buffer) {
492 // Reference variables to swapping input and output buffers for each command.
493 FrameBuffer input_frame_buffer = buffer;
494 FrameBuffer temp_frame_buffer = buffer;
495
496 // Temporary buffers and its size to hold intermediate results.
497 int buffer1_size = 0;
498 int buffer2_size = 0;
499 std::unique_ptr<uint8[]> buffer1;
500 std::unique_ptr<uint8[]> buffer2;
501
502 for (int i = 0; i < operations.size(); i++) {
503 const FrameBufferOperation& operation = operations[i];
504
505 // The first command's input is always passed in `buffer`. Before
506 // process each command, the input_frame_buffer is pointed at the previous
507 // command's output buffer.
508 if (i == 0) {
509 input_frame_buffer = buffer;
510 } else {
511 input_frame_buffer = temp_frame_buffer;
512 }
513
514 // Calculates the resulting metadata from the command and the input.
515 FrameBuffer::Dimension new_size = GetSize(input_frame_buffer, operation);
516 FrameBuffer::Orientation new_orientation =
517 GetOrientation(input_frame_buffer, operation);
518 FrameBuffer::Format new_format = GetFormat(input_frame_buffer, operation);
519 int byte_size = GetBufferByteSize(new_size, new_format);
520
521 // The last command's output buffer is always passed in `output_buffer`.
522 // For other commands, we create temporary FrameBuffer for processing.
523 if ((i + 1) == operations.size()) {
524 temp_frame_buffer = *output_buffer;
525 // Validate the `output_buffer` metadata mathes with command line chain
526 // resulting metadata.
527 if (temp_frame_buffer.format() != new_format ||
528 temp_frame_buffer.orientation() != new_orientation ||
529 temp_frame_buffer.dimension() != new_size) {
530 return absl::InvalidArgumentError(
531 "The output metadata does not match pipeline result metadata.");
532 }
533 } else {
534 // Create a temporary buffer to hold intermediate results. For simplicity,
535 // we only create one continuous memory with no padding for intermediate
536 // results.
537 //
538 // We hold maximum 2 temporary buffers in memory at any given time.
539 //
540 // The pipeline is a linear chain. The output buffer from previous command
541 // becomes the input buffer for the next command. We simply use odd / even
542 // index to swap between buffers.
543 std::vector<FrameBuffer::Plane> planes;
544 if (i % 2 == 0) {
545 if (buffer1_size < byte_size) {
546 buffer1_size = byte_size;
547 buffer1 = absl::make_unique<uint8[]>(byte_size);
548 }
549 planes = GetPlanes(buffer1.get(), new_size, new_format);
550 } else {
551 if (buffer2_size < byte_size) {
552 buffer2_size = byte_size;
553 buffer2 = absl::make_unique<uint8[]>(byte_size);
554 }
555 planes = GetPlanes(buffer2.get(), new_size, new_format);
556 }
557 if (planes.empty()) {
558 return absl::InternalError("Failed to construct temporary buffer.");
559 }
560 temp_frame_buffer = FrameBuffer(planes, new_size, new_format,
561 new_orientation, buffer.timestamp());
562 }
563 RETURN_IF_ERROR(Execute(input_frame_buffer, operation, &temp_frame_buffer));
564 }
565 return absl::OkStatus();
566 }
567
Preprocess(const FrameBuffer & buffer,absl::optional<BoundingBox> bounding_box,FrameBuffer * output_buffer)568 absl::Status FrameBufferUtils::Preprocess(
569 const FrameBuffer& buffer, absl::optional<BoundingBox> bounding_box,
570 FrameBuffer* output_buffer) {
571 std::vector<FrameBufferOperation> frame_buffer_operations;
572 // Handle cropping and resizing.
573 bool needs_dimension_swap =
574 RequireDimensionSwap(buffer.orientation(), output_buffer->orientation());
575 // For intermediate steps, we need to use dimensions based on the input
576 // orientation.
577 FrameBuffer::Dimension pre_orient_dimension = output_buffer->dimension();
578 if (needs_dimension_swap) {
579 pre_orient_dimension.Swap();
580 }
581
582 if (bounding_box.has_value()) {
583 // Cropping case.
584 frame_buffer_operations.push_back(CropResizeOperation(
585 bounding_box.value().origin_x(), bounding_box.value().origin_y(),
586 FrameBuffer::Dimension{bounding_box.value().width(),
587 bounding_box.value().height()},
588 pre_orient_dimension));
589 } else if (pre_orient_dimension != buffer.dimension()) {
590 // Resizing case.
591 frame_buffer_operations.push_back(
592 CropResizeOperation(0, 0, buffer.dimension(), pre_orient_dimension));
593 }
594
595 // Handle color space conversion.
596 if (output_buffer->format() != buffer.format()) {
597 frame_buffer_operations.push_back(
598 ConvertOperation(output_buffer->format()));
599 }
600
601 // Handle orientation conversion.
602 if (output_buffer->orientation() != buffer.orientation()) {
603 frame_buffer_operations.push_back(
604 OrientOperation(output_buffer->orientation()));
605 }
606
607 // Execute the processing pipeline.
608 if (frame_buffer_operations.empty()) {
609 // Using resize to perform copy.
610 RETURN_IF_ERROR(Resize(buffer, output_buffer));
611 } else {
612 RETURN_IF_ERROR(Execute(buffer, frame_buffer_operations, output_buffer));
613 }
614 return absl::OkStatus();
615 }
616
617 } // namespace vision
618 } // namespace task
619 } // namespace tflite
620