blob: beb58eb4e3d15a5503ccad5349b584b743cdc80c [file] [log] [blame]
/* 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/libyuv_frame_buffer_utils.h"
#include <stdint.h>
#include <memory>
#include <string>
#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "include/libyuv.h"
#include "tensorflow_lite_support/cc/common.h"
#include "tensorflow_lite_support/cc/port/integral_types.h"
#include "tensorflow_lite_support/cc/port/status_macros.h"
#include "tensorflow_lite_support/cc/port/statusor.h"
#include "tensorflow_lite_support/cc/task/vision/utils/frame_buffer_common_utils.h"
namespace tflite {
namespace task {
namespace vision {
using ::absl::StatusCode;
using ::tflite::support::CreateStatusWithPayload;
using ::tflite::support::TfLiteSupportStatus;
namespace {
// Converts NV12 `buffer` to the `output_buffer` of the target color space.
// Supported output format includes RGB24 and YV21.
absl::Status ConvertFromNv12(const FrameBuffer& buffer,
FrameBuffer* output_buffer) {
ASSIGN_OR_RETURN(FrameBuffer::YuvData yuv_data,
FrameBuffer::GetYuvDataFromFrameBuffer(buffer));
switch (output_buffer->format()) {
case FrameBuffer::Format::kRGB: {
// The RAW format of Libyuv represents the 8-bit interleaved RGB format in
// the big endian style with R being the first byte in memory.
int ret = libyuv::NV12ToRAW(
yuv_data.y_buffer, yuv_data.y_row_stride, yuv_data.u_buffer,
yuv_data.uv_row_stride,
const_cast<uint8*>(output_buffer->plane(0).buffer),
output_buffer->plane(0).stride.row_stride_bytes,
buffer.dimension().width, buffer.dimension().height);
if (ret != 0) {
return CreateStatusWithPayload(
StatusCode::kUnknown, "Libyuv NV12ToRAW operation failed.",
TfLiteSupportStatus::kImageProcessingBackendError);
}
break;
}
case FrameBuffer::Format::kRGBA: {
// The libyuv ABGR format is interleaved RGBA format in memory.
int ret = libyuv::NV12ToABGR(
yuv_data.y_buffer, yuv_data.y_row_stride, yuv_data.u_buffer,
yuv_data.uv_row_stride,
const_cast<uint8*>(output_buffer->plane(0).buffer),
output_buffer->plane(0).stride.row_stride_bytes,
buffer.dimension().width, buffer.dimension().height);
if (ret != 0) {
return CreateStatusWithPayload(
StatusCode::kUnknown, "Libyuv NV12ToABGR operation failed.",
TfLiteSupportStatus::kImageProcessingBackendError);
}
break;
}
case FrameBuffer::Format::kYV12:
case FrameBuffer::Format::kYV21: {
ASSIGN_OR_RETURN(FrameBuffer::YuvData output_data,
FrameBuffer::GetYuvDataFromFrameBuffer(*output_buffer));
int ret = libyuv::NV12ToI420(
yuv_data.y_buffer, yuv_data.y_row_stride, yuv_data.u_buffer,
yuv_data.uv_row_stride, const_cast<uint8_t*>(output_data.y_buffer),
output_data.y_row_stride, const_cast<uint8_t*>(output_data.u_buffer),
output_data.uv_row_stride, const_cast<uint8_t*>(output_data.v_buffer),
output_data.uv_row_stride, output_buffer->dimension().width,
output_buffer->dimension().height);
if (ret != 0) {
return CreateStatusWithPayload(
StatusCode::kUnknown, "Libyuv NV12ToI420 operation failed.",
TfLiteSupportStatus::kImageProcessingBackendError);
}
break;
}
case FrameBuffer::Format::kNV21: {
ASSIGN_OR_RETURN(FrameBuffer::YuvData output_data,
FrameBuffer::GetYuvDataFromFrameBuffer(*output_buffer));
libyuv::CopyPlane(yuv_data.y_buffer, yuv_data.y_row_stride,
const_cast<uint8*>(output_data.y_buffer),
output_data.y_row_stride, buffer.dimension().width,
buffer.dimension().height);
ASSIGN_OR_RETURN(
const FrameBuffer::Dimension uv_plane_dimension,
GetUvPlaneDimension(buffer.dimension(), buffer.format()));
libyuv::SwapUVPlane(yuv_data.u_buffer, yuv_data.uv_row_stride,
const_cast<uint8*>(output_data.v_buffer),
output_data.uv_row_stride, uv_plane_dimension.width,
uv_plane_dimension.height);
break;
}
case FrameBuffer::Format::kGRAY: {
libyuv::CopyPlane(yuv_data.y_buffer, yuv_data.y_row_stride,
const_cast<uint8*>(output_buffer->plane(0).buffer),
output_buffer->plane(0).stride.row_stride_bytes,
output_buffer->dimension().width,
output_buffer->dimension().height);
break;
}
default:
return absl::InternalError(absl::StrFormat("Format %i is not supported.",
output_buffer->format()));
}
return absl::OkStatus();
}
// Converts NV21 `buffer` into the `output_buffer` of the target color space.
// Supported output format includes RGB24 and YV21.
absl::Status ConvertFromNv21(const FrameBuffer& buffer,
FrameBuffer* output_buffer) {
ASSIGN_OR_RETURN(FrameBuffer::YuvData yuv_data,
FrameBuffer::GetYuvDataFromFrameBuffer(buffer));
switch (output_buffer->format()) {
case FrameBuffer::Format::kRGB: {
// The RAW format of Libyuv represents the 8-bit interleaved RGB format in
// the big endian style with R being the first byte in memory.
int ret = libyuv::NV21ToRAW(
yuv_data.y_buffer, yuv_data.y_row_stride, yuv_data.v_buffer,
yuv_data.uv_pixel_stride,
const_cast<uint8*>(output_buffer->plane(0).buffer),
output_buffer->plane(0).stride.row_stride_bytes,
buffer.dimension().width, buffer.dimension().height);
if (ret != 0) {
return CreateStatusWithPayload(
StatusCode::kUnknown, "Libyuv NV21ToRAW operation failed.",
TfLiteSupportStatus::kImageProcessingBackendError);
}
break;
}
case FrameBuffer::Format::kRGBA: {
// The libyuv ABGR format is interleaved RGBA format in memory.
int ret = libyuv::NV21ToABGR(
yuv_data.y_buffer, yuv_data.y_row_stride, yuv_data.v_buffer,
yuv_data.uv_pixel_stride,
const_cast<uint8*>(output_buffer->plane(0).buffer),
output_buffer->plane(0).stride.row_stride_bytes,
buffer.dimension().width, buffer.dimension().height);
if (ret != 0) {
return CreateStatusWithPayload(
StatusCode::kUnknown, "Libyuv NV21ToABGR operation failed.",
TfLiteSupportStatus::kImageProcessingBackendError);
}
break;
}
case FrameBuffer::Format::kYV12:
case FrameBuffer::Format::kYV21: {
ASSIGN_OR_RETURN(FrameBuffer::YuvData output_data,
FrameBuffer::GetYuvDataFromFrameBuffer(*output_buffer));
int ret = libyuv::NV21ToI420(
yuv_data.y_buffer, yuv_data.y_row_stride, yuv_data.v_buffer,
yuv_data.uv_row_stride, const_cast<uint8_t*>(output_data.y_buffer),
output_data.y_row_stride, const_cast<uint8_t*>(output_data.u_buffer),
output_data.uv_row_stride, const_cast<uint8_t*>(output_data.v_buffer),
output_data.uv_row_stride, output_buffer->dimension().width,
output_buffer->dimension().height);
if (ret != 0) {
return CreateStatusWithPayload(
StatusCode::kUnknown, "Libyuv NV21ToI420 operation failed.",
TfLiteSupportStatus::kImageProcessingBackendError);
}
break;
}
case FrameBuffer::Format::kNV12: {
ASSIGN_OR_RETURN(FrameBuffer::YuvData output_data,
FrameBuffer::GetYuvDataFromFrameBuffer(*output_buffer));
libyuv::CopyPlane(yuv_data.y_buffer, yuv_data.y_row_stride,
const_cast<uint8*>(output_data.y_buffer),
output_data.y_row_stride, buffer.dimension().width,
buffer.dimension().height);
ASSIGN_OR_RETURN(
const FrameBuffer::Dimension uv_plane_dimension,
GetUvPlaneDimension(buffer.dimension(), buffer.format()));
libyuv::SwapUVPlane(yuv_data.v_buffer, yuv_data.uv_row_stride,
const_cast<uint8*>(output_data.u_buffer),
output_data.uv_row_stride, uv_plane_dimension.width,
uv_plane_dimension.height);
break;
}
case FrameBuffer::Format::kGRAY: {
libyuv::CopyPlane(yuv_data.y_buffer, yuv_data.y_row_stride,
const_cast<uint8*>(output_buffer->plane(0).buffer),
output_buffer->plane(0).stride.row_stride_bytes,
output_buffer->dimension().width,
output_buffer->dimension().height);
break;
}
default:
return CreateStatusWithPayload(
StatusCode::kInternal,
absl::StrFormat("Format %i is not supported.",
output_buffer->format()),
TfLiteSupportStatus::kImageProcessingError);
}
return absl::OkStatus();
}
// Converts YV12/YV21 `buffer` to the `output_buffer` of the target color space.
// Supported output format includes RGB24, NV12, and NV21.
absl::Status ConvertFromYv(const FrameBuffer& buffer,
FrameBuffer* output_buffer) {
ASSIGN_OR_RETURN(FrameBuffer::YuvData yuv_data,
FrameBuffer::GetYuvDataFromFrameBuffer(buffer));
switch (output_buffer->format()) {
case FrameBuffer::Format::kRGB: {
// The RAW format of Libyuv represents the 8-bit interleaved RGB format in
// the big endian style with R being the first byte in memory.
int ret = libyuv::I420ToRAW(
yuv_data.y_buffer, yuv_data.y_row_stride, yuv_data.u_buffer,
yuv_data.uv_row_stride, yuv_data.v_buffer, yuv_data.uv_row_stride,
const_cast<uint8*>(output_buffer->plane(0).buffer),
output_buffer->plane(0).stride.row_stride_bytes,
buffer.dimension().width, buffer.dimension().height);
if (ret != 0) {
return CreateStatusWithPayload(
StatusCode::kUnknown, "Libyuv I420ToRAW operation failed.",
TfLiteSupportStatus::kImageProcessingBackendError);
}
break;
}
case FrameBuffer::Format::kRGBA: {
// The libyuv ABGR format is interleaved RGBA format in memory.
int ret = libyuv::I420ToABGR(
yuv_data.y_buffer, yuv_data.y_row_stride, yuv_data.u_buffer,
yuv_data.uv_row_stride, yuv_data.v_buffer, yuv_data.uv_row_stride,
const_cast<uint8*>(output_buffer->plane(0).buffer),
output_buffer->plane(0).stride.row_stride_bytes,
buffer.dimension().width, buffer.dimension().height);
if (ret != 0) {
return CreateStatusWithPayload(
StatusCode::kUnknown, "Libyuv I420ToABGR operation failed.",
TfLiteSupportStatus::kImageProcessingBackendError);
}
break;
}
case FrameBuffer::Format::kNV12: {
ASSIGN_OR_RETURN(FrameBuffer::YuvData output_data,
FrameBuffer::GetYuvDataFromFrameBuffer(*output_buffer));
int ret = libyuv::I420ToNV12(
yuv_data.y_buffer, yuv_data.y_row_stride, yuv_data.u_buffer,
yuv_data.uv_row_stride, yuv_data.v_buffer, yuv_data.uv_row_stride,
const_cast<uint8*>(output_data.y_buffer), output_data.y_row_stride,
const_cast<uint8*>(output_data.u_buffer), output_data.uv_row_stride,
output_buffer->dimension().width, output_buffer->dimension().height);
if (ret != 0) {
return CreateStatusWithPayload(
StatusCode::kUnknown, "Libyuv I420ToNV12 operation failed.",
TfLiteSupportStatus::kImageProcessingBackendError);
}
break;
}
case FrameBuffer::Format::kNV21: {
ASSIGN_OR_RETURN(FrameBuffer::YuvData output_data,
FrameBuffer::GetYuvDataFromFrameBuffer(*output_buffer));
int ret = libyuv::I420ToNV21(
yuv_data.y_buffer, yuv_data.y_row_stride, yuv_data.u_buffer,
yuv_data.uv_row_stride, yuv_data.v_buffer, yuv_data.uv_row_stride,
const_cast<uint8*>(output_data.y_buffer), output_data.y_row_stride,
const_cast<uint8*>(output_data.v_buffer), output_data.uv_row_stride,
output_buffer->dimension().width, output_buffer->dimension().height);
if (ret != 0) {
return CreateStatusWithPayload(
StatusCode::kUnknown, "Libyuv I420ToNV21 operation failed.",
TfLiteSupportStatus::kImageProcessingBackendError);
}
break;
}
case FrameBuffer::Format::kGRAY: {
libyuv::CopyPlane(yuv_data.y_buffer, yuv_data.y_row_stride,
const_cast<uint8*>(output_buffer->plane(0).buffer),
output_buffer->plane(0).stride.row_stride_bytes,
output_buffer->dimension().width,
output_buffer->dimension().height);
break;
}
case FrameBuffer::Format::kYV12:
case FrameBuffer::Format::kYV21: {
ASSIGN_OR_RETURN(FrameBuffer::YuvData output_yuv_data,
FrameBuffer::GetYuvDataFromFrameBuffer(*output_buffer));
ASSIGN_OR_RETURN(
const FrameBuffer::Dimension uv_plane_dimension,
GetUvPlaneDimension(buffer.dimension(), buffer.format()));
libyuv::CopyPlane(yuv_data.y_buffer, yuv_data.y_row_stride,
const_cast<uint8*>(output_yuv_data.y_buffer),
output_yuv_data.y_row_stride, buffer.dimension().width,
buffer.dimension().height);
libyuv::CopyPlane(yuv_data.u_buffer, yuv_data.uv_row_stride,
const_cast<uint8*>(output_yuv_data.u_buffer),
output_yuv_data.uv_row_stride, uv_plane_dimension.width,
uv_plane_dimension.height);
libyuv::CopyPlane(yuv_data.v_buffer, yuv_data.uv_row_stride,
const_cast<uint8*>(output_yuv_data.v_buffer),
output_yuv_data.uv_row_stride, uv_plane_dimension.width,
uv_plane_dimension.height);
break;
}
default:
return CreateStatusWithPayload(
StatusCode::kInternal,
absl::StrFormat("Format %i is not supported.",
output_buffer->format()),
TfLiteSupportStatus::kImageProcessingError);
}
return absl::OkStatus();
}
// Resizes YV12/YV21 `buffer` to the target `output_buffer`.
absl::Status ResizeYv(const FrameBuffer& buffer, FrameBuffer* output_buffer) {
ASSIGN_OR_RETURN(FrameBuffer::YuvData input_data,
FrameBuffer::GetYuvDataFromFrameBuffer(buffer));
ASSIGN_OR_RETURN(FrameBuffer::YuvData output_data,
FrameBuffer::GetYuvDataFromFrameBuffer(*output_buffer));
// TODO(b/151217096): Choose the optimal image resizing filter to optimize
// the model inference performance.
int ret = libyuv::I420Scale(
input_data.y_buffer, input_data.y_row_stride, input_data.u_buffer,
input_data.uv_row_stride, input_data.v_buffer, input_data.uv_row_stride,
buffer.dimension().width, buffer.dimension().height,
const_cast<uint8_t*>(output_data.y_buffer), output_data.y_row_stride,
const_cast<uint8_t*>(output_data.u_buffer), output_data.uv_row_stride,
const_cast<uint8_t*>(output_data.v_buffer), output_data.uv_row_stride,
output_buffer->dimension().width, output_buffer->dimension().height,
libyuv::FilterMode::kFilterBilinear);
if (ret != 0) {
return CreateStatusWithPayload(
StatusCode::kUnknown, "Libyuv I420Scale operation failed.",
TfLiteSupportStatus::kImageProcessingBackendError);
}
return absl::OkStatus();
}
// Resizes NV12/NV21 `buffer` to the target `output_buffer`.
absl::Status ResizeNv(const FrameBuffer& buffer, FrameBuffer* output_buffer) {
const int buffer_size =
GetFrameBufferByteSize(buffer.dimension(), FrameBuffer::Format::kYV21);
auto yuv_raw_buffer = absl::make_unique<uint8[]>(buffer_size);
ASSIGN_OR_RETURN(
std::unique_ptr<FrameBuffer> yuv_buffer,
CreateFromRawBuffer(yuv_raw_buffer.get(), buffer.dimension(),
FrameBuffer::Format::kYV21, buffer.orientation()));
// TODO(b/151375918): Current implementation is a workaround by converting
// input NV12/NV21 buffer to the YV12 formats, resizing the YV12 buffer, and
// converting the resized YV12 buffer back to the target format. Consider
// optimizes this by adding the support of NV12/NV21 resizing in Libyuv.
if (buffer.format() == FrameBuffer::Format::kNV12) {
RETURN_IF_ERROR(ConvertFromNv12(buffer, yuv_buffer.get()));
} else if (buffer.format() == FrameBuffer::Format::kNV21) {
RETURN_IF_ERROR(ConvertFromNv21(buffer, yuv_buffer.get()));
} else {
return CreateStatusWithPayload(
StatusCode::kInternal,
absl::StrFormat("Format %i is not supported.", buffer.format()),
TfLiteSupportStatus::kImageProcessingError);
}
const int resized_buffer_size = GetFrameBufferByteSize(
output_buffer->dimension(), FrameBuffer::Format::kYV12);
auto resized_yuv_raw_buffer = absl::make_unique<uint8[]>(resized_buffer_size);
ASSIGN_OR_RETURN(std::unique_ptr<FrameBuffer> resized_yuv_buffer,
CreateFromRawBuffer(resized_yuv_raw_buffer.get(),
output_buffer->dimension(),
FrameBuffer::Format::kYV12,
output_buffer->orientation()));
RETURN_IF_ERROR(ResizeYv(*yuv_buffer, resized_yuv_buffer.get()));
RETURN_IF_ERROR(ConvertFromYv(*resized_yuv_buffer, output_buffer));
return absl::OkStatus();
}
// Converts `buffer` to libyuv ARGB format and stores the conversion result
// in `dest_argb`.
absl::Status ConvertRgbToArgb(const FrameBuffer& buffer, uint8* dest_argb,
int dest_stride_argb) {
RETURN_IF_ERROR(ValidateBufferPlaneMetadata(buffer));
if (buffer.format() != FrameBuffer::Format::kRGB) {
return CreateStatusWithPayload(StatusCode::kInternal,
"RGB input format is expected.",
TfLiteSupportStatus::kImageProcessingError);
}
if (dest_argb == nullptr || dest_stride_argb <= 0) {
return CreateStatusWithPayload(
StatusCode::kInternal,
"Invalid destination arguments for ConvertRgbToArgb.",
TfLiteSupportStatus::kImageProcessingError);
}
if (buffer.plane_count() > 1) {
return CreateStatusWithPayload(
StatusCode::kInternal,
absl::StrFormat("Only single plane is supported for format %i.",
buffer.format()),
TfLiteSupportStatus::kImageProcessingError);
}
int ret = libyuv::RGB24ToARGB(
buffer.plane(0).buffer, buffer.plane(0).stride.row_stride_bytes,
dest_argb, dest_stride_argb, buffer.dimension().width,
buffer.dimension().height);
if (ret != 0) {
return CreateStatusWithPayload(
StatusCode::kUnknown, "Libyuv RGB24ToARGB operation failed.",
TfLiteSupportStatus::kImageProcessingBackendError);
}
return absl::OkStatus();
}
// Converts `src_argb` in libyuv ARGB format to FrameBuffer::kRGB format and
// stores the conversion result in `output_buffer`.
absl::Status ConvertArgbToRgb(uint8* src_argb, int src_stride_argb,
FrameBuffer* output_buffer) {
RETURN_IF_ERROR(ValidateBufferPlaneMetadata(*output_buffer));
if (output_buffer->format() != FrameBuffer::Format::kRGB) {
return absl::InternalError("RGB input format is expected.");
}
if (src_argb == nullptr || src_stride_argb <= 0) {
return CreateStatusWithPayload(
StatusCode::kInternal, "Invalid source arguments for ConvertArgbToRgb.",
TfLiteSupportStatus::kImageProcessingError);
}
if (output_buffer->plane_count() > 1) {
return CreateStatusWithPayload(
StatusCode::kInternal,
absl::StrFormat("Only single plane is supported for format %i.",
output_buffer->format()),
TfLiteSupportStatus::kImageProcessingError);
}
int ret = libyuv::ARGBToRGB24(
src_argb, src_stride_argb,
const_cast<uint8*>(output_buffer->plane(0).buffer),
output_buffer->plane(0).stride.row_stride_bytes,
output_buffer->dimension().width, output_buffer->dimension().height);
if (ret != 0) {
return CreateStatusWithPayload(
StatusCode::kUnknown, "Libyuv ARGBToRGB24 operation failed.",
TfLiteSupportStatus::kImageProcessingBackendError);
}
return absl::OkStatus();
}
// Converts `buffer` in FrameBuffer::kRGBA format to libyuv ARGB (BGRA in
// memory) format and stores the conversion result in `dest_argb`.
absl::Status ConvertRgbaToArgb(const FrameBuffer& buffer, uint8* dest_argb,
int dest_stride_argb) {
RETURN_IF_ERROR(ValidateBufferPlaneMetadata(buffer));
if (buffer.format() != FrameBuffer::Format::kRGBA) {
return CreateStatusWithPayload(
StatusCode::kInternal, "RGBA input format is expected.",
TfLiteSupportStatus::kImageProcessingBackendError);
}
if (dest_argb == nullptr || dest_stride_argb <= 0) {
return CreateStatusWithPayload(
StatusCode::kInternal,
"Invalid source arguments for ConvertRgbaToArgb.",
TfLiteSupportStatus::kImageProcessingBackendError);
}
if (buffer.plane_count() > 1) {
return CreateStatusWithPayload(
StatusCode::kInternal,
absl::StrFormat("Only single plane is supported for format %i.",
buffer.format()),
TfLiteSupportStatus::kImageProcessingError);
}
int ret = libyuv::ABGRToARGB(
buffer.plane(0).buffer, buffer.plane(0).stride.row_stride_bytes,
dest_argb, dest_stride_argb, buffer.dimension().width,
buffer.dimension().height);
if (ret != 0) {
return CreateStatusWithPayload(
StatusCode::kInternal, "Libyuv ABGRToARGB operation failed.",
TfLiteSupportStatus::kImageProcessingBackendError);
}
return absl::OkStatus();
}
// Converts kRGB `buffer` to the `output_buffer` of the target color space.
absl::Status ConvertFromRgb(const FrameBuffer& buffer,
FrameBuffer* output_buffer) {
if (output_buffer->format() == FrameBuffer::Format::kGRAY) {
int ret = libyuv::RAWToJ400(
buffer.plane(0).buffer, buffer.plane(0).stride.row_stride_bytes,
const_cast<uint8*>(output_buffer->plane(0).buffer),
output_buffer->plane(0).stride.row_stride_bytes,
buffer.dimension().width, buffer.dimension().height);
if (ret != 0) {
return CreateStatusWithPayload(
StatusCode::kInternal, "Libyuv RAWToJ400 operation failed.",
TfLiteSupportStatus::kImageProcessingBackendError);
}
return absl::OkStatus();
} else if (output_buffer->format() == FrameBuffer::Format::kYV12 ||
output_buffer->format() == FrameBuffer::Format::kYV21 ||
output_buffer->format() == FrameBuffer::Format::kNV12 ||
output_buffer->format() == FrameBuffer::Format::kNV21) {
// libyuv does not support conversion directly from kRGB to kNV12 / kNV21.
// For kNV12 / kNV21, the implementation converts the kRGB to I420,
// then converts I420 to kNV12 / kNV21.
// TODO(b/153000936): use libyuv::RawToNV12 / libyuv::RawToNV21 when they
// are ready.
FrameBuffer::YuvData yuv_data;
std::unique_ptr<uint8[]> tmp_yuv_buffer;
std::unique_ptr<FrameBuffer> yuv_frame_buffer;
if (output_buffer->format() == FrameBuffer::Format::kNV12 ||
output_buffer->format() == FrameBuffer::Format::kNV21) {
tmp_yuv_buffer = absl::make_unique<uint8[]>(
GetFrameBufferByteSize(buffer.dimension(), output_buffer->format()));
ASSIGN_OR_RETURN(
yuv_frame_buffer,
CreateFromRawBuffer(tmp_yuv_buffer.get(), buffer.dimension(),
FrameBuffer::Format::kYV21,
output_buffer->orientation()));
ASSIGN_OR_RETURN(
yuv_data, FrameBuffer::GetYuvDataFromFrameBuffer(*yuv_frame_buffer));
} else {
ASSIGN_OR_RETURN(yuv_data,
FrameBuffer::GetYuvDataFromFrameBuffer(*output_buffer));
}
int ret = libyuv::RAWToI420(
buffer.plane(0).buffer, buffer.plane(0).stride.row_stride_bytes,
const_cast<uint8*>(yuv_data.y_buffer), yuv_data.y_row_stride,
const_cast<uint8*>(yuv_data.u_buffer), yuv_data.uv_row_stride,
const_cast<uint8*>(yuv_data.v_buffer), yuv_data.uv_row_stride,
buffer.dimension().width, buffer.dimension().height);
if (ret != 0) {
return CreateStatusWithPayload(
StatusCode::kInternal, "Libyuv RAWToI420 operation failed.",
TfLiteSupportStatus::kImageProcessingBackendError);
}
if (output_buffer->format() == FrameBuffer::Format::kNV12 ||
output_buffer->format() == FrameBuffer::Format::kNV21) {
return ConvertFromYv(*yuv_frame_buffer, output_buffer);
}
return absl::OkStatus();
}
return CreateStatusWithPayload(
StatusCode::kInternal,
absl::StrFormat("Format %i is not supported.", output_buffer->format()),
TfLiteSupportStatus::kImageProcessingError);
}
// Converts kRGBA `buffer` to the `output_buffer` of the target color space.
absl::Status ConvertFromRgba(const FrameBuffer& buffer,
FrameBuffer* output_buffer) {
switch (output_buffer->format()) {
case FrameBuffer::Format::kGRAY: {
// libyuv does not support convert kRGBA (ABGR) foramat. In this method,
// the implementation converts kRGBA format to ARGB and use ARGB buffer
// for conversion.
// TODO(b/141181395): Use libyuv::ABGRToJ400 when it is ready.
// Convert kRGBA to ARGB
int argb_buffer_size = GetFrameBufferByteSize(buffer.dimension(),
FrameBuffer::Format::kRGBA);
auto argb_buffer = absl::make_unique<uint8[]>(argb_buffer_size);
const int argb_row_bytes = buffer.dimension().width * kRgbaPixelBytes;
RETURN_IF_ERROR(
ConvertRgbaToArgb(buffer, argb_buffer.get(), argb_row_bytes));
// Convert ARGB to kGRAY
int ret = libyuv::ARGBToJ400(
argb_buffer.get(), argb_row_bytes,
const_cast<uint8*>(output_buffer->plane(0).buffer),
output_buffer->plane(0).stride.row_stride_bytes,
buffer.dimension().width, buffer.dimension().height);
if (ret != 0) {
return CreateStatusWithPayload(
StatusCode::kUnknown, "Libyuv ARGBToJ400 operation failed.",
TfLiteSupportStatus::kImageProcessingBackendError);
}
break;
}
case FrameBuffer::Format::kNV12: {
ASSIGN_OR_RETURN(FrameBuffer::YuvData output_data,
FrameBuffer::GetYuvDataFromFrameBuffer(*output_buffer));
int ret = libyuv::ABGRToNV12(
buffer.plane(0).buffer, buffer.plane(0).stride.row_stride_bytes,
const_cast<uint8*>(output_data.y_buffer), output_data.y_row_stride,
const_cast<uint8*>(output_data.u_buffer), output_data.uv_row_stride,
buffer.dimension().width, buffer.dimension().height);
if (ret != 0) {
return CreateStatusWithPayload(
StatusCode::kUnknown, "Libyuv ABGRToNV12 operation failed.",
TfLiteSupportStatus::kImageProcessingBackendError);
}
break;
}
case FrameBuffer::Format::kNV21: {
ASSIGN_OR_RETURN(FrameBuffer::YuvData output_data,
FrameBuffer::GetYuvDataFromFrameBuffer(*output_buffer));
int ret = libyuv::ABGRToNV21(
buffer.plane(0).buffer, buffer.plane(0).stride.row_stride_bytes,
const_cast<uint8*>(output_data.y_buffer), output_data.y_row_stride,
const_cast<uint8*>(output_data.v_buffer), output_data.uv_row_stride,
buffer.dimension().width, buffer.dimension().height);
if (ret != 0) {
return CreateStatusWithPayload(
StatusCode::kUnknown, "Libyuv ABGRToNV21 operation failed.",
TfLiteSupportStatus::kImageProcessingBackendError);
}
break;
}
case FrameBuffer::Format::kYV12:
case FrameBuffer::Format::kYV21: {
ASSIGN_OR_RETURN(FrameBuffer::YuvData output_data,
FrameBuffer::GetYuvDataFromFrameBuffer(*output_buffer));
int ret = libyuv::ABGRToI420(
buffer.plane(0).buffer, buffer.plane(0).stride.row_stride_bytes,
const_cast<uint8*>(output_data.y_buffer), output_data.y_row_stride,
const_cast<uint8*>(output_data.u_buffer), output_data.uv_row_stride,
const_cast<uint8*>(output_data.v_buffer), output_data.uv_row_stride,
buffer.dimension().width, buffer.dimension().height);
if (ret != 0) {
return CreateStatusWithPayload(
StatusCode::kUnknown, "Libyuv ABGRToI420 operation failed.",
TfLiteSupportStatus::kImageProcessingBackendError);
}
break;
}
case FrameBuffer::Format::kRGB: {
// ARGB is BGRA in memory and RGB24 is BGR in memory. The removal of the
// alpha channel will not impact the RGB ordering.
int ret = libyuv::ARGBToRGB24(
buffer.plane(0).buffer, buffer.plane(0).stride.row_stride_bytes,
const_cast<uint8*>(output_buffer->plane(0).buffer),
output_buffer->plane(0).stride.row_stride_bytes,
buffer.dimension().width, buffer.dimension().height);
if (ret != 0) {
return CreateStatusWithPayload(
StatusCode::kUnknown, "Libyuv ABGRToRGB24 operation failed.",
TfLiteSupportStatus::kImageProcessingBackendError);
}
break;
}
default:
return CreateStatusWithPayload(
StatusCode::kInternal,
absl::StrFormat("Convert Rgba to format %i is not supported.",
output_buffer->format()),
TfLiteSupportStatus::kImageProcessingError);
}
return absl::OkStatus();
}
// Returns libyuv rotation based on counter-clockwise angle_deg.
libyuv::RotationMode GetLibyuvRotationMode(int angle_deg) {
switch (angle_deg) {
case 90:
return libyuv::kRotate270;
case 270:
return libyuv::kRotate90;
case 180:
return libyuv::kRotate180;
default:
return libyuv::kRotate0;
}
}
absl::Status RotateRgba(const FrameBuffer& buffer, int angle_deg,
FrameBuffer* output_buffer) {
if (buffer.plane_count() > 1) {
return CreateStatusWithPayload(
StatusCode::kInternal,
absl::StrFormat("Only single plane is supported for format %i.",
buffer.format()),
TfLiteSupportStatus::kImageProcessingError);
}
// libyuv::ARGBRotate assumes RGBA buffer is in the interleaved format.
int ret = libyuv::ARGBRotate(
buffer.plane(0).buffer, buffer.plane(0).stride.row_stride_bytes,
const_cast<uint8*>(output_buffer->plane(0).buffer),
output_buffer->plane(0).stride.row_stride_bytes, buffer.dimension().width,
buffer.dimension().height, GetLibyuvRotationMode(angle_deg % 360));
if (ret != 0) {
return CreateStatusWithPayload(
StatusCode::kUnknown, "Libyuv ARGBRotate operation failed.",
TfLiteSupportStatus::kImageProcessingBackendError);
}
return absl::OkStatus();
}
absl::Status RotateRgb(const FrameBuffer& buffer, int angle_deg,
FrameBuffer* output_buffer) {
// libyuv does not support rotate kRGB (RGB24) foramat. In this method, the
// implementation converts kRGB format to ARGB and use ARGB buffer for
// rotation. The result is then convert back to RGB.
// Convert RGB to ARGB
int argb_buffer_size =
GetFrameBufferByteSize(buffer.dimension(), FrameBuffer::Format::kRGBA);
auto argb_buffer = absl::make_unique<uint8[]>(argb_buffer_size);
const int argb_row_bytes = buffer.dimension().width * kRgbaPixelBytes;
RETURN_IF_ERROR(ConvertRgbToArgb(buffer, argb_buffer.get(), argb_row_bytes));
// Rotate ARGB
auto argb_rotated_buffer = absl::make_unique<uint8[]>(argb_buffer_size);
int rotated_row_bytes = output_buffer->dimension().width * kRgbaPixelBytes;
// TODO(b/151954340): Optimize the current implementation by utilizing
// ARGBMirror for 180 degree rotation.
int ret = libyuv::ARGBRotate(
argb_buffer.get(), argb_row_bytes, argb_rotated_buffer.get(),
rotated_row_bytes, buffer.dimension().width, buffer.dimension().height,
GetLibyuvRotationMode(angle_deg % 360));
if (ret != 0) {
return CreateStatusWithPayload(
StatusCode::kUnknown, "Libyuv ARGBRotate operation failed.",
TfLiteSupportStatus::kImageProcessingBackendError);
}
// Convert ARGB to RGB
return ConvertArgbToRgb(argb_rotated_buffer.get(), rotated_row_bytes,
output_buffer);
}
absl::Status RotateGray(const FrameBuffer& buffer, int angle_deg,
FrameBuffer* output_buffer) {
if (buffer.plane_count() > 1) {
return CreateStatusWithPayload(
StatusCode::kInternal,
absl::StrFormat("Only single plane is supported for format %i.",
buffer.format()),
TfLiteSupportStatus::kImageProcessingError);
}
int ret = libyuv::RotatePlane(
buffer.plane(0).buffer, buffer.plane(0).stride.row_stride_bytes,
const_cast<uint8*>(output_buffer->plane(0).buffer),
output_buffer->plane(0).stride.row_stride_bytes, buffer.dimension().width,
buffer.dimension().height, GetLibyuvRotationMode(angle_deg % 360));
if (ret != 0) {
return CreateStatusWithPayload(
StatusCode::kUnknown, "Libyuv RotatePlane operation failed.",
TfLiteSupportStatus::kImageProcessingBackendError);
}
return absl::OkStatus();
}
// Rotates YV12/YV21 frame buffer.
absl::Status RotateYv(const FrameBuffer& buffer, int angle_deg,
FrameBuffer* output_buffer) {
ASSIGN_OR_RETURN(FrameBuffer::YuvData input_data,
FrameBuffer::GetYuvDataFromFrameBuffer(buffer));
ASSIGN_OR_RETURN(FrameBuffer::YuvData output_data,
FrameBuffer::GetYuvDataFromFrameBuffer(*output_buffer));
int ret = libyuv::I420Rotate(
input_data.y_buffer, input_data.y_row_stride, input_data.u_buffer,
input_data.uv_row_stride, input_data.v_buffer, input_data.uv_row_stride,
const_cast<uint8*>(output_data.y_buffer), output_data.y_row_stride,
const_cast<uint8*>(output_data.u_buffer), output_data.uv_row_stride,
const_cast<uint8*>(output_data.v_buffer), output_data.uv_row_stride,
buffer.dimension().width, buffer.dimension().height,
GetLibyuvRotationMode(angle_deg));
if (ret != 0) {
return CreateStatusWithPayload(
StatusCode::kUnknown, "Libyuv I420Rotate operation failed.",
TfLiteSupportStatus::kImageProcessingBackendError);
}
return absl::OkStatus();
}
// Rotates NV12/NV21 frame buffer.
// TODO(b/152097364): Refactor NV12/NV21 rotation after libyuv explicitly
// support that.
absl::Status RotateNv(const FrameBuffer& buffer, int angle_deg,
FrameBuffer* output_buffer) {
if (buffer.format() != FrameBuffer::Format::kNV12 &&
buffer.format() != FrameBuffer::Format::kNV21) {
return CreateStatusWithPayload(StatusCode::kInternal,
"kNV12 or kNV21 input formats are expected.",
TfLiteSupportStatus::kImageProcessingError);
}
ASSIGN_OR_RETURN(FrameBuffer::YuvData input_data,
FrameBuffer::GetYuvDataFromFrameBuffer(buffer));
ASSIGN_OR_RETURN(FrameBuffer::YuvData output_data,
FrameBuffer::GetYuvDataFromFrameBuffer(*output_buffer));
const int rotated_buffer_size = GetFrameBufferByteSize(
output_buffer->dimension(), FrameBuffer::Format::kYV21);
auto rotated_yuv_raw_buffer = absl::make_unique<uint8[]>(rotated_buffer_size);
ASSIGN_OR_RETURN(std::unique_ptr<FrameBuffer> rotated_yuv_buffer,
CreateFromRawBuffer(
rotated_yuv_raw_buffer.get(), output_buffer->dimension(),
/*target_format=*/FrameBuffer::Format::kYV21,
output_buffer->orientation()));
ASSIGN_OR_RETURN(FrameBuffer::YuvData rotated_yuv_data,
FrameBuffer::GetYuvDataFromFrameBuffer(*rotated_yuv_buffer));
// Get the first chroma plane and use it as the u plane. This is a workaround
// for optimizing NV21 rotation. For NV12, the implementation is logical
// correct. For NV21, use v plane as u plane will make the UV planes swapped
// in the intermediate rotated I420 frame. The output buffer is finally built
// by merging the swapped UV planes which produces V first interleaved UV
// buffer.
const uint8* chroma_buffer = buffer.format() == FrameBuffer::Format::kNV12
? input_data.u_buffer
: input_data.v_buffer;
// Rotate the Y plane and store into the Y plane in `output_buffer`. Rotate
// the interleaved UV plane and store into the interleaved UV plane in
// `rotated_yuv_buffer`.
int ret = libyuv::NV12ToI420Rotate(
input_data.y_buffer, input_data.y_row_stride, chroma_buffer,
input_data.uv_row_stride, const_cast<uint8*>(output_data.y_buffer),
output_data.y_row_stride, const_cast<uint8*>(rotated_yuv_data.u_buffer),
rotated_yuv_data.uv_row_stride,
const_cast<uint8*>(rotated_yuv_data.v_buffer),
rotated_yuv_data.uv_row_stride, buffer.dimension().width,
buffer.dimension().height, GetLibyuvRotationMode(angle_deg % 360));
if (ret != 0) {
return CreateStatusWithPayload(
StatusCode::kUnknown, "Libyuv Nv12ToI420Rotate operation failed.",
TfLiteSupportStatus::kImageProcessingBackendError);
}
// Merge rotated UV planes into the output buffer. For NV21, the UV buffer of
// the intermediate I420 frame is swapped. MergeUVPlane builds the interleaved
// VU buffer for NV21 by putting the U plane in the I420 frame which is
// actually the V plane from the input buffer first.
const uint8* output_chroma_buffer =
buffer.format() == FrameBuffer::Format::kNV12 ? output_data.u_buffer
: output_data.v_buffer;
// The width and height arguments of `libyuv::MergeUVPlane()` represent the
// width and height of the UV planes.
libyuv::MergeUVPlane(
rotated_yuv_data.u_buffer, rotated_yuv_data.uv_row_stride,
rotated_yuv_data.v_buffer, rotated_yuv_data.uv_row_stride,
const_cast<uint8*>(output_chroma_buffer), output_data.uv_row_stride,
(output_buffer->dimension().width + 1) / 2,
(output_buffer->dimension().height + 1) / 2);
return absl::OkStatus();
}
// This method only supports kGRAY, kRGB, and kRGBA format.
absl::Status FlipPlaneVertically(const FrameBuffer& buffer,
FrameBuffer* output_buffer) {
if (buffer.plane_count() > 1) {
return CreateStatusWithPayload(
StatusCode::kInternal,
absl::StrFormat("Only single plane is supported for format %i.",
buffer.format()),
TfLiteSupportStatus::kImageProcessingError);
}
ASSIGN_OR_RETURN(int pixel_stride, GetPixelStrides(buffer.format()));
// Flip vertically is achieved by passing in negative height.
libyuv::CopyPlane(buffer.plane(0).buffer,
buffer.plane(0).stride.row_stride_bytes,
const_cast<uint8*>(output_buffer->plane(0).buffer),
output_buffer->plane(0).stride.row_stride_bytes,
output_buffer->dimension().width * pixel_stride,
-output_buffer->dimension().height);
return absl::OkStatus();
}
// This method only supports kGRAY, kRGBA, and kRGB formats.
absl::Status CropPlane(const FrameBuffer& buffer, int x0, int y0, int x1,
int y1, FrameBuffer* output_buffer) {
if (buffer.plane_count() > 1) {
return CreateStatusWithPayload(
StatusCode::kInternal,
absl::StrFormat("Only single plane is supported for format %i.",
buffer.format()),
TfLiteSupportStatus::kImageProcessingError);
}
ASSIGN_OR_RETURN(int pixel_stride, GetPixelStrides(buffer.format()));
FrameBuffer::Dimension crop_dimension = GetCropDimension(x0, x1, y0, y1);
// Cropping is achieved by adjusting origin to (x0, y0).
int adjusted_offset =
buffer.plane(0).stride.row_stride_bytes * y0 + x0 * pixel_stride;
libyuv::CopyPlane(buffer.plane(0).buffer + adjusted_offset,
buffer.plane(0).stride.row_stride_bytes,
const_cast<uint8*>(output_buffer->plane(0).buffer),
output_buffer->plane(0).stride.row_stride_bytes,
crop_dimension.width * pixel_stride, crop_dimension.height);
return absl::OkStatus();
}
// Crops NV12/NV21 FrameBuffer to the subregion defined by the top left pixel
// position (x0, y0) and the bottom right pixel position (x1, y1).
absl::Status CropNv(const FrameBuffer& buffer, int x0, int y0, int x1, int y1,
FrameBuffer* output_buffer) {
ASSIGN_OR_RETURN(FrameBuffer::YuvData input_data,
FrameBuffer::GetYuvDataFromFrameBuffer(buffer));
ASSIGN_OR_RETURN(FrameBuffer::YuvData output_data,
FrameBuffer::GetYuvDataFromFrameBuffer(*output_buffer));
// Crop Y plane by copying the buffer with the origin offset to (x0, y0).
int crop_offset_y = input_data.y_row_stride * y0 + x0;
int crop_width = x1 - x0 + 1;
int crop_height = y1 - y0 + 1;
libyuv::CopyPlane(input_data.y_buffer + crop_offset_y,
input_data.y_row_stride,
const_cast<uint8*>(output_data.y_buffer),
output_data.y_row_stride, crop_width, crop_height);
// Crop chroma plane by copying the buffer with the origin offset to
// (x0 / 2, y0 / 2);
// TODO(b/152629712): Investigate the impact of color shifting caused by the
// bounding box with odd X or Y starting positions.
int crop_offset_chroma = input_data.uv_row_stride * (y0 / 2) +
input_data.uv_pixel_stride * (x0 / 2);
ASSIGN_OR_RETURN(const uint8* input_chroma_buffer, GetUvRawBuffer(buffer));
ASSIGN_OR_RETURN(const uint8* output_chroma_buffer,
GetUvRawBuffer(*output_buffer));
libyuv::CopyPlane(
input_chroma_buffer + crop_offset_chroma, input_data.uv_row_stride,
const_cast<uint8*>(output_chroma_buffer), output_data.uv_row_stride,
/*width=*/(crop_width + 1) / 2 * 2, /*height=*/(crop_height + 1) / 2);
return absl::OkStatus();
}
// Crops YV12/YV21 FrameBuffer to the subregion defined by the top left pixel
// position (x0, y0) and the bottom right pixel position (x1, y1).
absl::Status CropYv(const FrameBuffer& buffer, int x0, int y0, int x1, int y1,
FrameBuffer* output_buffer) {
ASSIGN_OR_RETURN(FrameBuffer::YuvData input_data,
FrameBuffer::GetYuvDataFromFrameBuffer(buffer));
ASSIGN_OR_RETURN(FrameBuffer::YuvData output_data,
FrameBuffer::GetYuvDataFromFrameBuffer(*output_buffer));
// Crop Y plane by copying the buffer with the origin offset to (x0, y0).
int crop_offset_y = input_data.y_row_stride * y0 + x0;
FrameBuffer::Dimension crop_dimension = GetCropDimension(x0, x1, y0, y1);
libyuv::CopyPlane(
input_data.y_buffer + crop_offset_y, input_data.y_row_stride,
const_cast<uint8*>(output_data.y_buffer), output_data.y_row_stride,
crop_dimension.width, crop_dimension.height);
// Crop U plane by copying the buffer with the origin offset to
// (x0 / 2, y0 / 2).
ASSIGN_OR_RETURN(const FrameBuffer::Dimension crop_uv_dimension,
GetUvPlaneDimension(crop_dimension, buffer.format()));
// TODO(b/152629712): Investigate the impact of color shifting caused by the
// bounding box with odd X or Y starting positions.
int crop_offset_chroma = input_data.uv_row_stride * (y0 / 2) +
input_data.uv_pixel_stride * (x0 / 2);
libyuv::CopyPlane(
input_data.u_buffer + crop_offset_chroma, input_data.uv_row_stride,
const_cast<uint8*>(output_data.u_buffer), output_data.uv_row_stride,
crop_uv_dimension.width, crop_uv_dimension.height);
// Crop V plane by copying the buffer with the origin offset to
// (x0 / 2, y0 / 2);
libyuv::CopyPlane(
input_data.v_buffer + crop_offset_chroma, input_data.uv_row_stride,
const_cast<uint8*>(output_data.v_buffer), output_data.uv_row_stride,
/*width=*/(crop_dimension.width + 1) / 2,
/*height=*/(crop_dimension.height + 1) / 2);
return absl::OkStatus();
}
absl::Status CropResizeYuv(const FrameBuffer& buffer, int x0, int y0, int x1,
int y1, FrameBuffer* output_buffer) {
FrameBuffer::Dimension crop_dimension = GetCropDimension(x0, x1, y0, y1);
if (crop_dimension == output_buffer->dimension()) {
switch (buffer.format()) {
case FrameBuffer::Format::kNV12:
case FrameBuffer::Format::kNV21:
return CropNv(buffer, x0, y0, x1, y1, output_buffer);
case FrameBuffer::Format::kYV12:
case FrameBuffer::Format::kYV21:
return CropYv(buffer, x0, y0, x1, y1, output_buffer);
default:
return CreateStatusWithPayload(
StatusCode::kInternal,
absl::StrFormat("Format %i is not supported.", buffer.format()),
TfLiteSupportStatus::kImageProcessingError);
}
}
ASSIGN_OR_RETURN(FrameBuffer::YuvData input_data,
FrameBuffer::GetYuvDataFromFrameBuffer(buffer));
// Cropping YUV planes by offsetting the origins of each plane.
// TODO(b/152629712): Investigate the impact of color shifting caused by the
// bounding box with odd X or Y starting positions.
const int plane_y_offset = input_data.y_row_stride * y0 + x0;
const int plane_uv_offset = input_data.uv_row_stride * (y0 / 2) +
input_data.uv_pixel_stride * (x0 / 2);
FrameBuffer::Plane cropped_plane_y = {
/*buffer=*/input_data.y_buffer + plane_y_offset,
/*stride=*/{input_data.y_row_stride, /*pixel_stride_bytes=*/1}};
FrameBuffer::Plane cropped_plane_u = {
/*buffer=*/input_data.u_buffer + plane_uv_offset,
/*stride=*/{input_data.uv_row_stride, input_data.uv_pixel_stride}};
FrameBuffer::Plane cropped_plane_v = {
/*buffer=*/input_data.v_buffer + plane_uv_offset,
/*stride=*/{input_data.uv_row_stride, input_data.uv_pixel_stride}};
switch (buffer.format()) {
case FrameBuffer::Format::kNV12: {
std::unique_ptr<FrameBuffer> cropped_buffer = FrameBuffer::Create(
{cropped_plane_y, cropped_plane_u, cropped_plane_v}, crop_dimension,
buffer.format(), buffer.orientation());
return ResizeNv(*cropped_buffer, output_buffer);
}
case FrameBuffer::Format::kNV21: {
std::unique_ptr<FrameBuffer> cropped_buffer = FrameBuffer::Create(
{cropped_plane_y, cropped_plane_v, cropped_plane_u}, crop_dimension,
buffer.format(), buffer.orientation());
return ResizeNv(*cropped_buffer, output_buffer);
}
case FrameBuffer::Format::kYV12: {
std::unique_ptr<FrameBuffer> cropped_buffer = FrameBuffer::Create(
{cropped_plane_y, cropped_plane_v, cropped_plane_u}, crop_dimension,
buffer.format(), buffer.orientation());
return ResizeYv(*cropped_buffer, output_buffer);
}
case FrameBuffer::Format::kYV21: {
std::unique_ptr<FrameBuffer> cropped_buffer = FrameBuffer::Create(
{cropped_plane_y, cropped_plane_u, cropped_plane_v}, crop_dimension,
buffer.format(), buffer.orientation());
return ResizeYv(*cropped_buffer, output_buffer);
}
default:
return CreateStatusWithPayload(
StatusCode::kInternal,
absl::StrFormat("Format %i is not supported.", buffer.format()),
TfLiteSupportStatus::kImageProcessingError);
}
return absl::OkStatus();
}
absl::Status FlipHorizontallyRgba(const FrameBuffer& buffer,
FrameBuffer* output_buffer) {
if (buffer.plane_count() > 1) {
return CreateStatusWithPayload(
StatusCode::kInternal,
absl::StrFormat("Only single plane is supported for format %i.",
buffer.format()),
TfLiteSupportStatus::kImageProcessingError);
}
int ret = libyuv::ARGBMirror(
buffer.plane(0).buffer, buffer.plane(0).stride.row_stride_bytes,
const_cast<uint8*>(output_buffer->plane(0).buffer),
output_buffer->plane(0).stride.row_stride_bytes,
output_buffer->dimension().width, output_buffer->dimension().height);
if (ret != 0) {
return CreateStatusWithPayload(
StatusCode::kUnknown, "Libyuv ARGBMirror operation failed.",
TfLiteSupportStatus::kImageProcessingBackendError);
}
return absl::OkStatus();
}
// Flips `buffer` horizontally and store the result in `output_buffer`. This
// method assumes all buffers have pixel stride equals to 1.
absl::Status FlipHorizontallyPlane(const FrameBuffer& buffer,
FrameBuffer* output_buffer) {
if (buffer.plane_count() > 1) {
return CreateStatusWithPayload(
StatusCode::kInternal,
absl::StrFormat("Only single plane is supported for format %i.",
buffer.format()),
TfLiteSupportStatus::kImageProcessingError);
}
libyuv::MirrorPlane(
buffer.plane(0).buffer, buffer.plane(0).stride.row_stride_bytes,
const_cast<uint8*>(output_buffer->plane(0).buffer),
output_buffer->plane(0).stride.row_stride_bytes,
output_buffer->dimension().width, output_buffer->dimension().height);
return absl::OkStatus();
}
absl::Status ResizeRgb(const FrameBuffer& buffer, FrameBuffer* output_buffer) {
if (buffer.plane_count() > 1) {
return CreateStatusWithPayload(
StatusCode::kInternal,
absl::StrFormat("Only single plane is supported for format %i.",
buffer.format()),
TfLiteSupportStatus::kImageProcessingError);
}
// libyuv doesn't support scale kRGB (RGB24) foramat. In this method,
// the implementation converts kRGB format to ARGB and use ARGB buffer for
// scaling. The result is then convert back to RGB.
// Convert RGB to ARGB
int argb_buffer_size =
GetFrameBufferByteSize(buffer.dimension(), FrameBuffer::Format::kRGBA);
auto argb_buffer = absl::make_unique<uint8[]>(argb_buffer_size);
const int argb_row_bytes = buffer.dimension().width * kRgbaPixelBytes;
RETURN_IF_ERROR(ConvertRgbToArgb(buffer, argb_buffer.get(), argb_row_bytes));
// Resize ARGB
int resized_argb_buffer_size = GetFrameBufferByteSize(
output_buffer->dimension(), FrameBuffer::Format::kRGBA);
auto resized_argb_buffer =
absl::make_unique<uint8[]>(resized_argb_buffer_size);
int resized_argb_row_bytes =
output_buffer->dimension().width * kRgbaPixelBytes;
int ret = libyuv::ARGBScale(
argb_buffer.get(), argb_row_bytes, buffer.dimension().width,
buffer.dimension().height, resized_argb_buffer.get(),
resized_argb_row_bytes, output_buffer->dimension().width,
output_buffer->dimension().height, libyuv::FilterMode::kFilterBilinear);
if (ret != 0) {
return CreateStatusWithPayload(
StatusCode::kUnknown, "Libyuv ARGBScale operation failed.",
TfLiteSupportStatus::kImageProcessingBackendError);
}
// Convert ARGB to RGB
return ConvertArgbToRgb(resized_argb_buffer.get(), resized_argb_row_bytes,
output_buffer);
}
// Horizontally flip `buffer` and store the result in `output_buffer`.
absl::Status FlipHorizontallyRgb(const FrameBuffer& buffer,
FrameBuffer* output_buffer) {
if (buffer.plane_count() > 1) {
return CreateStatusWithPayload(
StatusCode::kInternal,
absl::StrFormat("Only single plane is supported for format %i.",
buffer.format()),
TfLiteSupportStatus::kImageProcessingError);
}
#if LIBYUV_VERSION >= 1747
int ret = libyuv::RGB24Mirror(
buffer.plane(0).buffer, buffer.plane(0).stride.row_stride_bytes,
const_cast<uint8*>(output_buffer->plane(0).buffer),
output_buffer->plane(0).stride.row_stride_bytes, buffer.dimension().width,
buffer.dimension().height);
if (ret != 0) {
return CreateStatusWithPayload(
StatusCode::kUnknown, "Libyuv RGB24Mirror operation failed.",
TfLiteSupportStatus::kImageProcessingBackendError);
}
return absl::OkStatus();
#else
#error LibyuvFrameBufferUtils requires LIBYUV_VERSION 1747 or above
#endif // LIBYUV_VERSION >= 1747
}
absl::Status ResizeRgba(const FrameBuffer& buffer, FrameBuffer* output_buffer) {
if (buffer.plane_count() > 1) {
return CreateStatusWithPayload(
StatusCode::kInternal,
absl::StrFormat("Only single plane is supported for format %i.",
buffer.format()),
TfLiteSupportStatus::kImageProcessingError);
}
int ret = libyuv::ARGBScale(
buffer.plane(0).buffer, buffer.plane(0).stride.row_stride_bytes,
buffer.dimension().width, buffer.dimension().height,
const_cast<uint8*>(output_buffer->plane(0).buffer),
output_buffer->plane(0).stride.row_stride_bytes,
output_buffer->dimension().width, output_buffer->dimension().height,
libyuv::FilterMode::kFilterBilinear);
if (ret != 0) {
return CreateStatusWithPayload(
StatusCode::kUnknown, "Libyuv ARGBScale operation failed.",
TfLiteSupportStatus::kImageProcessingBackendError);
}
return absl::OkStatus();
}
// Flips NV12/NV21 FrameBuffer horizontally.
absl::Status FlipHorizontallyNv(const FrameBuffer& buffer,
FrameBuffer* output_buffer) {
ASSIGN_OR_RETURN(FrameBuffer::YuvData input_data,
FrameBuffer::GetYuvDataFromFrameBuffer(buffer));
ASSIGN_OR_RETURN(FrameBuffer::YuvData output_data,
FrameBuffer::GetYuvDataFromFrameBuffer(*output_buffer));
ASSIGN_OR_RETURN(const uint8* input_chroma_buffer, GetUvRawBuffer(buffer));
ASSIGN_OR_RETURN(const uint8* output_chroma_buffer,
GetUvRawBuffer(*output_buffer));
int ret = libyuv::NV12Mirror(
input_data.y_buffer, input_data.y_row_stride, input_chroma_buffer,
input_data.uv_row_stride, const_cast<uint8*>(output_data.y_buffer),
output_data.y_row_stride, const_cast<uint8*>(output_chroma_buffer),
output_data.uv_row_stride, buffer.dimension().width,
buffer.dimension().height);
if (ret != 0) {
return CreateStatusWithPayload(
StatusCode::kUnknown, "Libyuv NV12Mirror operation failed.",
TfLiteSupportStatus::kImageProcessingBackendError);
}
return absl::OkStatus();
}
// Flips YV12/YV21 FrameBuffer horizontally.
absl::Status FlipHorizontallyYv(const FrameBuffer& buffer,
FrameBuffer* output_buffer) {
ASSIGN_OR_RETURN(FrameBuffer::YuvData input_data,
FrameBuffer::GetYuvDataFromFrameBuffer(buffer));
ASSIGN_OR_RETURN(FrameBuffer::YuvData output_data,
FrameBuffer::GetYuvDataFromFrameBuffer(*output_buffer));
int ret = libyuv::I420Mirror(
input_data.y_buffer, input_data.y_row_stride, input_data.u_buffer,
input_data.uv_row_stride, input_data.v_buffer, input_data.uv_row_stride,
const_cast<uint8*>(output_data.y_buffer), output_data.y_row_stride,
const_cast<uint8*>(output_data.u_buffer), output_data.uv_row_stride,
const_cast<uint8*>(output_data.v_buffer), output_data.uv_row_stride,
buffer.dimension().width, buffer.dimension().height);
if (ret != 0) {
return CreateStatusWithPayload(
StatusCode::kUnknown, "Libyuv I420Mirror operation failed.",
TfLiteSupportStatus::kImageProcessingBackendError);
}
return absl::OkStatus();
}
// Flips NV12/NV21 FrameBuffer vertically.
absl::Status FlipVerticallyNv(const FrameBuffer& buffer,
FrameBuffer* output_buffer) {
ASSIGN_OR_RETURN(FrameBuffer::YuvData input_data,
FrameBuffer::GetYuvDataFromFrameBuffer(buffer));
ASSIGN_OR_RETURN(FrameBuffer::YuvData output_data,
FrameBuffer::GetYuvDataFromFrameBuffer(*output_buffer));
// Flip Y plane vertically by passing a negative height.
libyuv::CopyPlane(input_data.y_buffer, input_data.y_row_stride,
const_cast<uint8*>(output_data.y_buffer),
output_data.y_row_stride, buffer.dimension().width,
-output_buffer->dimension().height);
// Flip UV plane vertically by passing a negative height.
ASSIGN_OR_RETURN(const uint8* input_chroma_buffer, GetUvRawBuffer(buffer));
ASSIGN_OR_RETURN(const uint8* output_chroma_buffer,
GetUvRawBuffer(*output_buffer));
ASSIGN_OR_RETURN(const FrameBuffer::Dimension uv_plane_dimension,
GetUvPlaneDimension(buffer.dimension(), buffer.format()));
libyuv::CopyPlane(
input_chroma_buffer, input_data.uv_row_stride,
const_cast<uint8*>(output_chroma_buffer), output_data.uv_row_stride,
/*width=*/uv_plane_dimension.width * 2, -uv_plane_dimension.height);
return absl::OkStatus();
}
// Flips NV12/NV21 FrameBuffer vertically.
absl::Status FlipVerticallyYv(const FrameBuffer& buffer,
FrameBuffer* output_buffer) {
ASSIGN_OR_RETURN(FrameBuffer::YuvData input_data,
FrameBuffer::GetYuvDataFromFrameBuffer(buffer));
ASSIGN_OR_RETURN(FrameBuffer::YuvData output_data,
FrameBuffer::GetYuvDataFromFrameBuffer(*output_buffer));
// Flip buffer vertically by passing a negative height.
int ret = libyuv::I420Copy(
input_data.y_buffer, input_data.y_row_stride, input_data.u_buffer,
input_data.uv_row_stride, input_data.v_buffer, input_data.uv_row_stride,
const_cast<uint8*>(output_data.y_buffer), output_data.y_row_stride,
const_cast<uint8*>(output_data.u_buffer), output_data.uv_row_stride,
const_cast<uint8*>(output_data.v_buffer), output_data.uv_row_stride,
buffer.dimension().width, -buffer.dimension().height);
if (ret != 0) {
return CreateStatusWithPayload(
StatusCode::kUnknown, "Libyuv I420Copy operation failed.",
TfLiteSupportStatus::kImageProcessingBackendError);
}
return absl::OkStatus();
}
// Resize `buffer` to metadata defined in `output_buffer`. This
// method assumes buffer has pixel stride equals to 1 (grayscale equivalent).
absl::Status ResizeGray(const FrameBuffer& buffer, FrameBuffer* output_buffer) {
if (buffer.plane_count() > 1) {
return CreateStatusWithPayload(
StatusCode::kInternal,
absl::StrFormat("Only single plane is supported for format %i.",
buffer.format()),
TfLiteSupportStatus::kImageProcessingError);
}
libyuv::ScalePlane(
buffer.plane(0).buffer, buffer.plane(0).stride.row_stride_bytes,
buffer.dimension().width, buffer.dimension().height,
const_cast<uint8*>(output_buffer->plane(0).buffer),
output_buffer->plane(0).stride.row_stride_bytes,
output_buffer->dimension().width, output_buffer->dimension().height,
libyuv::FilterMode::kFilterBilinear);
return absl::OkStatus();
}
// This method only supports kGRAY, kRGBA, and kRGB formats.
absl::Status CropResize(const FrameBuffer& buffer, int x0, int y0, int x1,
int y1, FrameBuffer* output_buffer) {
FrameBuffer::Dimension crop_dimension = GetCropDimension(x0, x1, y0, y1);
if (crop_dimension == output_buffer->dimension()) {
return CropPlane(buffer, x0, y0, x1, y1, output_buffer);
}
ASSIGN_OR_RETURN(int pixel_stride, GetPixelStrides(buffer.format()));
// Cropping is achieved by adjusting origin to (x0, y0).
int adjusted_offset =
buffer.plane(0).stride.row_stride_bytes * y0 + x0 * pixel_stride;
FrameBuffer::Plane plane = {
/*buffer=*/buffer.plane(0).buffer + adjusted_offset,
/*stride=*/{buffer.plane(0).stride.row_stride_bytes, pixel_stride}};
auto adjusted_buffer =
FrameBuffer::Create({plane}, crop_dimension, buffer.format(),
buffer.orientation(), buffer.timestamp());
switch (buffer.format()) {
case FrameBuffer::Format::kRGB:
return ResizeRgb(*adjusted_buffer, output_buffer);
case FrameBuffer::Format::kRGBA:
return ResizeRgba(*adjusted_buffer, output_buffer);
case FrameBuffer::Format::kGRAY:
return ResizeGray(*adjusted_buffer, output_buffer);
default:
return CreateStatusWithPayload(
StatusCode::kInternal,
absl::StrFormat("Format %i is not supported.", buffer.format()),
TfLiteSupportStatus::kImageProcessingError);
}
}
} // namespace
absl::Status LibyuvFrameBufferUtils::Crop(const FrameBuffer& buffer, int x0,
int y0, int x1, int y1,
FrameBuffer* output_buffer) {
RETURN_IF_ERROR(ValidateBufferPlaneMetadata(buffer));
RETURN_IF_ERROR(ValidateBufferPlaneMetadata(*output_buffer));
RETURN_IF_ERROR(
ValidateCropBufferInputs(buffer, *output_buffer, x0, y0, x1, y1));
RETURN_IF_ERROR(ValidateBufferFormats(buffer, *output_buffer));
switch (buffer.format()) {
case FrameBuffer::Format::kRGBA:
case FrameBuffer::Format::kRGB:
case FrameBuffer::Format::kGRAY:
return CropResize(buffer, x0, y0, x1, y1, output_buffer);
case FrameBuffer::Format::kNV12:
case FrameBuffer::Format::kNV21:
case FrameBuffer::Format::kYV12:
case FrameBuffer::Format::kYV21:
return CropResizeYuv(buffer, x0, y0, x1, y1, output_buffer);
default:
return CreateStatusWithPayload(
StatusCode::kInternal,
absl::StrFormat("Format %i is not supported.", buffer.format()),
TfLiteSupportStatus::kImageProcessingError);
}
}
absl::Status LibyuvFrameBufferUtils::Resize(const FrameBuffer& buffer,
FrameBuffer* output_buffer) {
RETURN_IF_ERROR(ValidateResizeBufferInputs(buffer, *output_buffer));
switch (buffer.format()) {
case FrameBuffer::Format::kYV12:
case FrameBuffer::Format::kYV21:
return ResizeYv(buffer, output_buffer);
case FrameBuffer::Format::kNV12:
case FrameBuffer::Format::kNV21:
return ResizeNv(buffer, output_buffer);
case FrameBuffer::Format::kRGB:
return ResizeRgb(buffer, output_buffer);
case FrameBuffer::Format::kRGBA:
return ResizeRgba(buffer, output_buffer);
case FrameBuffer::Format::kGRAY:
return ResizeGray(buffer, output_buffer);
default:
return CreateStatusWithPayload(
StatusCode::kInternal,
absl::StrFormat("Format %i is not supported.", buffer.format()),
TfLiteSupportStatus::kImageProcessingError);
}
}
absl::Status LibyuvFrameBufferUtils::Rotate(const FrameBuffer& buffer,
int angle_deg,
FrameBuffer* output_buffer) {
RETURN_IF_ERROR(
ValidateRotateBufferInputs(buffer, *output_buffer, angle_deg));
RETURN_IF_ERROR(ValidateBufferFormats(buffer, *output_buffer));
RETURN_IF_ERROR(ValidateBufferPlaneMetadata(buffer));
RETURN_IF_ERROR(ValidateBufferPlaneMetadata(*output_buffer));
switch (buffer.format()) {
case FrameBuffer::Format::kGRAY:
return RotateGray(buffer, angle_deg, output_buffer);
case FrameBuffer::Format::kRGBA:
return RotateRgba(buffer, angle_deg, output_buffer);
case FrameBuffer::Format::kNV12:
case FrameBuffer::Format::kNV21:
return RotateNv(buffer, angle_deg, output_buffer);
case FrameBuffer::Format::kYV12:
case FrameBuffer::Format::kYV21:
return RotateYv(buffer, angle_deg, output_buffer);
case FrameBuffer::Format::kRGB:
return RotateRgb(buffer, angle_deg, output_buffer);
default:
return CreateStatusWithPayload(
StatusCode::kInternal,
absl::StrFormat("Format %i is not supported.", buffer.format()),
TfLiteSupportStatus::kImageProcessingError);
}
}
absl::Status LibyuvFrameBufferUtils::FlipHorizontally(
const FrameBuffer& buffer, FrameBuffer* output_buffer) {
RETURN_IF_ERROR(ValidateBufferPlaneMetadata(buffer));
RETURN_IF_ERROR(ValidateBufferPlaneMetadata(*output_buffer));
RETURN_IF_ERROR(ValidateFlipBufferInputs(buffer, *output_buffer));
RETURN_IF_ERROR(ValidateBufferFormats(buffer, *output_buffer));
switch (buffer.format()) {
case FrameBuffer::Format::kRGBA:
return FlipHorizontallyRgba(buffer, output_buffer);
case FrameBuffer::Format::kYV12:
case FrameBuffer::Format::kYV21:
return FlipHorizontallyYv(buffer, output_buffer);
case FrameBuffer::Format::kNV12:
case FrameBuffer::Format::kNV21:
return FlipHorizontallyNv(buffer, output_buffer);
case FrameBuffer::Format::kRGB:
return FlipHorizontallyRgb(buffer, output_buffer);
case FrameBuffer::Format::kGRAY:
return FlipHorizontallyPlane(buffer, output_buffer);
default:
return CreateStatusWithPayload(
StatusCode::kInternal,
absl::StrFormat("Format %i is not supported.", buffer.format()),
TfLiteSupportStatus::kImageProcessingError);
}
}
absl::Status LibyuvFrameBufferUtils::FlipVertically(
const FrameBuffer& buffer, FrameBuffer* output_buffer) {
RETURN_IF_ERROR(ValidateBufferPlaneMetadata(buffer));
RETURN_IF_ERROR(ValidateBufferPlaneMetadata(*output_buffer));
RETURN_IF_ERROR(ValidateFlipBufferInputs(buffer, *output_buffer));
RETURN_IF_ERROR(ValidateBufferFormats(buffer, *output_buffer));
switch (buffer.format()) {
case FrameBuffer::Format::kRGBA:
case FrameBuffer::Format::kRGB:
case FrameBuffer::Format::kGRAY:
return FlipPlaneVertically(buffer, output_buffer);
case FrameBuffer::Format::kNV12:
case FrameBuffer::Format::kNV21:
return FlipVerticallyNv(buffer, output_buffer);
case FrameBuffer::Format::kYV12:
case FrameBuffer::Format::kYV21:
return FlipVerticallyYv(buffer, output_buffer);
default:
return CreateStatusWithPayload(
StatusCode::kInternal,
absl::StrFormat("Format %i is not supported.", buffer.format()),
TfLiteSupportStatus::kImageProcessingError);
}
}
absl::Status LibyuvFrameBufferUtils::Convert(const FrameBuffer& buffer,
FrameBuffer* output_buffer) {
RETURN_IF_ERROR(
ValidateConvertFormats(buffer.format(), output_buffer->format()));
switch (buffer.format()) {
case FrameBuffer::Format::kNV12:
return ConvertFromNv12(buffer, output_buffer);
case FrameBuffer::Format::kNV21:
return ConvertFromNv21(buffer, output_buffer);
case FrameBuffer::Format::kYV12:
case FrameBuffer::Format::kYV21:
return ConvertFromYv(buffer, output_buffer);
case FrameBuffer::Format::kRGB:
return ConvertFromRgb(buffer, output_buffer);
case FrameBuffer::Format::kRGBA:
return ConvertFromRgba(buffer, output_buffer);
default:
return CreateStatusWithPayload(
StatusCode::kInternal,
absl::StrFormat("Format %i is not supported.", buffer.format()),
TfLiteSupportStatus::kImageProcessingError);
}
}
} // namespace vision
} // namespace task
} // namespace tflite