aboutsummaryrefslogtreecommitdiff
path: root/tensorflow_lite_support/cc/task/vision/utils/frame_buffer_utils.cc
diff options
context:
space:
mode:
Diffstat (limited to 'tensorflow_lite_support/cc/task/vision/utils/frame_buffer_utils.cc')
-rw-r--r--tensorflow_lite_support/cc/task/vision/utils/frame_buffer_utils.cc619
1 files changed, 619 insertions, 0 deletions
diff --git a/tensorflow_lite_support/cc/task/vision/utils/frame_buffer_utils.cc b/tensorflow_lite_support/cc/task/vision/utils/frame_buffer_utils.cc
new file mode 100644
index 00000000..9b9d830e
--- /dev/null
+++ b/tensorflow_lite_support/cc/task/vision/utils/frame_buffer_utils.cc
@@ -0,0 +1,619 @@
+/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+
+#include "tensorflow_lite_support/cc/task/vision/utils/frame_buffer_utils.h"
+
+#include <algorithm>
+#include <iterator>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "absl/memory/memory.h"
+#include "absl/status/status.h"
+#include "absl/strings/str_format.h"
+#include "tensorflow/lite/kernels/op_macros.h"
+#include "tensorflow/lite/kernels/internal/compatibility.h"
+#include "tensorflow_lite_support/cc/port/status_macros.h"
+#include "tensorflow_lite_support/cc/task/vision/utils/frame_buffer_common_utils.h"
+#include "tensorflow_lite_support/cc/task/vision/utils/libyuv_frame_buffer_utils.h"
+
+namespace tflite {
+namespace task {
+namespace vision {
+
+namespace {
+
+// Exif grouping to help determine rotation and flipping neededs between
+// different orientations.
+constexpr int kExifGroup[] = {1, 6, 3, 8, 2, 5, 4, 7};
+// Exif group size.
+constexpr int kExifGroupSize = 4;
+
+// Returns orientation position in Exif group.
+static int GetOrientationIndex(FrameBuffer::Orientation orientation) {
+ const int* index = std::find(kExifGroup, kExifGroup + kExifGroupSize * 2,
+ static_cast<int>(orientation));
+ if (index < kExifGroup + kExifGroupSize * 2) {
+ return std::distance(kExifGroup, index);
+ }
+ return -1;
+}
+
+// Returns the coordinates of `box` respect to its containing image (dimension
+// defined by `width` and `height`) orientation change. The `angle` is defined
+// in counterclockwise degree in one of the values [0, 90, 180, 270].
+//
+// The below diagrams illustrate calling this method with 90 CCW degree.
+//
+// The [1]-[4] denotes image corners and 1 - 4 denotes the box corners. The *
+// denotes the current origin.
+//
+// width
+// [1]*----------------[2]
+// | |
+// | |
+// | 1*-----2 | height
+// | | box | |
+// | 3------4 |
+// [3]-----------------[4]
+//
+// When rotate the above image by 90 CCW degree, the origin also changes
+// respects to its containing coordinate space.
+//
+// height
+// [2]*----------[4]
+// | |
+// | 2*---4 |
+// | |box | |
+// | | | | width
+// | 1----3 |
+// | |
+// | |
+// | |
+// [1]-----------[3]
+//
+// The origin is always defined by the top left corner. After rotation, the
+// box origin changed from 1 to 2.
+// The new box origin is (x:box.origin_y, y:width - (box.origin_x + box.width).
+// The new box dimension is (w: box.height, h: box.width).
+//
+static BoundingBox RotateBoundingBox(const BoundingBox& box, int angle,
+ FrameBuffer::Dimension frame_dimension) {
+ int rx = box.origin_x(), ry = box.origin_y(), rw = box.width(),
+ rh = box.height();
+ const int box_right_bound =
+ frame_dimension.width - (box.origin_x() + box.width());
+ const int box_bottom_bound =
+ frame_dimension.height - (box.origin_y() + box.height());
+ switch (angle) {
+ case 90:
+ rx = box.origin_y();
+ ry = box_right_bound;
+ using std::swap;
+ swap(rw, rh);
+ break;
+ case 180:
+ rx = box_right_bound;
+ ry = box_bottom_bound;
+ break;
+ case 270:
+ rx = box_bottom_bound;
+ ry = box.origin_x();
+ using std::swap;
+ swap(rw, rh);
+ break;
+ }
+ BoundingBox result;
+ result.set_origin_x(rx);
+ result.set_origin_y(ry);
+ result.set_width(rw);
+ result.set_height(rh);
+ return result;
+}
+
+// Returns the input coordinates with respect to its containing image (dimension
+// defined by `width` and `height`) orientation change. The `angle` is defined
+// in counterclockwise degree in one of the values [0, 90, 180, 270].
+//
+// See `RotateBoundingBox` above for more details.
+static void RotateCoordinates(int from_x, int from_y, int angle,
+ const FrameBuffer::Dimension& frame_dimension,
+ int* to_x, int* to_y) {
+ switch (angle) {
+ case 0:
+ *to_x = from_x;
+ *to_y = from_y;
+ break;
+ case 90:
+ *to_x = from_y;
+ *to_y = frame_dimension.width - from_x - 1;
+ break;
+ case 180:
+ *to_x = frame_dimension.width - from_x - 1;
+ *to_y = frame_dimension.height - from_y - 1;
+ break;
+ case 270:
+ *to_x = frame_dimension.height - from_y - 1;
+ *to_y = from_x;
+ break;
+ }
+}
+
+} // namespace
+
+int GetBufferByteSize(FrameBuffer::Dimension dimension,
+ FrameBuffer::Format format) {
+ return GetFrameBufferByteSize(dimension, format);
+}
+
+FrameBufferUtils::FrameBufferUtils(ProcessEngine engine) {
+ switch (engine) {
+ case ProcessEngine::kLibyuv:
+ utils_ = absl::make_unique<LibyuvFrameBufferUtils>();
+ break;
+ default:
+ TF_LITE_FATAL(
+ absl::StrFormat("Unexpected ProcessEngine: %d.", engine).c_str());
+ }
+}
+
+BoundingBox OrientBoundingBox(const BoundingBox& from_box,
+ FrameBuffer::Orientation from_orientation,
+ FrameBuffer::Orientation to_orientation,
+ FrameBuffer::Dimension from_dimension) {
+ BoundingBox to_box = from_box;
+ OrientParams params = GetOrientParams(from_orientation, to_orientation);
+ // First, rotate if needed.
+ if (params.rotation_angle_deg > 0) {
+ to_box =
+ RotateBoundingBox(to_box, params.rotation_angle_deg, from_dimension);
+ }
+ // Then perform horizontal or vertical flip if needed.
+ FrameBuffer::Dimension to_dimension = from_dimension;
+ if (params.rotation_angle_deg == 90 || params.rotation_angle_deg == 270) {
+ to_dimension.Swap();
+ }
+ if (params.flip == OrientParams::FlipType::kVertical) {
+ to_box.set_origin_y(to_dimension.height -
+ (to_box.origin_y() + to_box.height()));
+ }
+ if (params.flip == OrientParams::FlipType::kHorizontal) {
+ to_box.set_origin_x(to_dimension.width -
+ (to_box.origin_x() + to_box.width()));
+ }
+ return to_box;
+}
+
+BoundingBox 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) {
+ BoundingBox from_box;
+ from_box.set_origin_x(from_left * from_dimension.width);
+ from_box.set_origin_y(from_top * from_dimension.height);
+ from_box.set_width(round(abs(from_right - from_left) * from_dimension.width));
+ from_box.set_height(
+ round(abs(from_bottom - from_top) * from_dimension.height));
+ BoundingBox to_box = OrientBoundingBox(from_box, from_orientation,
+ to_orientation, from_dimension);
+ return to_box;
+}
+
+void 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) {
+ *to_x = from_x;
+ *to_y = from_y;
+ OrientParams params = GetOrientParams(from_orientation, to_orientation);
+ // First, rotate if needed.
+ if (params.rotation_angle_deg > 0) {
+ RotateCoordinates(from_x, from_y, params.rotation_angle_deg, from_dimension,
+ to_x, to_y);
+ }
+ // Then perform horizontal or vertical flip if needed.
+ FrameBuffer::Dimension to_dimension = from_dimension;
+ if (params.rotation_angle_deg == 90 || params.rotation_angle_deg == 270) {
+ to_dimension.Swap();
+ }
+ if (params.flip == OrientParams::FlipType::kVertical) {
+ *to_y = to_dimension.height - *to_y - 1;
+ }
+ if (params.flip == OrientParams::FlipType::kHorizontal) {
+ *to_x = to_dimension.width - *to_x - 1;
+ }
+}
+
+// The algorithm is based on grouping orientations into two groups with specific
+// order. The two groups of orientation are {1, 6, 3, 8} and {2, 5, 4, 7}. See
+// image (https://www.impulseadventure.com/photo/images/orient_flag.gif) for
+// the visual grouping illustration.
+//
+// Each group contains elements can be transformed into one another by rotation.
+// The elements order within a group is important such that the distance between
+// the elements indicates the multiples of 90 degree needed to orient from one
+// element to another. For example, to orient element 1 to element 6, a 90
+// degree CCW rotation is needed.
+//
+// The corresponding order between the two groups is important such that the
+// even index defined the need for horizontal flipping and the odd index defined
+// the need for vertical flipping. For example, to orient element 1 to element 2
+// (even index) a horizontal flipping is needed.
+//
+// The implementation determines the group and element index of from and to
+// orientations. Based on the group and element index information, the above
+// characteristic is used to calculate the rotation angle and the need for
+// horizontal or vertical flipping.
+OrientParams GetOrientParams(FrameBuffer::Orientation from_orientation,
+ FrameBuffer::Orientation to_orientation) {
+ int from_index = GetOrientationIndex(from_orientation);
+ int to_index = GetOrientationIndex(to_orientation);
+ int angle = 0;
+ absl::optional<OrientParams::FlipType> flip;
+
+ TFLITE_DCHECK(from_index > -1 && to_index > -1);
+
+ if ((from_index < kExifGroupSize && to_index < kExifGroupSize) ||
+ (from_index >= kExifGroupSize && to_index >= kExifGroupSize)) {
+ // Only needs rotation.
+
+ // The orientations' position differences translates to how many
+ // multiple of 90 degrees it needs for conversion. The position difference
+ // calculation within a group is circular.
+ angle = (kExifGroupSize - (from_index - to_index)) % kExifGroupSize * 90;
+ } else {
+ // Needs rotation and flipping.
+ int from_index_mod = from_index % kExifGroupSize;
+ int to_index_mod = to_index % kExifGroupSize;
+ angle = (kExifGroupSize - (from_index_mod - to_index_mod)) %
+ kExifGroupSize * 90;
+ if (to_index_mod % 2 == 1) {
+ flip = OrientParams::FlipType::kVertical;
+ } else {
+ flip = OrientParams::FlipType::kHorizontal;
+ }
+ }
+ return {angle, flip};
+}
+
+bool RequireDimensionSwap(FrameBuffer::Orientation from_orientation,
+ FrameBuffer::Orientation to_orientation) {
+ OrientParams params = GetOrientParams(from_orientation, to_orientation);
+ return params.rotation_angle_deg == 90 || params.rotation_angle_deg == 270;
+}
+
+absl::Status FrameBufferUtils::Crop(const FrameBuffer& buffer, int x0, int y0,
+ int x1, int y1,
+ FrameBuffer* output_buffer) {
+ TFLITE_DCHECK(utils_ != nullptr);
+ return utils_->Crop(buffer, x0, y0, x1, y1, output_buffer);
+}
+
+FrameBuffer::Dimension FrameBufferUtils::GetSize(
+ const FrameBuffer& buffer, const FrameBufferOperation& operation) {
+ FrameBuffer::Dimension dimension = buffer.dimension();
+ if (absl::holds_alternative<OrientOperation>(operation)) {
+ OrientParams params =
+ GetOrientParams(buffer.orientation(),
+ absl::get<OrientOperation>(operation).to_orientation);
+ if (params.rotation_angle_deg == 90 || params.rotation_angle_deg == 270) {
+ dimension.Swap();
+ }
+ } else if (absl::holds_alternative<CropResizeOperation>(operation)) {
+ const auto& crop_resize = absl::get<CropResizeOperation>(operation);
+ dimension = crop_resize.resize_dimension;
+ }
+ return dimension;
+}
+
+std::vector<FrameBuffer::Plane> FrameBufferUtils::GetPlanes(
+ const uint8* buffer, FrameBuffer::Dimension dimension,
+ FrameBuffer::Format format) {
+ std::vector<FrameBuffer::Plane> planes;
+ switch (format) {
+ case FrameBuffer::Format::kGRAY:
+ planes.push_back({/*buffer=*/buffer,
+ /*stride=*/{/*row_stride_bytes=*/dimension.width * 1,
+ /*pixel_stride_bytes=*/1}});
+ break;
+ case FrameBuffer::Format::kRGB:
+ planes.push_back({/*buffer=*/buffer,
+ /*stride=*/{/*row_stride_bytes=*/dimension.width * 3,
+ /*pixel_stride_bytes=*/3}});
+ break;
+ case FrameBuffer::Format::kRGBA:
+ planes.push_back({/*buffer=*/buffer,
+ /*stride=*/{/*row_stride_bytes=*/dimension.width * 4,
+ /*pixel_stride_bytes=*/4}});
+ break;
+ case FrameBuffer::Format::kNV21:
+ case FrameBuffer::Format::kNV12: {
+ planes.push_back(
+ {buffer, /*stride=*/{/*row_stride_bytes=*/dimension.width,
+ /*pixel_stride_bytes=*/1}});
+ planes.push_back({buffer + (dimension.width * dimension.height),
+ /*stride=*/{/*row_stride_bytes=*/dimension.width,
+ /*pixel_stride_bytes=*/2}});
+ } break;
+ case FrameBuffer::Format::kYV12:
+ case FrameBuffer::Format::kYV21: {
+ const int y_buffer_size = dimension.width * dimension.height;
+ const int uv_row_stride = (dimension.width + 1) / 2;
+ const int uv_buffer_size = uv_row_stride * (dimension.height + 1) / 2;
+ planes.push_back(
+ {buffer, /*stride=*/{/*row_stride_bytes=*/dimension.width,
+ /*pixel_stride_bytes=*/1}});
+ planes.push_back(
+ {buffer + y_buffer_size, /*stride=*/{
+ /*row_stride_bytes=*/uv_row_stride, /*pixel_stride_bytes=*/1}});
+ planes.push_back(
+ {buffer + y_buffer_size + uv_buffer_size, /*stride=*/{
+ /*row_stride_bytes=*/uv_row_stride, /*pixel_stride_bytes=*/1}});
+ } break;
+ default:
+ break;
+ }
+ return planes;
+}
+
+FrameBuffer::Orientation FrameBufferUtils::GetOrientation(
+ const FrameBuffer& buffer, const FrameBufferOperation& operation) {
+ if (absl::holds_alternative<OrientOperation>(operation)) {
+ return absl::get<OrientOperation>(operation).to_orientation;
+ }
+ return buffer.orientation();
+}
+
+FrameBuffer::Format FrameBufferUtils::GetFormat(
+ const FrameBuffer& buffer, const FrameBufferOperation& operation) {
+ if (absl::holds_alternative<ConvertOperation>(operation)) {
+ return absl::get<ConvertOperation>(operation).to_format;
+ }
+ return buffer.format();
+}
+
+absl::Status FrameBufferUtils::Execute(const FrameBuffer& buffer,
+ const FrameBufferOperation& operation,
+ FrameBuffer* output_buffer) {
+ if (absl::holds_alternative<CropResizeOperation>(operation)) {
+ const auto& params = absl::get<CropResizeOperation>(operation);
+ RETURN_IF_ERROR(
+ Crop(buffer, params.crop_origin_x, params.crop_origin_y,
+ (params.crop_dimension.width + params.crop_origin_x - 1),
+ (params.crop_dimension.height + params.crop_origin_y - 1),
+ output_buffer));
+ } else if (absl::holds_alternative<ConvertOperation>(operation)) {
+ RETURN_IF_ERROR(Convert(buffer, output_buffer));
+ } else if (absl::holds_alternative<OrientOperation>(operation)) {
+ RETURN_IF_ERROR(Orient(buffer, output_buffer));
+ } else {
+ return absl::UnimplementedError(absl::StrFormat(
+ "FrameBufferOperation %i is not supported.", operation.index()));
+ }
+ return absl::OkStatus();
+}
+
+absl::Status FrameBufferUtils::Resize(const FrameBuffer& buffer,
+ FrameBuffer* output_buffer) {
+ TFLITE_DCHECK(utils_ != nullptr);
+ return utils_->Resize(buffer, output_buffer);
+}
+
+absl::Status FrameBufferUtils::Rotate(const FrameBuffer& buffer,
+ RotationDegree rotation,
+ FrameBuffer* output_buffer) {
+ TFLITE_DCHECK(utils_ != nullptr);
+ return utils_->Rotate(buffer, 90 * static_cast<int>(rotation), output_buffer);
+}
+
+absl::Status FrameBufferUtils::FlipHorizontally(const FrameBuffer& buffer,
+ FrameBuffer* output_buffer) {
+ TFLITE_DCHECK(utils_ != nullptr);
+ return utils_->FlipHorizontally(buffer, output_buffer);
+}
+
+absl::Status FrameBufferUtils::FlipVertically(const FrameBuffer& buffer,
+ FrameBuffer* output_buffer) {
+ TFLITE_DCHECK(utils_ != nullptr);
+ return utils_->FlipVertically(buffer, output_buffer);
+}
+
+absl::Status FrameBufferUtils::Convert(const FrameBuffer& buffer,
+ FrameBuffer* output_buffer) {
+ TFLITE_DCHECK(utils_ != nullptr);
+ return utils_->Convert(buffer, output_buffer);
+}
+
+absl::Status FrameBufferUtils::Orient(const FrameBuffer& buffer,
+ FrameBuffer* output_buffer) {
+ TFLITE_DCHECK(utils_ != nullptr);
+
+ OrientParams params =
+ GetOrientParams(buffer.orientation(), output_buffer->orientation());
+ if (params.rotation_angle_deg == 0 && !params.flip.has_value()) {
+ // If no rotation or flip is needed, we will copy the buffer to
+ // output_buffer.
+ return utils_->Resize(buffer, output_buffer);
+ }
+
+ if (params.rotation_angle_deg == 0) {
+ // Only perform flip operation.
+ switch (*params.flip) {
+ case OrientParams::FlipType::kHorizontal:
+ return utils_->FlipHorizontally(buffer, output_buffer);
+ case OrientParams::FlipType::kVertical:
+ return utils_->FlipVertically(buffer, output_buffer);
+ }
+ }
+
+ if (!params.flip.has_value()) {
+ // Only perform rotation operation.
+ return utils_->Rotate(buffer, params.rotation_angle_deg, output_buffer);
+ }
+
+ // Perform rotation and flip operations.
+ // Create a temporary buffer to hold the rotation result.
+ auto tmp_buffer = absl::make_unique<uint8[]>(
+ GetBufferByteSize(output_buffer->dimension(), output_buffer->format()));
+ auto tmp_frame_buffer = FrameBuffer::Create(
+ GetPlanes(tmp_buffer.get(), output_buffer->dimension(),
+ output_buffer->format()),
+ output_buffer->dimension(), buffer.format(), buffer.orientation());
+
+ RETURN_IF_ERROR(utils_->Rotate(buffer, params.rotation_angle_deg,
+ tmp_frame_buffer.get()));
+ if (params.flip == OrientParams::FlipType::kHorizontal) {
+ return utils_->FlipHorizontally(*tmp_frame_buffer, output_buffer);
+ } else {
+ return utils_->FlipVertically(*tmp_frame_buffer, output_buffer);
+ }
+}
+
+absl::Status FrameBufferUtils::Execute(
+ const FrameBuffer& buffer,
+ const std::vector<FrameBufferOperation>& operations,
+ FrameBuffer* output_buffer) {
+ // Reference variables to swapping input and output buffers for each command.
+ FrameBuffer input_frame_buffer = buffer;
+ FrameBuffer temp_frame_buffer = buffer;
+
+ // Temporary buffers and its size to hold intermediate results.
+ int buffer1_size = 0;
+ int buffer2_size = 0;
+ std::unique_ptr<uint8[]> buffer1;
+ std::unique_ptr<uint8[]> buffer2;
+
+ for (int i = 0; i < operations.size(); i++) {
+ const FrameBufferOperation& operation = operations[i];
+
+ // The first command's input is always passed in `buffer`. Before
+ // process each command, the input_frame_buffer is pointed at the previous
+ // command's output buffer.
+ if (i == 0) {
+ input_frame_buffer = buffer;
+ } else {
+ input_frame_buffer = temp_frame_buffer;
+ }
+
+ // Calculates the resulting metadata from the command and the input.
+ FrameBuffer::Dimension new_size = GetSize(input_frame_buffer, operation);
+ FrameBuffer::Orientation new_orientation =
+ GetOrientation(input_frame_buffer, operation);
+ FrameBuffer::Format new_format = GetFormat(input_frame_buffer, operation);
+ int byte_size = GetBufferByteSize(new_size, new_format);
+
+ // The last command's output buffer is always passed in `output_buffer`.
+ // For other commands, we create temporary FrameBuffer for processing.
+ if ((i + 1) == operations.size()) {
+ temp_frame_buffer = *output_buffer;
+ // Validate the `output_buffer` metadata mathes with command line chain
+ // resulting metadata.
+ if (temp_frame_buffer.format() != new_format ||
+ temp_frame_buffer.orientation() != new_orientation ||
+ temp_frame_buffer.dimension() != new_size) {
+ return absl::InvalidArgumentError(
+ "The output metadata does not match pipeline result metadata.");
+ }
+ } else {
+ // Create a temporary buffer to hold intermediate results. For simplicity,
+ // we only create one continuous memory with no padding for intermediate
+ // results.
+ //
+ // We hold maximum 2 temporary buffers in memory at any given time.
+ //
+ // The pipeline is a linear chain. The output buffer from previous command
+ // becomes the input buffer for the next command. We simply use odd / even
+ // index to swap between buffers.
+ std::vector<FrameBuffer::Plane> planes;
+ if (i % 2 == 0) {
+ if (buffer1_size < byte_size) {
+ buffer1_size = byte_size;
+ buffer1 = absl::make_unique<uint8[]>(byte_size);
+ }
+ planes = GetPlanes(buffer1.get(), new_size, new_format);
+ } else {
+ if (buffer2_size < byte_size) {
+ buffer2_size = byte_size;
+ buffer2 = absl::make_unique<uint8[]>(byte_size);
+ }
+ planes = GetPlanes(buffer2.get(), new_size, new_format);
+ }
+ if (planes.empty()) {
+ return absl::InternalError("Failed to construct temporary buffer.");
+ }
+ temp_frame_buffer = FrameBuffer(planes, new_size, new_format,
+ new_orientation, buffer.timestamp());
+ }
+ RETURN_IF_ERROR(Execute(input_frame_buffer, operation, &temp_frame_buffer));
+ }
+ return absl::OkStatus();
+}
+
+absl::Status FrameBufferUtils::Preprocess(
+ const FrameBuffer& buffer, absl::optional<BoundingBox> bounding_box,
+ FrameBuffer* output_buffer) {
+ std::vector<FrameBufferOperation> frame_buffer_operations;
+ // Handle cropping and resizing.
+ bool needs_dimension_swap =
+ RequireDimensionSwap(buffer.orientation(), output_buffer->orientation());
+ // For intermediate steps, we need to use dimensions based on the input
+ // orientation.
+ FrameBuffer::Dimension pre_orient_dimension = output_buffer->dimension();
+ if (needs_dimension_swap) {
+ pre_orient_dimension.Swap();
+ }
+
+ if (bounding_box.has_value()) {
+ // Cropping case.
+ frame_buffer_operations.push_back(CropResizeOperation(
+ bounding_box.value().origin_x(), bounding_box.value().origin_y(),
+ FrameBuffer::Dimension{bounding_box.value().width(),
+ bounding_box.value().height()},
+ pre_orient_dimension));
+ } else if (pre_orient_dimension != buffer.dimension()) {
+ // Resizing case.
+ frame_buffer_operations.push_back(
+ CropResizeOperation(0, 0, buffer.dimension(), pre_orient_dimension));
+ }
+
+ // Handle color space conversion.
+ if (output_buffer->format() != buffer.format()) {
+ frame_buffer_operations.push_back(
+ ConvertOperation(output_buffer->format()));
+ }
+
+ // Handle orientation conversion.
+ if (output_buffer->orientation() != buffer.orientation()) {
+ frame_buffer_operations.push_back(
+ OrientOperation(output_buffer->orientation()));
+ }
+
+ // Execute the processing pipeline.
+ if (frame_buffer_operations.empty()) {
+ // Using resize to perform copy.
+ RETURN_IF_ERROR(Resize(buffer, output_buffer));
+ } else {
+ RETURN_IF_ERROR(Execute(buffer, frame_buffer_operations, output_buffer));
+ }
+ return absl::OkStatus();
+}
+
+} // namespace vision
+} // namespace task
+} // namespace tflite