• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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