Add build system for Halide and expose FrameBufferUtils.
PiperOrigin-RevId: 515304264
This commit is contained in:
parent
5398b8881d
commit
39e2c8351f
32
WORKSPACE
32
WORKSPACE
|
@ -543,3 +543,35 @@ external_files()
|
||||||
|
|
||||||
load("@//third_party:wasm_files.bzl", "wasm_files")
|
load("@//third_party:wasm_files.bzl", "wasm_files")
|
||||||
wasm_files()
|
wasm_files()
|
||||||
|
|
||||||
|
# Halide
|
||||||
|
|
||||||
|
new_local_repository(
|
||||||
|
name = "halide",
|
||||||
|
build_file = "@//third_party/halide:BUILD.bazel",
|
||||||
|
path = "third_party/halide"
|
||||||
|
)
|
||||||
|
|
||||||
|
http_archive(
|
||||||
|
name = "linux_halide",
|
||||||
|
sha256 = "be3bdd067acb9ee0d37d0830821113cd69174bee46da466a836d8829fef7cf91",
|
||||||
|
strip_prefix = "Halide-14.0.0-x86-64-linux",
|
||||||
|
urls = ["https://github.com/halide/Halide/releases/download/v14.0.0/Halide-14.0.0-x86-64-linux-6b9ed2afd1d6d0badf04986602c943e287d44e46.tar.gz"],
|
||||||
|
build_file = "@//third_party:halide.BUILD",
|
||||||
|
)
|
||||||
|
|
||||||
|
http_archive(
|
||||||
|
name = "macos_halide",
|
||||||
|
sha256 = "569b1cda858d278d9dd68e072768d8347775e419f2ff39ff34d001ceb299e3c4",
|
||||||
|
strip_prefix = "Halide-14.0.0-x86-64-osx",
|
||||||
|
urls = ["https://github.com/halide/Halide/releases/download/v14.0.0/Halide-14.0.0-x86-64-osx-6b9ed2afd1d6d0badf04986602c943e287d44e46.tar.gz"],
|
||||||
|
build_file = "@//third_party:halide.BUILD",
|
||||||
|
)
|
||||||
|
|
||||||
|
http_archive(
|
||||||
|
name = "windows_halide",
|
||||||
|
sha256 = "a7a0481af2691ec436d79c20ca441e9a701bfce409f4f763dab75a8f1d740179",
|
||||||
|
strip_prefix = "Halide-14.0.0-x86-64-windows",
|
||||||
|
urls = ["https://github.com/halide/Halide/releases/download/v14.0.0/Halide-14.0.0-x86-64-windows-6b9ed2afd1d6d0badf04986602c943e287d44e46.zip"],
|
||||||
|
build_file = "@//third_party:halide.BUILD",
|
||||||
|
)
|
||||||
|
|
106
mediapipe/util/frame_buffer/BUILD
Normal file
106
mediapipe/util/frame_buffer/BUILD
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
# Copyright 2023 The MediaPipe Authors.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
package(default_visibility = ["//mediapipe/util/frame_buffer:__subpackages__"])
|
||||||
|
|
||||||
|
cc_library(
|
||||||
|
name = "frame_buffer_util",
|
||||||
|
srcs = ["frame_buffer_util.cc"],
|
||||||
|
hdrs = ["frame_buffer_util.h"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
":buffer",
|
||||||
|
"//mediapipe/framework/formats:frame_buffer",
|
||||||
|
"//mediapipe/framework/port:status",
|
||||||
|
"@com_google_absl//absl/status",
|
||||||
|
"@com_google_absl//absl/status:statusor",
|
||||||
|
"@com_google_absl//absl/strings:str_format",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_test(
|
||||||
|
name = "frame_buffer_util_test",
|
||||||
|
srcs = [
|
||||||
|
"frame_buffer_util_test.cc",
|
||||||
|
],
|
||||||
|
deps = [
|
||||||
|
":frame_buffer_util",
|
||||||
|
"//mediapipe/framework/formats:frame_buffer",
|
||||||
|
"//mediapipe/framework/port:gtest_main",
|
||||||
|
"//mediapipe/framework/port:status",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_library(
|
||||||
|
name = "buffer",
|
||||||
|
srcs = [
|
||||||
|
"buffer_common.cc",
|
||||||
|
"gray_buffer.cc",
|
||||||
|
"rgb_buffer.cc",
|
||||||
|
"yuv_buffer.cc",
|
||||||
|
],
|
||||||
|
hdrs = [
|
||||||
|
"buffer_common.h",
|
||||||
|
"gray_buffer.h",
|
||||||
|
"rgb_buffer.h",
|
||||||
|
"yuv_buffer.h",
|
||||||
|
],
|
||||||
|
deps = [
|
||||||
|
"//mediapipe/util/frame_buffer/halide:gray_flip_halide",
|
||||||
|
"//mediapipe/util/frame_buffer/halide:gray_resize_halide",
|
||||||
|
"//mediapipe/util/frame_buffer/halide:gray_rotate_halide",
|
||||||
|
"//mediapipe/util/frame_buffer/halide:rgb_flip_halide",
|
||||||
|
"//mediapipe/util/frame_buffer/halide:rgb_gray_halide",
|
||||||
|
"//mediapipe/util/frame_buffer/halide:rgb_resize_halide",
|
||||||
|
"//mediapipe/util/frame_buffer/halide:rgb_rgb_halide",
|
||||||
|
"//mediapipe/util/frame_buffer/halide:rgb_rotate_halide",
|
||||||
|
"//mediapipe/util/frame_buffer/halide:rgb_yuv_halide",
|
||||||
|
"//mediapipe/util/frame_buffer/halide:yuv_flip_halide",
|
||||||
|
"//mediapipe/util/frame_buffer/halide:yuv_resize_halide",
|
||||||
|
"//mediapipe/util/frame_buffer/halide:yuv_rgb_halide",
|
||||||
|
"//mediapipe/util/frame_buffer/halide:yuv_rotate_halide",
|
||||||
|
"@halide//:runtime",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Tests:
|
||||||
|
cc_test(
|
||||||
|
name = "rgb_buffer_test",
|
||||||
|
srcs = ["rgb_buffer_test.cc"],
|
||||||
|
deps = [
|
||||||
|
":buffer",
|
||||||
|
"//mediapipe/framework/port:gtest_main",
|
||||||
|
"@com_google_absl//absl/log",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_test(
|
||||||
|
name = "yuv_buffer_test",
|
||||||
|
srcs = ["yuv_buffer_test.cc"],
|
||||||
|
deps = [
|
||||||
|
":buffer",
|
||||||
|
"//mediapipe/framework/port:gtest_main",
|
||||||
|
"@com_google_absl//absl/log",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_test(
|
||||||
|
name = "gray_buffer_test",
|
||||||
|
srcs = ["gray_buffer_test.cc"],
|
||||||
|
deps = [
|
||||||
|
":buffer",
|
||||||
|
"//mediapipe/framework/port:gtest_main",
|
||||||
|
"@com_google_absl//absl/log",
|
||||||
|
],
|
||||||
|
)
|
40
mediapipe/util/frame_buffer/buffer_common.cc
Normal file
40
mediapipe/util/frame_buffer/buffer_common.cc
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
// Copyright 2023 The MediaPipe Authors.
|
||||||
|
//
|
||||||
|
// 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 "mediapipe/util/frame_buffer/buffer_common.h"
|
||||||
|
|
||||||
|
namespace mediapipe {
|
||||||
|
namespace frame_buffer {
|
||||||
|
namespace common {
|
||||||
|
|
||||||
|
bool crop_buffer(int x0, int y0, int x1, int y1, halide_buffer_t* buffer) {
|
||||||
|
if (x0 < 0 || x1 >= buffer->dim[0].extent) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (y0 < 0 || y1 >= buffer->dim[1].extent) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move the start pointer so that it points at (x0, y0) and set the new
|
||||||
|
// extents. Leave the strides unchanged; we simply skip over the cropped
|
||||||
|
// image data.
|
||||||
|
buffer->host += y0 * buffer->dim[1].stride + x0 * buffer->dim[0].stride;
|
||||||
|
buffer->dim[0].extent = x1 - x0 + 1;
|
||||||
|
buffer->dim[1].extent = y1 - y0 + 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace common
|
||||||
|
} // namespace frame_buffer
|
||||||
|
} // namespace mediapipe
|
32
mediapipe/util/frame_buffer/buffer_common.h
Normal file
32
mediapipe/util/frame_buffer/buffer_common.h
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
// Copyright 2023 The MediaPipe Authors.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#ifndef MEDIAPIPE_UTIL_FRAME_BUFFER_BUFFER_COMMON_H_
|
||||||
|
#define MEDIAPIPE_UTIL_FRAME_BUFFER_BUFFER_COMMON_H_
|
||||||
|
|
||||||
|
#include "HalideRuntime.h"
|
||||||
|
|
||||||
|
namespace mediapipe {
|
||||||
|
namespace frame_buffer {
|
||||||
|
namespace common {
|
||||||
|
|
||||||
|
// Performs in-place cropping on the given buffer; the provided rectangle
|
||||||
|
// becomes the full extent of the buffer upon success. Returns false on error.
|
||||||
|
bool crop_buffer(int x0, int y0, int x1, int y1, halide_buffer_t* buffer);
|
||||||
|
|
||||||
|
} // namespace common
|
||||||
|
} // namespace frame_buffer
|
||||||
|
} // namespace mediapipe
|
||||||
|
|
||||||
|
#endif // MEDIAPIPE_UTIL_FRAME_BUFFER_BUFFER_COMMON_H_
|
794
mediapipe/util/frame_buffer/frame_buffer_util.cc
Normal file
794
mediapipe/util/frame_buffer/frame_buffer_util.cc
Normal file
|
@ -0,0 +1,794 @@
|
||||||
|
// Copyright 2023 The MediaPipe Authors.
|
||||||
|
//
|
||||||
|
// 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 "mediapipe/util/frame_buffer/frame_buffer_util.h"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
#include "absl/status/statusor.h"
|
||||||
|
#include "absl/strings/str_format.h"
|
||||||
|
#include "mediapipe/framework/formats/frame_buffer.h"
|
||||||
|
#include "mediapipe/framework/port/status_macros.h"
|
||||||
|
#include "mediapipe/util/frame_buffer/gray_buffer.h"
|
||||||
|
#include "mediapipe/util/frame_buffer/rgb_buffer.h"
|
||||||
|
#include "mediapipe/util/frame_buffer/yuv_buffer.h"
|
||||||
|
|
||||||
|
namespace mediapipe {
|
||||||
|
namespace frame_buffer {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr int kRgbaChannels = 4;
|
||||||
|
constexpr int kRgbaPixelBytes = 4;
|
||||||
|
constexpr int kRgbChannels = 3;
|
||||||
|
constexpr int kRgbPixelBytes = 3;
|
||||||
|
constexpr int kGrayChannel = 1;
|
||||||
|
constexpr int kGrayPixelBytes = 1;
|
||||||
|
|
||||||
|
// YUV helpers.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Returns whether the buffer is part of the supported Yuv format.
|
||||||
|
bool IsSupportedYuvBuffer(const FrameBuffer& buffer) {
|
||||||
|
return buffer.format() == FrameBuffer::Format::kNV21 ||
|
||||||
|
buffer.format() == FrameBuffer::Format::kNV12 ||
|
||||||
|
buffer.format() == FrameBuffer::Format::kYV12 ||
|
||||||
|
buffer.format() == FrameBuffer::Format::kYV21;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shared validation functions.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Indicates whether the given buffers have the same dimensions.
|
||||||
|
bool AreBufferDimsEqual(const FrameBuffer& buffer1,
|
||||||
|
const FrameBuffer& buffer2) {
|
||||||
|
return buffer1.dimension() == buffer2.dimension();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Indicates whether the given buffers formats are compatible. Same formats are
|
||||||
|
// compatible and all YUV family formats (e.g. NV21, NV12, YV12, YV21, etc) are
|
||||||
|
// compatible.
|
||||||
|
bool AreBufferFormatsCompatible(const FrameBuffer& buffer1,
|
||||||
|
const FrameBuffer& buffer2) {
|
||||||
|
switch (buffer1.format()) {
|
||||||
|
case FrameBuffer::Format::kRGBA:
|
||||||
|
case FrameBuffer::Format::kRGB:
|
||||||
|
return (buffer2.format() == FrameBuffer::Format::kRGBA ||
|
||||||
|
buffer2.format() == FrameBuffer::Format::kRGB);
|
||||||
|
case FrameBuffer::Format::kNV12:
|
||||||
|
case FrameBuffer::Format::kNV21:
|
||||||
|
case FrameBuffer::Format::kYV12:
|
||||||
|
case FrameBuffer::Format::kYV21:
|
||||||
|
return (buffer2.format() == FrameBuffer::Format::kNV12 ||
|
||||||
|
buffer2.format() == FrameBuffer::Format::kNV21 ||
|
||||||
|
buffer2.format() == FrameBuffer::Format::kYV12 ||
|
||||||
|
buffer2.format() == FrameBuffer::Format::kYV21);
|
||||||
|
case FrameBuffer::Format::kGRAY:
|
||||||
|
default:
|
||||||
|
return buffer1.format() == buffer2.format();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status ValidateBufferFormat(const FrameBuffer& buffer) {
|
||||||
|
switch (buffer.format()) {
|
||||||
|
case FrameBuffer::Format::kGRAY:
|
||||||
|
case FrameBuffer::Format::kRGB:
|
||||||
|
case FrameBuffer::Format::kRGBA:
|
||||||
|
if (buffer.plane_count() == 1) return absl::OkStatus();
|
||||||
|
return absl::InvalidArgumentError(
|
||||||
|
"Plane count must be 1 for grayscale and RGB[a] buffers.");
|
||||||
|
case FrameBuffer::Format::kNV21:
|
||||||
|
case FrameBuffer::Format::kNV12:
|
||||||
|
case FrameBuffer::Format::kYV21:
|
||||||
|
case FrameBuffer::Format::kYV12:
|
||||||
|
return absl::OkStatus();
|
||||||
|
default:
|
||||||
|
return absl::InternalError(
|
||||||
|
absl::StrFormat("Unsupported buffer format: %i.", buffer.format()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status ValidateBufferFormats(const FrameBuffer& buffer1,
|
||||||
|
const FrameBuffer& buffer2) {
|
||||||
|
MP_RETURN_IF_ERROR(ValidateBufferFormat(buffer1));
|
||||||
|
MP_RETURN_IF_ERROR(ValidateBufferFormat(buffer2));
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status ValidateResizeBufferInputs(const FrameBuffer& buffer,
|
||||||
|
const FrameBuffer& output_buffer) {
|
||||||
|
bool valid_format = false;
|
||||||
|
switch (buffer.format()) {
|
||||||
|
case FrameBuffer::Format::kGRAY:
|
||||||
|
case FrameBuffer::Format::kRGB:
|
||||||
|
case FrameBuffer::Format::kNV12:
|
||||||
|
case FrameBuffer::Format::kNV21:
|
||||||
|
case FrameBuffer::Format::kYV12:
|
||||||
|
case FrameBuffer::Format::kYV21:
|
||||||
|
valid_format = (buffer.format() == output_buffer.format());
|
||||||
|
break;
|
||||||
|
case FrameBuffer::Format::kRGBA:
|
||||||
|
valid_format = (output_buffer.format() == FrameBuffer::Format::kRGBA ||
|
||||||
|
output_buffer.format() == FrameBuffer::Format::kRGB);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return absl::InternalError(
|
||||||
|
absl::StrFormat("Unsupported buffer format: %i.", buffer.format()));
|
||||||
|
}
|
||||||
|
if (!valid_format) {
|
||||||
|
return absl::InvalidArgumentError(
|
||||||
|
"Input and output buffer formats must match.");
|
||||||
|
}
|
||||||
|
return ValidateBufferFormats(buffer, output_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status ValidateRotateBufferInputs(const FrameBuffer& buffer,
|
||||||
|
const FrameBuffer& output_buffer,
|
||||||
|
int angle_deg) {
|
||||||
|
if (!AreBufferFormatsCompatible(buffer, output_buffer)) {
|
||||||
|
return absl::InvalidArgumentError(
|
||||||
|
"Input and output buffer formats must match.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool is_dimension_change = (angle_deg / 90) % 2 == 1;
|
||||||
|
const bool are_dimensions_rotated =
|
||||||
|
(buffer.dimension().width == output_buffer.dimension().height) &&
|
||||||
|
(buffer.dimension().height == output_buffer.dimension().width);
|
||||||
|
const bool are_dimensions_equal =
|
||||||
|
buffer.dimension() == output_buffer.dimension();
|
||||||
|
|
||||||
|
if (angle_deg >= 360 || angle_deg <= 0 || angle_deg % 90 != 0) {
|
||||||
|
return absl::InvalidArgumentError(
|
||||||
|
"Rotation angle must be between 0 and 360, in multiples of 90 "
|
||||||
|
"degrees.");
|
||||||
|
} else if ((is_dimension_change && !are_dimensions_rotated) ||
|
||||||
|
(!is_dimension_change && !are_dimensions_equal)) {
|
||||||
|
return absl::InvalidArgumentError(
|
||||||
|
"Output buffer has invalid dimensions for rotation.");
|
||||||
|
}
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status ValidateCropBufferInputs(const FrameBuffer& buffer,
|
||||||
|
const FrameBuffer& output_buffer, int x0,
|
||||||
|
int y0, int x1, int y1) {
|
||||||
|
if (!AreBufferFormatsCompatible(buffer, output_buffer)) {
|
||||||
|
return absl::InvalidArgumentError(
|
||||||
|
"Input and output buffer formats must match.");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_buffer_size_valid =
|
||||||
|
((x1 < buffer.dimension().width) && y1 < buffer.dimension().height);
|
||||||
|
bool are_points_valid = (x0 >= 0) && (y0 >= 0) && (x1 >= x0) && (y1 >= y0);
|
||||||
|
|
||||||
|
if (!is_buffer_size_valid || !are_points_valid) {
|
||||||
|
return absl::InvalidArgumentError("Invalid crop coordinates.");
|
||||||
|
}
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status ValidateFlipBufferInputs(const FrameBuffer& buffer,
|
||||||
|
const FrameBuffer& output_buffer) {
|
||||||
|
if (!AreBufferFormatsCompatible(buffer, output_buffer)) {
|
||||||
|
return absl::InvalidArgumentError(
|
||||||
|
"Input and output buffer formats must match.");
|
||||||
|
}
|
||||||
|
return AreBufferDimsEqual(buffer, output_buffer)
|
||||||
|
? absl::OkStatus()
|
||||||
|
: absl::InvalidArgumentError(
|
||||||
|
"Input and output buffers must have the same dimensions.");
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status ValidateConvertFormats(FrameBuffer::Format from_format,
|
||||||
|
FrameBuffer::Format to_format) {
|
||||||
|
if (from_format == to_format) {
|
||||||
|
return absl::InvalidArgumentError("Formats must be different.");
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (from_format) {
|
||||||
|
case FrameBuffer::Format::kGRAY:
|
||||||
|
return absl::InvalidArgumentError(
|
||||||
|
"Grayscale format does not convert to other formats.");
|
||||||
|
case FrameBuffer::Format::kRGB:
|
||||||
|
case FrameBuffer::Format::kRGBA:
|
||||||
|
case FrameBuffer::Format::kNV12:
|
||||||
|
case FrameBuffer::Format::kNV21:
|
||||||
|
case FrameBuffer::Format::kYV12:
|
||||||
|
case FrameBuffer::Format::kYV21:
|
||||||
|
return absl::OkStatus();
|
||||||
|
default:
|
||||||
|
return absl::InternalError(
|
||||||
|
absl::StrFormat("Unsupported buffer format: %i.", from_format));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct buffer helper functions.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Creates NV12 / NV21 / YV12 / YV21 YuvBuffer from the input `buffer`. The
|
||||||
|
// output YuvBuffer is agnostic to the YUV format since the YUV buffers are
|
||||||
|
// managed individually.
|
||||||
|
absl::StatusOr<YuvBuffer> CreateYuvBuffer(const FrameBuffer& buffer) {
|
||||||
|
ASSIGN_OR_RETURN(FrameBuffer::YuvData yuv_data,
|
||||||
|
FrameBuffer::GetYuvDataFromFrameBuffer(buffer));
|
||||||
|
return YuvBuffer(const_cast<uint8_t*>(yuv_data.y_buffer),
|
||||||
|
const_cast<uint8_t*>(yuv_data.u_buffer),
|
||||||
|
const_cast<uint8_t*>(yuv_data.v_buffer),
|
||||||
|
buffer.dimension().width, buffer.dimension().height,
|
||||||
|
yuv_data.y_row_stride, yuv_data.uv_row_stride,
|
||||||
|
yuv_data.uv_pixel_stride);
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::StatusOr<GrayBuffer> CreateGrayBuffer(const FrameBuffer& buffer) {
|
||||||
|
if (buffer.plane_count() != 1) {
|
||||||
|
return absl::InternalError("Unsupported grayscale planar format.");
|
||||||
|
}
|
||||||
|
return GrayBuffer(const_cast<uint8_t*>(buffer.plane(0).buffer()),
|
||||||
|
buffer.dimension().width, buffer.dimension().height);
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::StatusOr<RgbBuffer> CreateRgbBuffer(const FrameBuffer& buffer) {
|
||||||
|
if (buffer.plane_count() != 1) {
|
||||||
|
return absl::InternalError("Unsupported rgb[a] planar format.");
|
||||||
|
}
|
||||||
|
bool alpha = buffer.format() == FrameBuffer::Format::kRGBA ? true : false;
|
||||||
|
return RgbBuffer(const_cast<uint8_t*>(buffer.plane(0).buffer()),
|
||||||
|
buffer.dimension().width, buffer.dimension().height,
|
||||||
|
buffer.plane(0).stride().row_stride_bytes, alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grayscale transformation functions.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
absl::Status CropGrayscale(const FrameBuffer& buffer, int x0, int y0, int x1,
|
||||||
|
int y1, FrameBuffer* output_buffer) {
|
||||||
|
ASSIGN_OR_RETURN(auto input, CreateGrayBuffer(buffer));
|
||||||
|
ASSIGN_OR_RETURN(auto output, CreateGrayBuffer(*output_buffer));
|
||||||
|
bool success_crop = input.Crop(x0, y0, x1, y1);
|
||||||
|
if (!success_crop) {
|
||||||
|
return absl::UnknownError("Halide grayscale crop operation failed.");
|
||||||
|
}
|
||||||
|
bool success_resize = input.Resize(&output);
|
||||||
|
if (!success_resize) {
|
||||||
|
return absl::UnknownError("Halide grayscale resize operation failed.");
|
||||||
|
}
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status ResizeGrayscale(const FrameBuffer& buffer,
|
||||||
|
FrameBuffer* output_buffer) {
|
||||||
|
ASSIGN_OR_RETURN(auto input, CreateGrayBuffer(buffer));
|
||||||
|
ASSIGN_OR_RETURN(auto output, CreateGrayBuffer(*output_buffer));
|
||||||
|
return input.Resize(&output)
|
||||||
|
? absl::OkStatus()
|
||||||
|
: absl::UnknownError("Halide grayscale resize operation failed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status RotateGrayscale(const FrameBuffer& buffer, int angle_deg,
|
||||||
|
FrameBuffer* output_buffer) {
|
||||||
|
ASSIGN_OR_RETURN(auto input, CreateGrayBuffer(buffer));
|
||||||
|
ASSIGN_OR_RETURN(auto output, CreateGrayBuffer(*output_buffer));
|
||||||
|
return input.Rotate(angle_deg % 360, &output)
|
||||||
|
? absl::OkStatus()
|
||||||
|
: absl::UnknownError("Halide grayscale rotate operation failed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status FlipHorizontallyGrayscale(const FrameBuffer& buffer,
|
||||||
|
FrameBuffer* output_buffer) {
|
||||||
|
ASSIGN_OR_RETURN(auto input, CreateGrayBuffer(buffer));
|
||||||
|
ASSIGN_OR_RETURN(auto output, CreateGrayBuffer(*output_buffer));
|
||||||
|
return input.FlipHorizontally(&output)
|
||||||
|
? absl::OkStatus()
|
||||||
|
: absl::UnknownError(
|
||||||
|
"Halide grayscale horizontal flip operation failed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status FlipVerticallyGrayscale(const FrameBuffer& buffer,
|
||||||
|
FrameBuffer* output_buffer) {
|
||||||
|
ASSIGN_OR_RETURN(auto input, CreateGrayBuffer(buffer));
|
||||||
|
ASSIGN_OR_RETURN(auto output, CreateGrayBuffer(*output_buffer));
|
||||||
|
return input.FlipVertically(&output)
|
||||||
|
? absl::OkStatus()
|
||||||
|
: absl::UnknownError(
|
||||||
|
"Halide grayscale vertical flip operation failed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rgb transformation functions.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
absl::Status ResizeRgb(const FrameBuffer& buffer, FrameBuffer* output_buffer) {
|
||||||
|
ASSIGN_OR_RETURN(auto input, CreateRgbBuffer(buffer));
|
||||||
|
ASSIGN_OR_RETURN(auto output, CreateRgbBuffer(*output_buffer));
|
||||||
|
return input.Resize(&output)
|
||||||
|
? absl::OkStatus()
|
||||||
|
: absl::UnknownError("Halide rgb[a] resize operation failed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status ConvertRgb(const FrameBuffer& buffer, FrameBuffer* output_buffer) {
|
||||||
|
ASSIGN_OR_RETURN(auto input, CreateRgbBuffer(buffer));
|
||||||
|
bool result = false;
|
||||||
|
if (output_buffer->format() == FrameBuffer::Format::kGRAY) {
|
||||||
|
ASSIGN_OR_RETURN(auto output, CreateGrayBuffer(*output_buffer));
|
||||||
|
result = input.Convert(&output);
|
||||||
|
} else if (IsSupportedYuvBuffer(*output_buffer)) {
|
||||||
|
ASSIGN_OR_RETURN(auto output, CreateYuvBuffer(*output_buffer));
|
||||||
|
result = input.Convert(&output);
|
||||||
|
} else if (output_buffer->format() == FrameBuffer::Format::kRGBA ||
|
||||||
|
output_buffer->format() == FrameBuffer::Format::kRGB) {
|
||||||
|
ASSIGN_OR_RETURN(auto output, CreateRgbBuffer(*output_buffer));
|
||||||
|
result = input.Convert(&output);
|
||||||
|
}
|
||||||
|
return result ? absl::OkStatus()
|
||||||
|
: absl::UnknownError("Halide rgb[a] convert operation failed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status CropRgb(const FrameBuffer& buffer, int x0, int y0, int x1, int y1,
|
||||||
|
FrameBuffer* output_buffer) {
|
||||||
|
ASSIGN_OR_RETURN(auto input, CreateRgbBuffer(buffer));
|
||||||
|
ASSIGN_OR_RETURN(auto output, CreateRgbBuffer(*output_buffer));
|
||||||
|
bool success_crop = input.Crop(x0, y0, x1, y1);
|
||||||
|
if (!success_crop) {
|
||||||
|
return absl::UnknownError("Halide rgb[a] crop operation failed.");
|
||||||
|
}
|
||||||
|
bool success_resize = input.Resize(&output);
|
||||||
|
if (!success_resize) {
|
||||||
|
return absl::UnknownError("Halide rgb resize operation failed.");
|
||||||
|
}
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status FlipHorizontallyRgb(const FrameBuffer& buffer,
|
||||||
|
FrameBuffer* output_buffer) {
|
||||||
|
ASSIGN_OR_RETURN(auto input, CreateRgbBuffer(buffer));
|
||||||
|
ASSIGN_OR_RETURN(auto output, CreateRgbBuffer(*output_buffer));
|
||||||
|
return input.FlipHorizontally(&output)
|
||||||
|
? absl::OkStatus()
|
||||||
|
: absl::UnknownError(
|
||||||
|
"Halide rgb[a] horizontal flip operation failed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status FlipVerticallyRgb(const FrameBuffer& buffer,
|
||||||
|
FrameBuffer* output_buffer) {
|
||||||
|
ASSIGN_OR_RETURN(auto input, CreateRgbBuffer(buffer));
|
||||||
|
ASSIGN_OR_RETURN(auto output, CreateRgbBuffer(*output_buffer));
|
||||||
|
return input.FlipVertically(&output)
|
||||||
|
? absl::OkStatus()
|
||||||
|
: absl::UnknownError(
|
||||||
|
"Halide rgb[a] vertical flip operation failed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status RotateRgb(const FrameBuffer& buffer, int angle,
|
||||||
|
FrameBuffer* output_buffer) {
|
||||||
|
ASSIGN_OR_RETURN(auto input, CreateRgbBuffer(buffer));
|
||||||
|
ASSIGN_OR_RETURN(auto output, CreateRgbBuffer(*output_buffer));
|
||||||
|
return input.Rotate(angle % 360, &output)
|
||||||
|
? absl::OkStatus()
|
||||||
|
: absl::UnknownError("Halide rgb[a] rotate operation failed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Yuv transformation functions.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
absl::Status CropYuv(const FrameBuffer& buffer, int x0, int y0, int x1, int y1,
|
||||||
|
FrameBuffer* output_buffer) {
|
||||||
|
ASSIGN_OR_RETURN(auto input, CreateYuvBuffer(buffer));
|
||||||
|
ASSIGN_OR_RETURN(auto output, CreateYuvBuffer(*output_buffer));
|
||||||
|
bool success_crop = input.Crop(x0, y0, x1, y1);
|
||||||
|
if (!success_crop) {
|
||||||
|
return absl::UnknownError("Halide YUV crop operation failed.");
|
||||||
|
}
|
||||||
|
bool success_resize = input.Resize(&output);
|
||||||
|
if (!success_resize) {
|
||||||
|
return absl::UnknownError("Halide YUV resize operation failed.");
|
||||||
|
}
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status ResizeYuv(const FrameBuffer& buffer, FrameBuffer* output_buffer) {
|
||||||
|
ASSIGN_OR_RETURN(auto input, CreateYuvBuffer(buffer));
|
||||||
|
ASSIGN_OR_RETURN(auto output, CreateYuvBuffer(*output_buffer));
|
||||||
|
return input.Resize(&output)
|
||||||
|
? absl::OkStatus()
|
||||||
|
: absl::UnknownError("Halide YUV resize operation failed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status RotateYuv(const FrameBuffer& buffer, int angle_deg,
|
||||||
|
FrameBuffer* output_buffer) {
|
||||||
|
ASSIGN_OR_RETURN(auto input, CreateYuvBuffer(buffer));
|
||||||
|
ASSIGN_OR_RETURN(auto output, CreateYuvBuffer(*output_buffer));
|
||||||
|
return input.Rotate(angle_deg % 360, &output)
|
||||||
|
? absl::OkStatus()
|
||||||
|
: absl::UnknownError("Halide YUV rotate operation failed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status FlipHorizontallyYuv(const FrameBuffer& buffer,
|
||||||
|
FrameBuffer* output_buffer) {
|
||||||
|
ASSIGN_OR_RETURN(auto input, CreateYuvBuffer(buffer));
|
||||||
|
ASSIGN_OR_RETURN(auto output, CreateYuvBuffer(*output_buffer));
|
||||||
|
return input.FlipHorizontally(&output)
|
||||||
|
? absl::OkStatus()
|
||||||
|
: absl::UnknownError(
|
||||||
|
"Halide YUV horizontal flip operation failed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status FlipVerticallyYuv(const FrameBuffer& buffer,
|
||||||
|
FrameBuffer* output_buffer) {
|
||||||
|
ASSIGN_OR_RETURN(auto input, CreateYuvBuffer(buffer));
|
||||||
|
ASSIGN_OR_RETURN(auto output, CreateYuvBuffer(*output_buffer));
|
||||||
|
return input.FlipVertically(&output)
|
||||||
|
? absl::OkStatus()
|
||||||
|
: absl::UnknownError("Halide YUV vertical flip operation failed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Converts input YUV `buffer` into the `output_buffer` in RGB, RGBA or gray
|
||||||
|
// scale format.
|
||||||
|
absl::Status ConvertYuv(const FrameBuffer& buffer, FrameBuffer* output_buffer) {
|
||||||
|
bool success_convert = false;
|
||||||
|
ASSIGN_OR_RETURN(auto input, CreateYuvBuffer(buffer));
|
||||||
|
if (output_buffer->format() == FrameBuffer::Format::kRGBA ||
|
||||||
|
output_buffer->format() == FrameBuffer::Format::kRGB) {
|
||||||
|
ASSIGN_OR_RETURN(auto output, CreateRgbBuffer(*output_buffer));
|
||||||
|
bool half_sampling = false;
|
||||||
|
if (buffer.dimension().width / 2 == output_buffer->dimension().width &&
|
||||||
|
buffer.dimension().height / 2 == output_buffer->dimension().height) {
|
||||||
|
half_sampling = true;
|
||||||
|
}
|
||||||
|
success_convert = input.Convert(half_sampling, &output);
|
||||||
|
} else if (output_buffer->format() == FrameBuffer::Format::kGRAY) {
|
||||||
|
if (buffer.plane(0).stride().row_stride_bytes == buffer.dimension().width) {
|
||||||
|
std::copy(input.y_buffer()->host,
|
||||||
|
input.y_buffer()->host + buffer.dimension().Size(),
|
||||||
|
const_cast<uint8_t*>(output_buffer->plane(0).buffer()));
|
||||||
|
} else {
|
||||||
|
// The y_buffer is padded. The conversion removes the padding.
|
||||||
|
uint8_t* gray_buffer =
|
||||||
|
const_cast<uint8_t*>(output_buffer->plane(0).buffer());
|
||||||
|
for (int i = 0; i < buffer.dimension().height; i++) {
|
||||||
|
int src_address = i * buffer.plane(0).stride().row_stride_bytes;
|
||||||
|
int dest_address = i * buffer.dimension().width;
|
||||||
|
std::memcpy(&gray_buffer[dest_address],
|
||||||
|
&buffer.plane(0).buffer()[src_address],
|
||||||
|
buffer.dimension().width);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
success_convert = true;
|
||||||
|
} else if (IsSupportedYuvBuffer(*output_buffer)) {
|
||||||
|
ASSIGN_OR_RETURN(auto output, CreateYuvBuffer(*output_buffer));
|
||||||
|
success_convert = input.Resize(&output);
|
||||||
|
}
|
||||||
|
return success_convert
|
||||||
|
? absl::OkStatus()
|
||||||
|
: absl::UnknownError("Halide YUV convert operation failed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
// Public methods.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
std::shared_ptr<FrameBuffer> CreateFromRgbaRawBuffer(
|
||||||
|
uint8_t* input, FrameBuffer::Dimension dimension,
|
||||||
|
FrameBuffer::Stride stride) {
|
||||||
|
if (stride == kDefaultStride) {
|
||||||
|
stride.row_stride_bytes = dimension.width * kRgbaChannels;
|
||||||
|
stride.pixel_stride_bytes = kRgbaChannels;
|
||||||
|
}
|
||||||
|
FrameBuffer::Plane input_plane(/*buffer=*/input,
|
||||||
|
/*stride=*/stride);
|
||||||
|
std::vector<FrameBuffer::Plane> planes{input_plane};
|
||||||
|
return std::make_shared<FrameBuffer>(planes, dimension,
|
||||||
|
FrameBuffer::Format::kRGBA);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<FrameBuffer> CreateFromRgbRawBuffer(
|
||||||
|
uint8_t* input, FrameBuffer::Dimension dimension,
|
||||||
|
FrameBuffer::Stride stride) {
|
||||||
|
if (stride == kDefaultStride) {
|
||||||
|
stride.row_stride_bytes = dimension.width * kRgbChannels;
|
||||||
|
stride.pixel_stride_bytes = kRgbChannels;
|
||||||
|
}
|
||||||
|
FrameBuffer::Plane input_plane(/*buffer=*/input,
|
||||||
|
/*stride=*/stride);
|
||||||
|
std::vector<FrameBuffer::Plane> planes{input_plane};
|
||||||
|
return std::make_shared<FrameBuffer>(planes, dimension,
|
||||||
|
FrameBuffer::Format::kRGB);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<FrameBuffer> CreateFromGrayRawBuffer(
|
||||||
|
uint8_t* input, FrameBuffer::Dimension dimension,
|
||||||
|
FrameBuffer::Stride stride) {
|
||||||
|
if (stride == kDefaultStride) {
|
||||||
|
stride.row_stride_bytes = dimension.width * kGrayChannel;
|
||||||
|
stride.pixel_stride_bytes = kGrayChannel;
|
||||||
|
}
|
||||||
|
FrameBuffer::Plane input_plane(/*buffer=*/input,
|
||||||
|
/*stride=*/stride);
|
||||||
|
std::vector<FrameBuffer::Plane> planes{input_plane};
|
||||||
|
return std::make_shared<FrameBuffer>(planes, dimension,
|
||||||
|
FrameBuffer::Format::kGRAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::StatusOr<std::shared_ptr<FrameBuffer>> CreateFromYuvRawBuffer(
|
||||||
|
uint8_t* y_plane, uint8_t* u_plane, uint8_t* v_plane,
|
||||||
|
FrameBuffer::Format format, FrameBuffer::Dimension dimension,
|
||||||
|
int row_stride_y, int row_stride_uv, int pixel_stride_uv) {
|
||||||
|
const int pixel_stride_y = 1;
|
||||||
|
std::vector<FrameBuffer::Plane> planes;
|
||||||
|
if (format == FrameBuffer::Format::kNV21 ||
|
||||||
|
format == FrameBuffer::Format::kYV12) {
|
||||||
|
planes = {{y_plane, /*stride=*/{row_stride_y, pixel_stride_y}},
|
||||||
|
{v_plane, /*stride=*/{row_stride_uv, pixel_stride_uv}},
|
||||||
|
{u_plane, /*stride=*/{row_stride_uv, pixel_stride_uv}}};
|
||||||
|
} else if (format == FrameBuffer::Format::kNV12 ||
|
||||||
|
format == FrameBuffer::Format::kYV21) {
|
||||||
|
planes = {{y_plane, /*stride=*/{row_stride_y, pixel_stride_y}},
|
||||||
|
{u_plane, /*stride=*/{row_stride_uv, pixel_stride_uv}},
|
||||||
|
{v_plane, /*stride=*/{row_stride_uv, pixel_stride_uv}}};
|
||||||
|
} else {
|
||||||
|
return absl::InvalidArgumentError(
|
||||||
|
absl::StrFormat("Input format is not YUV-like: %i.", format));
|
||||||
|
}
|
||||||
|
return std::make_shared<FrameBuffer>(planes, dimension, format);
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::StatusOr<std::shared_ptr<FrameBuffer>> CreateFromRawBuffer(
|
||||||
|
uint8_t* buffer, FrameBuffer::Dimension dimension,
|
||||||
|
const FrameBuffer::Format target_format) {
|
||||||
|
switch (target_format) {
|
||||||
|
case FrameBuffer::Format::kNV12:
|
||||||
|
case FrameBuffer::Format::kNV21: {
|
||||||
|
FrameBuffer::Plane plane(/*buffer=*/buffer,
|
||||||
|
/*stride=*/{dimension.width, kGrayChannel});
|
||||||
|
std::vector<FrameBuffer::Plane> planes{plane};
|
||||||
|
return std::make_shared<FrameBuffer>(planes, dimension, target_format);
|
||||||
|
}
|
||||||
|
case FrameBuffer::Format::kYV12: {
|
||||||
|
ASSIGN_OR_RETURN(const FrameBuffer::Dimension uv_dimension,
|
||||||
|
GetUvPlaneDimension(dimension, target_format));
|
||||||
|
return CreateFromYuvRawBuffer(
|
||||||
|
/*y_plane=*/buffer,
|
||||||
|
/*u_plane=*/buffer + dimension.Size() + uv_dimension.Size(),
|
||||||
|
/*v_plane=*/buffer + dimension.Size(), target_format, dimension,
|
||||||
|
/*row_stride_y=*/dimension.width, uv_dimension.width,
|
||||||
|
/*pixel_stride_uv=*/1);
|
||||||
|
}
|
||||||
|
case FrameBuffer::Format::kYV21: {
|
||||||
|
ASSIGN_OR_RETURN(const FrameBuffer::Dimension uv_dimension,
|
||||||
|
GetUvPlaneDimension(dimension, target_format));
|
||||||
|
return CreateFromYuvRawBuffer(
|
||||||
|
/*y_plane=*/buffer, /*u_plane=*/buffer + dimension.Size(),
|
||||||
|
/*v_plane=*/buffer + dimension.Size() + uv_dimension.Size(),
|
||||||
|
target_format, dimension, /*row_stride_y=*/dimension.width,
|
||||||
|
uv_dimension.width,
|
||||||
|
/*pixel_stride_uv=*/1);
|
||||||
|
}
|
||||||
|
case FrameBuffer::Format::kRGBA:
|
||||||
|
return CreateFromRgbaRawBuffer(buffer, dimension);
|
||||||
|
case FrameBuffer::Format::kRGB:
|
||||||
|
return CreateFromRgbRawBuffer(buffer, dimension);
|
||||||
|
case FrameBuffer::Format::kGRAY:
|
||||||
|
return CreateFromGrayRawBuffer(buffer, dimension);
|
||||||
|
default:
|
||||||
|
return absl::InternalError(
|
||||||
|
absl::StrFormat("Unsupported buffer format: %i.", target_format));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status Crop(const FrameBuffer& buffer, int x0, int y0, int x1, int y1,
|
||||||
|
FrameBuffer* output_buffer) {
|
||||||
|
MP_RETURN_IF_ERROR(
|
||||||
|
ValidateCropBufferInputs(buffer, *output_buffer, x0, y0, x1, y1));
|
||||||
|
MP_RETURN_IF_ERROR(ValidateBufferFormats(buffer, *output_buffer));
|
||||||
|
|
||||||
|
switch (buffer.format()) {
|
||||||
|
case FrameBuffer::Format::kGRAY:
|
||||||
|
return CropGrayscale(buffer, x0, y0, x1, y1, output_buffer);
|
||||||
|
case FrameBuffer::Format::kRGBA:
|
||||||
|
case FrameBuffer::Format::kRGB:
|
||||||
|
return CropRgb(buffer, x0, y0, x1, y1, output_buffer);
|
||||||
|
case FrameBuffer::Format::kNV12:
|
||||||
|
case FrameBuffer::Format::kNV21:
|
||||||
|
case FrameBuffer::Format::kYV12:
|
||||||
|
case FrameBuffer::Format::kYV21:
|
||||||
|
return CropYuv(buffer, x0, y0, x1, y1, output_buffer);
|
||||||
|
default:
|
||||||
|
return absl::InternalError(
|
||||||
|
absl::StrFormat("Format %i is not supported.", buffer.format()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status Resize(const FrameBuffer& buffer, FrameBuffer* output_buffer) {
|
||||||
|
MP_RETURN_IF_ERROR(ValidateResizeBufferInputs(buffer, *output_buffer));
|
||||||
|
|
||||||
|
switch (buffer.format()) {
|
||||||
|
case FrameBuffer::Format::kGRAY:
|
||||||
|
return ResizeGrayscale(buffer, output_buffer);
|
||||||
|
case FrameBuffer::Format::kRGBA:
|
||||||
|
case FrameBuffer::Format::kRGB:
|
||||||
|
return ResizeRgb(buffer, output_buffer);
|
||||||
|
case FrameBuffer::Format::kNV12:
|
||||||
|
case FrameBuffer::Format::kNV21:
|
||||||
|
case FrameBuffer::Format::kYV12:
|
||||||
|
case FrameBuffer::Format::kYV21:
|
||||||
|
return ResizeYuv(buffer, output_buffer);
|
||||||
|
default:
|
||||||
|
return absl::InternalError(
|
||||||
|
absl::StrFormat("Format %i is not supported.", buffer.format()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status Rotate(const FrameBuffer& buffer, int angle_deg,
|
||||||
|
FrameBuffer* output_buffer) {
|
||||||
|
MP_RETURN_IF_ERROR(
|
||||||
|
ValidateRotateBufferInputs(buffer, *output_buffer, angle_deg));
|
||||||
|
MP_RETURN_IF_ERROR(ValidateBufferFormats(buffer, *output_buffer));
|
||||||
|
|
||||||
|
switch (buffer.format()) {
|
||||||
|
case FrameBuffer::Format::kGRAY:
|
||||||
|
return RotateGrayscale(buffer, angle_deg, output_buffer);
|
||||||
|
case FrameBuffer::Format::kRGBA:
|
||||||
|
case FrameBuffer::Format::kRGB:
|
||||||
|
return RotateRgb(buffer, angle_deg, output_buffer);
|
||||||
|
case FrameBuffer::Format::kNV12:
|
||||||
|
case FrameBuffer::Format::kNV21:
|
||||||
|
case FrameBuffer::Format::kYV12:
|
||||||
|
case FrameBuffer::Format::kYV21:
|
||||||
|
return RotateYuv(buffer, angle_deg, output_buffer);
|
||||||
|
default:
|
||||||
|
return absl::InternalError(
|
||||||
|
absl::StrFormat("Format %i is not supported.", buffer.format()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status FlipHorizontally(const FrameBuffer& buffer,
|
||||||
|
FrameBuffer* output_buffer) {
|
||||||
|
MP_RETURN_IF_ERROR(ValidateFlipBufferInputs(buffer, *output_buffer));
|
||||||
|
MP_RETURN_IF_ERROR(ValidateBufferFormats(buffer, *output_buffer));
|
||||||
|
|
||||||
|
switch (buffer.format()) {
|
||||||
|
case FrameBuffer::Format::kGRAY:
|
||||||
|
return FlipHorizontallyGrayscale(buffer, output_buffer);
|
||||||
|
case FrameBuffer::Format::kRGBA:
|
||||||
|
case FrameBuffer::Format::kRGB:
|
||||||
|
return FlipHorizontallyRgb(buffer, output_buffer);
|
||||||
|
case FrameBuffer::Format::kNV12:
|
||||||
|
case FrameBuffer::Format::kNV21:
|
||||||
|
case FrameBuffer::Format::kYV12:
|
||||||
|
case FrameBuffer::Format::kYV21:
|
||||||
|
return FlipHorizontallyYuv(buffer, output_buffer);
|
||||||
|
default:
|
||||||
|
return absl::InternalError(
|
||||||
|
absl::StrFormat("Format %i is not supported.", buffer.format()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status FlipVertically(const FrameBuffer& buffer,
|
||||||
|
FrameBuffer* output_buffer) {
|
||||||
|
MP_RETURN_IF_ERROR(ValidateFlipBufferInputs(buffer, *output_buffer));
|
||||||
|
MP_RETURN_IF_ERROR(ValidateBufferFormats(buffer, *output_buffer));
|
||||||
|
|
||||||
|
switch (buffer.format()) {
|
||||||
|
case FrameBuffer::Format::kGRAY:
|
||||||
|
return FlipVerticallyGrayscale(buffer, output_buffer);
|
||||||
|
case FrameBuffer::Format::kRGBA:
|
||||||
|
case FrameBuffer::Format::kRGB:
|
||||||
|
return FlipVerticallyRgb(buffer, output_buffer);
|
||||||
|
case FrameBuffer::Format::kNV12:
|
||||||
|
case FrameBuffer::Format::kNV21:
|
||||||
|
case FrameBuffer::Format::kYV12:
|
||||||
|
case FrameBuffer::Format::kYV21:
|
||||||
|
return FlipVerticallyYuv(buffer, output_buffer);
|
||||||
|
default:
|
||||||
|
return absl::InternalError(
|
||||||
|
absl::StrFormat("Format %i is not supported.", buffer.format()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status Convert(const FrameBuffer& buffer, FrameBuffer* output_buffer) {
|
||||||
|
MP_RETURN_IF_ERROR(
|
||||||
|
ValidateConvertFormats(buffer.format(), output_buffer->format()));
|
||||||
|
|
||||||
|
switch (buffer.format()) {
|
||||||
|
case FrameBuffer::Format::kRGBA:
|
||||||
|
case FrameBuffer::Format::kRGB:
|
||||||
|
return ConvertRgb(buffer, output_buffer);
|
||||||
|
case FrameBuffer::Format::kNV12:
|
||||||
|
case FrameBuffer::Format::kNV21:
|
||||||
|
case FrameBuffer::Format::kYV12:
|
||||||
|
case FrameBuffer::Format::kYV21:
|
||||||
|
return ConvertYuv(buffer, output_buffer);
|
||||||
|
default:
|
||||||
|
return absl::InternalError(
|
||||||
|
absl::StrFormat("Format %i is not supported.", buffer.format()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int GetFrameBufferByteSize(FrameBuffer::Dimension dimension,
|
||||||
|
FrameBuffer::Format format) {
|
||||||
|
switch (format) {
|
||||||
|
case FrameBuffer::Format::kNV12:
|
||||||
|
case FrameBuffer::Format::kNV21:
|
||||||
|
case FrameBuffer::Format::kYV12:
|
||||||
|
case FrameBuffer::Format::kYV21:
|
||||||
|
return /*y plane*/ dimension.Size() +
|
||||||
|
/*uv plane*/ (dimension.width + 1) / 2 * (dimension.height + 1) /
|
||||||
|
2 * 2;
|
||||||
|
case FrameBuffer::Format::kRGB:
|
||||||
|
return dimension.Size() * kRgbPixelBytes;
|
||||||
|
case FrameBuffer::Format::kRGBA:
|
||||||
|
return dimension.Size() * kRgbaPixelBytes;
|
||||||
|
case FrameBuffer::Format::kGRAY:
|
||||||
|
return dimension.Size();
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::StatusOr<int> GetPixelStrides(FrameBuffer::Format format) {
|
||||||
|
switch (format) {
|
||||||
|
case FrameBuffer::Format::kGRAY:
|
||||||
|
return kGrayPixelBytes;
|
||||||
|
case FrameBuffer::Format::kRGB:
|
||||||
|
return kRgbPixelBytes;
|
||||||
|
case FrameBuffer::Format::kRGBA:
|
||||||
|
return kRgbaPixelBytes;
|
||||||
|
default:
|
||||||
|
return absl::InvalidArgumentError(absl::StrFormat(
|
||||||
|
"GetPixelStrides does not support format: %i.", format));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::StatusOr<const uint8_t*> GetUvRawBuffer(const FrameBuffer& buffer) {
|
||||||
|
if (buffer.format() != FrameBuffer::Format::kNV12 &&
|
||||||
|
buffer.format() != FrameBuffer::Format::kNV21) {
|
||||||
|
return absl::InvalidArgumentError(
|
||||||
|
"Only support getting biplanar UV buffer from NV12/NV21 frame buffer.");
|
||||||
|
}
|
||||||
|
ASSIGN_OR_RETURN(FrameBuffer::YuvData yuv_data,
|
||||||
|
FrameBuffer::GetYuvDataFromFrameBuffer(buffer));
|
||||||
|
const uint8_t* uv_buffer = buffer.format() == FrameBuffer::Format::kNV12
|
||||||
|
? yuv_data.u_buffer
|
||||||
|
: yuv_data.v_buffer;
|
||||||
|
return uv_buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::StatusOr<FrameBuffer::Dimension> GetUvPlaneDimension(
|
||||||
|
FrameBuffer::Dimension dimension, FrameBuffer::Format format) {
|
||||||
|
if (dimension.width <= 0 || dimension.height <= 0) {
|
||||||
|
return absl::InvalidArgumentError(
|
||||||
|
absl::StrFormat("Invalid input dimension: {%d, %d}.", dimension.width,
|
||||||
|
dimension.height));
|
||||||
|
}
|
||||||
|
switch (format) {
|
||||||
|
case FrameBuffer::Format::kNV12:
|
||||||
|
case FrameBuffer::Format::kNV21:
|
||||||
|
case FrameBuffer::Format::kYV12:
|
||||||
|
case FrameBuffer::Format::kYV21:
|
||||||
|
return FrameBuffer::Dimension{(dimension.width + 1) / 2,
|
||||||
|
(dimension.height + 1) / 2};
|
||||||
|
default:
|
||||||
|
return absl::InvalidArgumentError(
|
||||||
|
absl::StrFormat("Input format is not YUV-like: %i.", format));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FrameBuffer::Dimension GetCropDimension(int x0, int x1, int y0, int y1) {
|
||||||
|
return {x1 - x0 + 1, y1 - y0 + 1};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace frame_buffer
|
||||||
|
} // namespace mediapipe
|
131
mediapipe/util/frame_buffer/frame_buffer_util.h
Normal file
131
mediapipe/util/frame_buffer/frame_buffer_util.h
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
// Copyright 2023 The MediaPipe Authors.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#ifndef MEDIAPIPE_UTIL_FRAME_BUFFER_FRAME_BUFFER_UTIL_H_
|
||||||
|
#define MEDIAPIPE_UTIL_FRAME_BUFFER_FRAME_BUFFER_UTIL_H_
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
#include "absl/status/statusor.h"
|
||||||
|
#include "mediapipe/framework/formats/frame_buffer.h"
|
||||||
|
|
||||||
|
namespace mediapipe {
|
||||||
|
namespace frame_buffer {
|
||||||
|
|
||||||
|
// Creation helpers.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Default stride value for creating frame buffer from raw buffer. When using
|
||||||
|
// this default value, the default row stride and pixel stride values will be
|
||||||
|
// applied. e.g. for an RGB image:
|
||||||
|
// row_stride = width * 3
|
||||||
|
// pixel_stride = 3.
|
||||||
|
inline constexpr FrameBuffer::Stride kDefaultStride = {0, 0};
|
||||||
|
|
||||||
|
// Creates a FrameBuffer from raw RGBA buffer and passing arguments.
|
||||||
|
std::shared_ptr<FrameBuffer> CreateFromRgbaRawBuffer(
|
||||||
|
uint8_t* input, FrameBuffer::Dimension dimension,
|
||||||
|
FrameBuffer::Stride stride = kDefaultStride);
|
||||||
|
|
||||||
|
// Creates a FrameBuffer from raw RGB buffer and passing arguments.
|
||||||
|
std::shared_ptr<FrameBuffer> CreateFromRgbRawBuffer(
|
||||||
|
uint8_t* input, FrameBuffer::Dimension dimension,
|
||||||
|
FrameBuffer::Stride stride = kDefaultStride);
|
||||||
|
|
||||||
|
// Creates a FrameBuffer from raw grayscale buffer and passing arguments.
|
||||||
|
std::shared_ptr<FrameBuffer> CreateFromGrayRawBuffer(
|
||||||
|
uint8_t* input, FrameBuffer::Dimension dimension,
|
||||||
|
FrameBuffer::Stride stride = kDefaultStride);
|
||||||
|
|
||||||
|
// Creates a FrameBuffer from raw YUV buffer and passing arguments.
|
||||||
|
absl::StatusOr<std::shared_ptr<FrameBuffer>> CreateFromYuvRawBuffer(
|
||||||
|
uint8_t* y_plane, uint8_t* u_plane, uint8_t* v_plane,
|
||||||
|
FrameBuffer::Format format, FrameBuffer::Dimension dimension,
|
||||||
|
int row_stride_y, int row_stride_uv, int pixel_stride_uv);
|
||||||
|
|
||||||
|
// Creates an instance of FrameBuffer from raw buffer and passing arguments.
|
||||||
|
absl::StatusOr<std::shared_ptr<FrameBuffer>> CreateFromRawBuffer(
|
||||||
|
uint8_t* buffer, FrameBuffer::Dimension dimension,
|
||||||
|
FrameBuffer::Format target_format);
|
||||||
|
|
||||||
|
// Transformations.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Crops `buffer` to the specified points.
|
||||||
|
//
|
||||||
|
// (x0, y0) represents the top-left point of the buffer.
|
||||||
|
// (x1, y1) represents the bottom-right point of the buffer.
|
||||||
|
//
|
||||||
|
// The implementation performs origin moving and resizing operations.
|
||||||
|
absl::Status Crop(const FrameBuffer& buffer, int x0, int y0, int x1, int y1,
|
||||||
|
FrameBuffer* output_buffer);
|
||||||
|
|
||||||
|
// Resizes `buffer` to the size of the given `output_buffer` using bilinear
|
||||||
|
// interpolation.
|
||||||
|
absl::Status Resize(const FrameBuffer& buffer, FrameBuffer* output_buffer);
|
||||||
|
|
||||||
|
// Rotates `buffer` counter-clockwise by the given `angle_deg` (in degrees).
|
||||||
|
//
|
||||||
|
// The given angle must be a multiple of 90 degrees.
|
||||||
|
absl::Status Rotate(const FrameBuffer& buffer, int angle_deg,
|
||||||
|
FrameBuffer* output_buffer);
|
||||||
|
|
||||||
|
// Flips `buffer` horizontally.
|
||||||
|
absl::Status FlipHorizontally(const FrameBuffer& buffer,
|
||||||
|
FrameBuffer* output_buffer);
|
||||||
|
|
||||||
|
// Flips `buffer` vertically.
|
||||||
|
absl::Status FlipVertically(const FrameBuffer& buffer,
|
||||||
|
FrameBuffer* output_buffer);
|
||||||
|
|
||||||
|
// Converts `buffer`'s format to the format of the given `output_buffer`.
|
||||||
|
//
|
||||||
|
// Note that grayscale format does not convert to other formats.
|
||||||
|
// Note the NV21 to RGB/RGBA conversion may downsample by factor of 2 based
|
||||||
|
// on the buffer and output_buffer dimensions.
|
||||||
|
absl::Status Convert(const FrameBuffer& buffer, FrameBuffer* output_buffer);
|
||||||
|
|
||||||
|
// Miscellaneous Methods
|
||||||
|
// -----------------------------------------------------------------
|
||||||
|
|
||||||
|
// Returns the frame buffer size in bytes based on the input format and
|
||||||
|
// dimensions. GRAY, YV12/YV21 are in the planar formats, NV12/NV21 are in the
|
||||||
|
// semi-planar formats with the interleaved UV planes. RGB/RGBA are in the
|
||||||
|
// interleaved format.
|
||||||
|
int GetFrameBufferByteSize(FrameBuffer::Dimension dimension,
|
||||||
|
FrameBuffer::Format format);
|
||||||
|
|
||||||
|
// Returns pixel stride info for kGRAY, kRGB, kRGBA formats.
|
||||||
|
absl::StatusOr<int> GetPixelStrides(FrameBuffer::Format format);
|
||||||
|
|
||||||
|
// Returns the biplanar UV raw buffer for NV12/NV21 frame buffer.
|
||||||
|
absl::StatusOr<const uint8_t*> GetUvRawBuffer(const FrameBuffer& buffer);
|
||||||
|
|
||||||
|
// Returns U or V plane dimension with the given buffer `dimension` and
|
||||||
|
// `format`. Only supports NV12/NV21/YV12/YV21 formats. Returns
|
||||||
|
// InvalidArgumentError if 'dimension' is invalid or 'format' is other than the
|
||||||
|
// supported formats. This method assums the UV plane share the same dimension,
|
||||||
|
// especially for the YV12 / YV21 formats.
|
||||||
|
absl::StatusOr<FrameBuffer::Dimension> GetUvPlaneDimension(
|
||||||
|
FrameBuffer::Dimension dimension, FrameBuffer::Format format);
|
||||||
|
|
||||||
|
// Returns crop dimension based on crop start and end points.
|
||||||
|
FrameBuffer::Dimension GetCropDimension(int x0, int x1, int y0, int y1);
|
||||||
|
|
||||||
|
} // namespace frame_buffer
|
||||||
|
} // namespace mediapipe
|
||||||
|
|
||||||
|
#endif // MEDIAPIPE_UTIL_FRAME_BUFFER_FRAME_BUFFER_UTIL_H_
|
1176
mediapipe/util/frame_buffer/frame_buffer_util_test.cc
Normal file
1176
mediapipe/util/frame_buffer/frame_buffer_util_test.cc
Normal file
File diff suppressed because it is too large
Load Diff
95
mediapipe/util/frame_buffer/gray_buffer.cc
Normal file
95
mediapipe/util/frame_buffer/gray_buffer.cc
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
// Copyright 2023 The MediaPipe Authors.
|
||||||
|
//
|
||||||
|
// 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 "mediapipe/util/frame_buffer/gray_buffer.h"
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "mediapipe/util/frame_buffer/buffer_common.h"
|
||||||
|
#include "mediapipe/util/frame_buffer/halide/gray_flip_halide.h"
|
||||||
|
#include "mediapipe/util/frame_buffer/halide/gray_resize_halide.h"
|
||||||
|
#include "mediapipe/util/frame_buffer/halide/gray_rotate_halide.h"
|
||||||
|
#include "mediapipe/util/frame_buffer/yuv_buffer.h"
|
||||||
|
|
||||||
|
namespace mediapipe {
|
||||||
|
namespace frame_buffer {
|
||||||
|
|
||||||
|
GrayBuffer::GrayBuffer(uint8_t* buffer, int width, int height)
|
||||||
|
: owned_buffer_(nullptr) {
|
||||||
|
Initialize(buffer, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
GrayBuffer::GrayBuffer(int width, int height)
|
||||||
|
: owned_buffer_(new uint8_t[ByteSize(width, height)]) {
|
||||||
|
Initialize(owned_buffer_.get(), width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
GrayBuffer::GrayBuffer(const GrayBuffer& other) : buffer_(other.buffer_) {}
|
||||||
|
|
||||||
|
GrayBuffer::GrayBuffer(GrayBuffer&& other) { *this = std::move(other); }
|
||||||
|
|
||||||
|
GrayBuffer& GrayBuffer::operator=(const GrayBuffer& other) {
|
||||||
|
if (this != &other) {
|
||||||
|
buffer_ = other.buffer_;
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
GrayBuffer& GrayBuffer::operator=(GrayBuffer&& other) {
|
||||||
|
if (this != &other) {
|
||||||
|
owned_buffer_ = std::move(other.owned_buffer_);
|
||||||
|
buffer_ = other.buffer_;
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
GrayBuffer::~GrayBuffer() {}
|
||||||
|
|
||||||
|
void GrayBuffer::Initialize(uint8_t* data, int width, int height) {
|
||||||
|
buffer_ = Halide::Runtime::Buffer<uint8_t>(data, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GrayBuffer::Crop(int x0, int y0, int x1, int y1) {
|
||||||
|
// Twiddle the buffer start and extents to crop images.
|
||||||
|
return common::crop_buffer(x0, y0, x1, y1, buffer());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GrayBuffer::Resize(GrayBuffer* output) {
|
||||||
|
const int result = gray_resize_halide(
|
||||||
|
buffer(), static_cast<float>(width()) / output->width(),
|
||||||
|
static_cast<float>(height()) / output->height(), output->buffer());
|
||||||
|
return result == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GrayBuffer::Rotate(int angle, GrayBuffer* output) {
|
||||||
|
const int result = gray_rotate_halide(buffer(), angle, output->buffer());
|
||||||
|
return result == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GrayBuffer::FlipHorizontally(GrayBuffer* output) {
|
||||||
|
const int result = gray_flip_halide(buffer(),
|
||||||
|
false, // horizontal
|
||||||
|
output->buffer());
|
||||||
|
return result == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GrayBuffer::FlipVertically(GrayBuffer* output) {
|
||||||
|
const int result = gray_flip_halide(buffer(),
|
||||||
|
true, // vertical
|
||||||
|
output->buffer());
|
||||||
|
return result == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace frame_buffer
|
||||||
|
} // namespace mediapipe
|
137
mediapipe/util/frame_buffer/gray_buffer.h
Normal file
137
mediapipe/util/frame_buffer/gray_buffer.h
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
// Copyright 2023 The MediaPipe Authors.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#ifndef MEDIAPIPE_UTIL_FRAME_BUFFER_GRAY_BUFFER_H_
|
||||||
|
#define MEDIAPIPE_UTIL_FRAME_BUFFER_GRAY_BUFFER_H_
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "HalideBuffer.h"
|
||||||
|
#include "HalideRuntime.h"
|
||||||
|
|
||||||
|
namespace mediapipe {
|
||||||
|
namespace frame_buffer {
|
||||||
|
|
||||||
|
// GrayBuffer represents a view over a grayscale (i.e. luminance,
|
||||||
|
// or Y-only) buffer.
|
||||||
|
// GrayBuffer may be copied and moved efficiently; their backing buffers are
|
||||||
|
// shared and never deep copied.
|
||||||
|
// GrayBuffer requires a minimum image width depending on the natural vector
|
||||||
|
// size of the platform, e.g., 16px. This is not validated by GrayBuffer.
|
||||||
|
class GrayBuffer {
|
||||||
|
public:
|
||||||
|
// Returns the size (in bytes) of a grayscale image of the given
|
||||||
|
// dimensions. The given dimensions contain padding.
|
||||||
|
static int ByteSize(int buffer_width, int buffer_height) {
|
||||||
|
const int size = buffer_width * buffer_height;
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Builds a grayscale buffer with size as width * height. The buffer should
|
||||||
|
// be in row-major order with no padding.
|
||||||
|
//
|
||||||
|
// Does not take ownership of any backing buffers, which must be large
|
||||||
|
// enough to fit their contents.
|
||||||
|
GrayBuffer(uint8_t* buffer, int width, int height);
|
||||||
|
|
||||||
|
// Builds a grayscale buffer with size as width * height.
|
||||||
|
//
|
||||||
|
// The underlying backing buffer is allocated and owned by this
|
||||||
|
// GrayBuffer.
|
||||||
|
GrayBuffer(int width, int height);
|
||||||
|
|
||||||
|
// GrayBuffer is copyable. The source retains ownership of its backing
|
||||||
|
// buffers.
|
||||||
|
//
|
||||||
|
// Since the source retains ownership of its backing buffer, the source needs
|
||||||
|
// to outlive this instance's lifetime when the backing buffer is owned by
|
||||||
|
// the source. Otherwise, the passing in backing buffer should outlive this
|
||||||
|
// instance.
|
||||||
|
GrayBuffer(const GrayBuffer& other);
|
||||||
|
// GrayBuffer is moveable. The source loses ownership of any backing buffers.
|
||||||
|
// Specifically, if the source owns its backing buffer, after the move,
|
||||||
|
// Release() will return nullptr.
|
||||||
|
GrayBuffer(GrayBuffer&& other);
|
||||||
|
|
||||||
|
// GrayBuffer is assignable. The source retains ownership of its backing
|
||||||
|
// buffers.
|
||||||
|
//
|
||||||
|
// Since the source retains ownership of its backing buffer, the source needs
|
||||||
|
// to outlive this instance's lifetime when the backing buffer is owned by the
|
||||||
|
// source. Otherwise, the passing in backing buffer should outlive this
|
||||||
|
// instance.
|
||||||
|
GrayBuffer& operator=(const GrayBuffer& other);
|
||||||
|
GrayBuffer& operator=(GrayBuffer&& other);
|
||||||
|
|
||||||
|
~GrayBuffer();
|
||||||
|
|
||||||
|
// Performs an in-place crop. Modifies this buffer so that the new extent
|
||||||
|
// matches that of the given crop rectangle -- (x0, y0) becomes (0, 0) and
|
||||||
|
// the new width and height are x1 - x0 + 1 and y1 - y0 + 1, respectively.
|
||||||
|
bool Crop(int x0, int y0, int x1, int y1);
|
||||||
|
|
||||||
|
// Resizes this image to match the dimensions of the given output GrayBuffer
|
||||||
|
// and places the result into output's backing buffer.
|
||||||
|
//
|
||||||
|
// Note, if the output backing buffer is shared with multiple instances, by
|
||||||
|
// calling this method, all the instances' backing buffers will change.
|
||||||
|
bool Resize(GrayBuffer* output);
|
||||||
|
|
||||||
|
// Rotates this image into the given buffer by the given angle (90, 180, 270).
|
||||||
|
//
|
||||||
|
// Rotation is specified in degrees counter-clockwise such that when rotating
|
||||||
|
// by 90 degrees, the top-right corner of the source becomes the top-left of
|
||||||
|
// the output. The output buffer must have its height and width swapped when
|
||||||
|
// rotating by 90 or 270.
|
||||||
|
//
|
||||||
|
// Any angle values other than (90, 180, 270) are invalid.
|
||||||
|
//
|
||||||
|
// Note, if the output backing buffer is shared with multiple instances, by
|
||||||
|
// calling this method, all the instances' backing buffers will change.
|
||||||
|
bool Rotate(int angle, GrayBuffer* output);
|
||||||
|
|
||||||
|
// Flips this image horizontally/vertically into the given buffer. Both buffer
|
||||||
|
// dimensions must match.
|
||||||
|
//
|
||||||
|
// Note, if the output backing buffer is shared with multiple instances, by
|
||||||
|
// calling this method, all the instances' backing buffers will change.
|
||||||
|
bool FlipHorizontally(GrayBuffer* output);
|
||||||
|
bool FlipVertically(GrayBuffer* output);
|
||||||
|
|
||||||
|
// Releases ownership of the owned backing buffer.
|
||||||
|
uint8_t* Release() { return owned_buffer_.release(); }
|
||||||
|
|
||||||
|
// Returns the halide_buffer_t* for the image.
|
||||||
|
halide_buffer_t* buffer() { return buffer_.raw_buffer(); }
|
||||||
|
|
||||||
|
// Returns the image width.
|
||||||
|
const int width() const { return buffer_.dim(0).extent(); }
|
||||||
|
// Returns the image height.
|
||||||
|
const int height() const { return buffer_.dim(1).extent(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void Initialize(uint8_t* data, int width, int height);
|
||||||
|
|
||||||
|
// Non-NULL iff this GrayBuffer owns its buffer.
|
||||||
|
std::unique_ptr<uint8_t[]> owned_buffer_;
|
||||||
|
|
||||||
|
// Backing buffer: layout is always width x height. The backing buffer binds
|
||||||
|
// to either "owned_buffer_" or an external buffer.
|
||||||
|
Halide::Runtime::Buffer<uint8_t> buffer_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace frame_buffer
|
||||||
|
} // namespace mediapipe
|
||||||
|
|
||||||
|
#endif // MEDIAPIPE_UTIL_FRAME_BUFFER_GRAY_BUFFER_H_
|
223
mediapipe/util/frame_buffer/gray_buffer_test.cc
Normal file
223
mediapipe/util/frame_buffer/gray_buffer_test.cc
Normal file
|
@ -0,0 +1,223 @@
|
||||||
|
// Copyright 2023 The MediaPipe Authors.
|
||||||
|
//
|
||||||
|
// 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 "mediapipe/util/frame_buffer/gray_buffer.h"
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "absl/log/log.h"
|
||||||
|
#include "mediapipe/framework/port/gmock.h"
|
||||||
|
#include "mediapipe/framework/port/gtest.h"
|
||||||
|
|
||||||
|
// The default implementation of halide_error calls abort(), which we don't
|
||||||
|
// want. Instead, log the error and let the filter invocation fail.
|
||||||
|
extern "C" void halide_error(void*, const char* message) {
|
||||||
|
LOG(ERROR) << "Halide Error: " << message;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace mediapipe {
|
||||||
|
namespace frame_buffer {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Fill a GrayBuffer with zeroes.
|
||||||
|
void Fill(GrayBuffer* gray_buffer) {
|
||||||
|
halide_buffer_t* buffer = gray_buffer->buffer();
|
||||||
|
for (int y = 0; y < buffer->dim[1].extent; ++y) {
|
||||||
|
for (int x = 0; x < buffer->dim[0].extent; ++x) {
|
||||||
|
buffer->host[buffer->dim[1].stride * y + buffer->dim[0].stride * x] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(GrayBufferTest, Properties) {
|
||||||
|
GrayBuffer buffer(5, 4);
|
||||||
|
EXPECT_EQ(5, buffer.width());
|
||||||
|
EXPECT_EQ(4, buffer.height());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(GrayBufferTest, Release) {
|
||||||
|
GrayBuffer buffer(4, 4);
|
||||||
|
delete[] buffer.Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(GrayBufferTest, Assign) {
|
||||||
|
GrayBuffer buffer(3, 2);
|
||||||
|
GrayBuffer sink(nullptr, 0, 0);
|
||||||
|
Fill(&buffer);
|
||||||
|
sink = buffer;
|
||||||
|
EXPECT_EQ(3, sink.width());
|
||||||
|
EXPECT_EQ(2, sink.height());
|
||||||
|
|
||||||
|
sink = GrayBuffer(5, 4);
|
||||||
|
EXPECT_EQ(5, sink.width());
|
||||||
|
EXPECT_EQ(4, sink.height());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(GrayBufferTest, MoveAssign) {
|
||||||
|
GrayBuffer buffer(3, 2);
|
||||||
|
GrayBuffer sink(nullptr, 0, 0);
|
||||||
|
Fill(&buffer);
|
||||||
|
sink = std::move(buffer);
|
||||||
|
EXPECT_EQ(nullptr, buffer.Release());
|
||||||
|
EXPECT_EQ(3, sink.width());
|
||||||
|
EXPECT_EQ(2, sink.height());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(GrayBufferTest, MoveConstructor) {
|
||||||
|
GrayBuffer buffer(5, 4);
|
||||||
|
GrayBuffer sink(std::move(buffer));
|
||||||
|
Fill(&buffer);
|
||||||
|
EXPECT_EQ(nullptr, buffer.Release());
|
||||||
|
EXPECT_EQ(5, sink.width());
|
||||||
|
EXPECT_EQ(4, sink.height());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(GrayBufferTest, Crop) {
|
||||||
|
GrayBuffer source(8, 8);
|
||||||
|
EXPECT_TRUE(source.Crop(2, 2, 6, 6));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(GrayBufferTest, Resize_Even) {
|
||||||
|
uint8_t* data = new uint8_t[16];
|
||||||
|
for (int y = 0; y < 4; ++y) {
|
||||||
|
for (int x = 0; x < 4; ++x) {
|
||||||
|
data[x + y * 4] = x + y * 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GrayBuffer source(data, 4, 4);
|
||||||
|
GrayBuffer result(2, 2);
|
||||||
|
EXPECT_TRUE(source.Resize(&result));
|
||||||
|
EXPECT_EQ(0, result.buffer()->host[0]);
|
||||||
|
EXPECT_EQ(2, result.buffer()->host[1]);
|
||||||
|
EXPECT_EQ(8, result.buffer()->host[2]);
|
||||||
|
EXPECT_EQ(10, result.buffer()->host[3]);
|
||||||
|
delete[] data;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(GrayBufferTest, Resize_Odd) {
|
||||||
|
uint8_t* data = new uint8_t[16];
|
||||||
|
for (int y = 0; y < 4; ++y) {
|
||||||
|
for (int x = 0; x < 4; ++x) {
|
||||||
|
data[x + y * 4] = x + y * 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GrayBuffer source(data, 4, 4);
|
||||||
|
GrayBuffer result(1, 3);
|
||||||
|
EXPECT_TRUE(source.Resize(&result));
|
||||||
|
EXPECT_EQ(0, result.buffer()->host[0]);
|
||||||
|
EXPECT_EQ(5, result.buffer()->host[1]);
|
||||||
|
EXPECT_EQ(11, result.buffer()->host[2]);
|
||||||
|
delete[] data;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(GrayBufferTest, Rotate) {
|
||||||
|
GrayBuffer buffer(5, 4);
|
||||||
|
GrayBuffer result(4, 5);
|
||||||
|
Fill(&buffer);
|
||||||
|
EXPECT_TRUE(buffer.Rotate(90, &result));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(GrayBufferTest, Rotate_90) {
|
||||||
|
uint8_t* data = new uint8_t[4];
|
||||||
|
data[0] = 1;
|
||||||
|
data[1] = 2;
|
||||||
|
data[2] = 3;
|
||||||
|
data[3] = 4;
|
||||||
|
GrayBuffer buffer(data, 2, 2);
|
||||||
|
GrayBuffer result(2, 2);
|
||||||
|
EXPECT_TRUE(buffer.Rotate(90, &result));
|
||||||
|
|
||||||
|
EXPECT_EQ(2, result.buffer()->host[0]);
|
||||||
|
EXPECT_EQ(4, result.buffer()->host[1]);
|
||||||
|
EXPECT_EQ(1, result.buffer()->host[2]);
|
||||||
|
EXPECT_EQ(3, result.buffer()->host[3]);
|
||||||
|
|
||||||
|
delete[] data;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(GrayBufferTest, Rotate_180) {
|
||||||
|
uint8_t* data = new uint8_t[4];
|
||||||
|
data[0] = 1;
|
||||||
|
data[1] = 2;
|
||||||
|
data[2] = 3;
|
||||||
|
data[3] = 4;
|
||||||
|
GrayBuffer buffer(data, 2, 2);
|
||||||
|
GrayBuffer result(2, 2);
|
||||||
|
EXPECT_TRUE(buffer.Rotate(180, &result));
|
||||||
|
EXPECT_EQ(4, result.buffer()->host[0]);
|
||||||
|
EXPECT_EQ(3, result.buffer()->host[1]);
|
||||||
|
EXPECT_EQ(2, result.buffer()->host[2]);
|
||||||
|
EXPECT_EQ(1, result.buffer()->host[3]);
|
||||||
|
delete[] data;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(GrayBufferTest, Rotate_270) {
|
||||||
|
uint8_t* data = new uint8_t[4];
|
||||||
|
data[0] = 1;
|
||||||
|
data[1] = 2;
|
||||||
|
data[2] = 3;
|
||||||
|
data[3] = 4;
|
||||||
|
GrayBuffer buffer(data, 2, 2);
|
||||||
|
GrayBuffer result(2, 2);
|
||||||
|
EXPECT_TRUE(buffer.Rotate(270, &result));
|
||||||
|
EXPECT_EQ(3, result.buffer()->host[0]);
|
||||||
|
EXPECT_EQ(1, result.buffer()->host[1]);
|
||||||
|
EXPECT_EQ(4, result.buffer()->host[2]);
|
||||||
|
EXPECT_EQ(2, result.buffer()->host[3]);
|
||||||
|
delete[] data;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(GrayBufferTest, Flip) {
|
||||||
|
GrayBuffer buffer(5, 4);
|
||||||
|
GrayBuffer result(5, 4);
|
||||||
|
Fill(&buffer);
|
||||||
|
EXPECT_TRUE(buffer.FlipHorizontally(&result));
|
||||||
|
EXPECT_TRUE(buffer.FlipVertically(&result));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(GrayBufferTest, Flip_Horizontally) {
|
||||||
|
uint8_t* data = new uint8_t[4];
|
||||||
|
data[0] = 1;
|
||||||
|
data[1] = 2;
|
||||||
|
data[2] = 3;
|
||||||
|
data[3] = 4;
|
||||||
|
GrayBuffer buffer(data, 2, 2);
|
||||||
|
GrayBuffer result(2, 2);
|
||||||
|
EXPECT_TRUE(buffer.FlipHorizontally(&result));
|
||||||
|
EXPECT_EQ(2, result.buffer()->host[0]);
|
||||||
|
EXPECT_EQ(1, result.buffer()->host[1]);
|
||||||
|
EXPECT_EQ(4, result.buffer()->host[2]);
|
||||||
|
EXPECT_EQ(3, result.buffer()->host[3]);
|
||||||
|
delete[] data;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(GrayBufferTest, Flip_Vertically) {
|
||||||
|
uint8_t* data = new uint8_t[4];
|
||||||
|
data[0] = 1;
|
||||||
|
data[1] = 2;
|
||||||
|
data[2] = 3;
|
||||||
|
data[3] = 4;
|
||||||
|
GrayBuffer buffer(data, 2, 2);
|
||||||
|
GrayBuffer result(2, 2);
|
||||||
|
EXPECT_TRUE(buffer.FlipVertically(&result));
|
||||||
|
EXPECT_EQ(3, result.buffer()->host[0]);
|
||||||
|
EXPECT_EQ(4, result.buffer()->host[1]);
|
||||||
|
EXPECT_EQ(1, result.buffer()->host[2]);
|
||||||
|
EXPECT_EQ(2, result.buffer()->host[3]);
|
||||||
|
delete[] data;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace frame_buffer
|
||||||
|
} // namespace mediapipe
|
118
mediapipe/util/frame_buffer/halide/BUILD
Normal file
118
mediapipe/util/frame_buffer/halide/BUILD
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
# Copyright 2023 The MediaPipe Authors.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
load("@halide//:halide.bzl", "halide_library")
|
||||||
|
|
||||||
|
package(default_visibility = ["//mediapipe/util/frame_buffer:__subpackages__"])
|
||||||
|
|
||||||
|
# Common Halide library:
|
||||||
|
cc_library(
|
||||||
|
name = "common",
|
||||||
|
srcs = ["common.cc"],
|
||||||
|
hdrs = ["common.h"],
|
||||||
|
deps = ["@halide//:language"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Enable Halide's built-in profiler with:
|
||||||
|
# bazel ... --define halide_target_features=profile
|
||||||
|
# and HTML output of its intermediate representation with:
|
||||||
|
# bazel ... --define halide_extra_outputs=html
|
||||||
|
|
||||||
|
# RGB operations:
|
||||||
|
halide_library(
|
||||||
|
name = "rgb_flip_halide",
|
||||||
|
srcs = ["rgb_flip_generator.cc"],
|
||||||
|
generator_name = "rgb_flip_generator",
|
||||||
|
)
|
||||||
|
|
||||||
|
halide_library(
|
||||||
|
name = "rgb_resize_halide",
|
||||||
|
srcs = ["rgb_resize_generator.cc"],
|
||||||
|
generator_deps = [":common"],
|
||||||
|
generator_name = "rgb_resize_generator",
|
||||||
|
)
|
||||||
|
|
||||||
|
halide_library(
|
||||||
|
name = "rgb_rotate_halide",
|
||||||
|
srcs = ["rgb_rotate_generator.cc"],
|
||||||
|
generator_deps = [":common"],
|
||||||
|
generator_name = "rgb_rotate_generator",
|
||||||
|
)
|
||||||
|
|
||||||
|
halide_library(
|
||||||
|
name = "rgb_yuv_halide",
|
||||||
|
srcs = ["rgb_yuv_generator.cc"],
|
||||||
|
generator_name = "rgb_yuv_generator",
|
||||||
|
)
|
||||||
|
|
||||||
|
halide_library(
|
||||||
|
name = "rgb_rgb_halide",
|
||||||
|
srcs = ["rgb_rgb_generator.cc"],
|
||||||
|
generator_name = "rgb_rgb_generator",
|
||||||
|
)
|
||||||
|
|
||||||
|
# YUV operations:
|
||||||
|
halide_library(
|
||||||
|
name = "yuv_flip_halide",
|
||||||
|
srcs = ["yuv_flip_generator.cc"],
|
||||||
|
generator_name = "yuv_flip_generator",
|
||||||
|
)
|
||||||
|
|
||||||
|
halide_library(
|
||||||
|
name = "yuv_rgb_halide",
|
||||||
|
srcs = ["yuv_rgb_generator.cc"],
|
||||||
|
generator_name = "yuv_rgb_generator",
|
||||||
|
)
|
||||||
|
|
||||||
|
halide_library(
|
||||||
|
name = "yuv_resize_halide",
|
||||||
|
srcs = ["yuv_resize_generator.cc"],
|
||||||
|
generator_deps = [":common"],
|
||||||
|
generator_name = "yuv_resize_generator",
|
||||||
|
)
|
||||||
|
|
||||||
|
halide_library(
|
||||||
|
name = "yuv_rotate_halide",
|
||||||
|
srcs = ["yuv_rotate_generator.cc"],
|
||||||
|
generator_deps = [":common"],
|
||||||
|
generator_name = "yuv_rotate_generator",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Grayscale operations:
|
||||||
|
|
||||||
|
halide_library(
|
||||||
|
name = "rgb_gray_halide",
|
||||||
|
srcs = ["rgb_gray_generator.cc"],
|
||||||
|
generator_name = "rgb_gray_generator",
|
||||||
|
)
|
||||||
|
|
||||||
|
halide_library(
|
||||||
|
name = "gray_rotate_halide",
|
||||||
|
srcs = ["gray_rotate_generator.cc"],
|
||||||
|
generator_deps = [":common"],
|
||||||
|
generator_name = "gray_rotate_generator",
|
||||||
|
)
|
||||||
|
|
||||||
|
halide_library(
|
||||||
|
name = "gray_flip_halide",
|
||||||
|
srcs = ["gray_flip_generator.cc"],
|
||||||
|
generator_name = "gray_flip_generator",
|
||||||
|
)
|
||||||
|
|
||||||
|
halide_library(
|
||||||
|
name = "gray_resize_halide",
|
||||||
|
srcs = ["gray_resize_generator.cc"],
|
||||||
|
generator_deps = [":common"],
|
||||||
|
generator_name = "gray_resize_generator",
|
||||||
|
)
|
89
mediapipe/util/frame_buffer/halide/common.cc
Normal file
89
mediapipe/util/frame_buffer/halide/common.cc
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
// Copyright 2023 The MediaPipe Authors.
|
||||||
|
//
|
||||||
|
// 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 "mediapipe/util/frame_buffer/halide/common.h"
|
||||||
|
|
||||||
|
namespace mediapipe {
|
||||||
|
namespace frame_buffer {
|
||||||
|
namespace halide {
|
||||||
|
namespace common {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
using ::Halide::_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void resize_nn(Halide::Func input, Halide::Func result, Halide::Expr fx,
|
||||||
|
Halide::Expr fy) {
|
||||||
|
Halide::Var x{"x"}, y{"y"};
|
||||||
|
result(x, y, _) = input(Halide::cast<int>((x + 0.5f) * fx),
|
||||||
|
Halide::cast<int>((y + 0.5f) * fy), _);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Borrowed from photos/editing/halide/src/resize_image_bilinear_generator.cc:
|
||||||
|
void resize_bilinear(Halide::Func input, Halide::Func result, Halide::Expr fx,
|
||||||
|
Halide::Expr fy) {
|
||||||
|
Halide::Var x{"x"}, y{"y"};
|
||||||
|
Halide::Func x_interpolated("x_interpolated");
|
||||||
|
|
||||||
|
Halide::Expr xi = Halide::cast<int>(x * fx);
|
||||||
|
Halide::Expr xr = x * fx - xi;
|
||||||
|
Halide::Expr x0 = input(xi + 0, y, _);
|
||||||
|
Halide::Expr x1 = input(xi + 1, y, _);
|
||||||
|
x_interpolated(x, y, _) = lerp(x0, x1, xr);
|
||||||
|
|
||||||
|
Halide::Expr yi = Halide::cast<int>(y * fy);
|
||||||
|
Halide::Expr yr = y * fy - yi;
|
||||||
|
Halide::Expr y0 = x_interpolated(x, yi + 0, _);
|
||||||
|
Halide::Expr y1 = x_interpolated(x, yi + 1, _);
|
||||||
|
result(x, y, _) = lerp(y0, y1, yr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void resize_bilinear_int(Halide::Func input, Halide::Func result,
|
||||||
|
Halide::Expr fx, Halide::Expr fy) {
|
||||||
|
Halide::Var x{"x"}, y{"y"};
|
||||||
|
Halide::Func x_interpolated("x_interpolated");
|
||||||
|
|
||||||
|
fx = Halide::cast<int>(fx * 65536);
|
||||||
|
Halide::Expr xi = Halide::cast<int>(x * fx / 65536);
|
||||||
|
Halide::Expr xr = Halide::cast<uint16_t>(x * fx % 65536);
|
||||||
|
Halide::Expr x0 = input(xi + 0, y, _);
|
||||||
|
Halide::Expr x1 = input(xi + 1, y, _);
|
||||||
|
x_interpolated(x, y, _) = lerp(x0, x1, xr);
|
||||||
|
|
||||||
|
fy = Halide::cast<int>(fy * 65536);
|
||||||
|
Halide::Expr yi = Halide::cast<int>(y * fy / 65536);
|
||||||
|
Halide::Expr yr = Halide::cast<uint16_t>(y * fy % 65536);
|
||||||
|
Halide::Expr y0 = x_interpolated(x, yi + 0, _);
|
||||||
|
Halide::Expr y1 = x_interpolated(x, yi + 1, _);
|
||||||
|
result(x, y, _) = lerp(y0, y1, yr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void rotate(Halide::Func input, Halide::Func result, Halide::Expr width,
|
||||||
|
Halide::Expr height, Halide::Expr angle) {
|
||||||
|
Halide::Var x{"x"}, y{"y"};
|
||||||
|
Halide::Func result_90_degrees, result_180_degrees, result_270_degrees;
|
||||||
|
result_90_degrees(x, y, _) = input(width - 1 - y, x, _);
|
||||||
|
result_180_degrees(x, y, _) = input(width - 1 - x, height - 1 - y, _);
|
||||||
|
result_270_degrees(x, y, _) = input(y, height - 1 - x, _);
|
||||||
|
|
||||||
|
result(x, y, _) =
|
||||||
|
select(angle == 90, result_90_degrees(x, y, _), angle == 180,
|
||||||
|
result_180_degrees(x, y, _), angle == 270,
|
||||||
|
result_270_degrees(x, y, _), input(x, y, _));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace common
|
||||||
|
} // namespace halide
|
||||||
|
} // namespace frame_buffer
|
||||||
|
} // namespace mediapipe
|
61
mediapipe/util/frame_buffer/halide/common.h
Normal file
61
mediapipe/util/frame_buffer/halide/common.h
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
// Copyright 2023 The MediaPipe Authors.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#ifndef MEDIAPIPE_UTIL_FRAME_BUFFER_HALIDE_COMMON_H_
|
||||||
|
#define MEDIAPIPE_UTIL_FRAME_BUFFER_HALIDE_COMMON_H_
|
||||||
|
|
||||||
|
#include "Halide.h"
|
||||||
|
|
||||||
|
namespace mediapipe {
|
||||||
|
namespace frame_buffer {
|
||||||
|
namespace halide {
|
||||||
|
namespace common {
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
Halide::Expr is_planar(const T& buffer) {
|
||||||
|
return buffer.dim(0).stride() == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
Halide::Expr is_interleaved(const T& buffer) {
|
||||||
|
return buffer.dim(0).stride() == buffer.dim(2).extent() &&
|
||||||
|
buffer.dim(2).stride() == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resize scale parameters (fx, fy) are the ratio of source size to output
|
||||||
|
// size; thus if you want to produce an image half as wide and twice as tall
|
||||||
|
// as the input, (fx, fy) should be (2, 0.5).
|
||||||
|
|
||||||
|
// Nearest-neighbor resize: fast, but low-quality (prone to aliasing).
|
||||||
|
void resize_nn(Halide::Func input, Halide::Func result, Halide::Expr fx,
|
||||||
|
Halide::Expr fy);
|
||||||
|
|
||||||
|
// Resize with bilinear interpolation: slower but higher-quality.
|
||||||
|
void resize_bilinear(Halide::Func input, Halide::Func result, Halide::Expr fx,
|
||||||
|
Halide::Expr fy);
|
||||||
|
// Identical to the above, except that it uses fixed point integer math.
|
||||||
|
void resize_bilinear_int(Halide::Func input, Halide::Func result,
|
||||||
|
Halide::Expr fx, Halide::Expr fy);
|
||||||
|
|
||||||
|
// Note: width and height are the source image dimensions; angle must be one
|
||||||
|
// of [0, 90, 180, 270] or the result is undefined.
|
||||||
|
void rotate(Halide::Func input, Halide::Func result, Halide::Expr width,
|
||||||
|
Halide::Expr height, Halide::Expr angle);
|
||||||
|
|
||||||
|
} // namespace common
|
||||||
|
} // namespace halide
|
||||||
|
} // namespace frame_buffer
|
||||||
|
} // namespace mediapipe
|
||||||
|
|
||||||
|
#endif // MEDIAPIPE_UTIL_FRAME_BUFFER_HALIDE_COMMON_H_
|
61
mediapipe/util/frame_buffer/halide/gray_flip_generator.cc
Normal file
61
mediapipe/util/frame_buffer/halide/gray_flip_generator.cc
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
// Copyright 2023 The MediaPipe Authors.
|
||||||
|
//
|
||||||
|
// 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 "Halide.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using ::Halide::_;
|
||||||
|
|
||||||
|
class GrayFlip : public Halide::Generator<GrayFlip> {
|
||||||
|
public:
|
||||||
|
Var x{"x"}, y{"y"};
|
||||||
|
|
||||||
|
// Input<Buffer> because that allows us to apply constraints on stride, etc.
|
||||||
|
Input<Buffer<uint8_t, 2>> src_y{"src_y"};
|
||||||
|
|
||||||
|
// Flip vertically if true; flips horizontally (mirroring) otherwise.
|
||||||
|
Input<bool> flip_vertical{"flip_vertical", false};
|
||||||
|
|
||||||
|
Output<Func> dst_y{"dst_y", UInt(8), 2};
|
||||||
|
|
||||||
|
void generate();
|
||||||
|
void schedule();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void flip(Func input, Func result, Expr width, Expr height, Expr vertical);
|
||||||
|
};
|
||||||
|
|
||||||
|
void GrayFlip::generate() {
|
||||||
|
Halide::Func flip_x, flip_y;
|
||||||
|
flip_x(x, y, _) = src_y(src_y.dim(0).extent() - x - 1, y, _);
|
||||||
|
flip_y(x, y, _) = src_y(x, src_y.dim(1).extent() - y - 1, _);
|
||||||
|
|
||||||
|
dst_y(x, y, _) = select(flip_vertical, flip_y(x, y, _), flip_x(x, y, _));
|
||||||
|
}
|
||||||
|
|
||||||
|
void GrayFlip::schedule() {
|
||||||
|
Halide::Func dst_y_func = dst_y;
|
||||||
|
|
||||||
|
// Y plane dimensions start at zero and destination bounds must match.
|
||||||
|
Halide::OutputImageParam dst_y_output = dst_y_func.output_buffer();
|
||||||
|
src_y.dim(0).set_min(0);
|
||||||
|
src_y.dim(1).set_min(0);
|
||||||
|
dst_y_output.dim(0).set_bounds(0, src_y.dim(0).extent());
|
||||||
|
dst_y_output.dim(1).set_bounds(0, src_y.dim(1).extent());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
HALIDE_REGISTER_GENERATOR(GrayFlip, gray_flip_generator)
|
60
mediapipe/util/frame_buffer/halide/gray_resize_generator.cc
Normal file
60
mediapipe/util/frame_buffer/halide/gray_resize_generator.cc
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
// Copyright 2023 The MediaPipe Authors.
|
||||||
|
//
|
||||||
|
// 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 "Halide.h"
|
||||||
|
#include "mediapipe/util/frame_buffer/halide/common.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using ::Halide::BoundaryConditions::repeat_edge;
|
||||||
|
using ::mediapipe::frame_buffer::halide::common::resize_bilinear_int;
|
||||||
|
|
||||||
|
class GrayResize : public Halide::Generator<GrayResize> {
|
||||||
|
public:
|
||||||
|
Var x{"x"}, y{"y"};
|
||||||
|
|
||||||
|
Input<Buffer<uint8_t, 2>> src_y{"src_y"};
|
||||||
|
Input<float> scale_x{"scale_x", 1.0f, 0.0f, 1024.0f};
|
||||||
|
Input<float> scale_y{"scale_y", 1.0f, 0.0f, 1024.0f};
|
||||||
|
|
||||||
|
Output<Func> dst_y{"dst_y", UInt(8), 2};
|
||||||
|
|
||||||
|
void generate();
|
||||||
|
void schedule();
|
||||||
|
};
|
||||||
|
|
||||||
|
void GrayResize::generate() {
|
||||||
|
resize_bilinear_int(repeat_edge(src_y), dst_y, scale_x, scale_y);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GrayResize::schedule() {
|
||||||
|
// Grayscale image dimensions start at zero.
|
||||||
|
Halide::Func dst_y_func = dst_y;
|
||||||
|
Halide::OutputImageParam dst_y_output = dst_y_func.output_buffer();
|
||||||
|
src_y.dim(0).set_min(0);
|
||||||
|
src_y.dim(1).set_min(0);
|
||||||
|
dst_y_output.dim(0).set_min(0);
|
||||||
|
dst_y_output.dim(1).set_min(0);
|
||||||
|
|
||||||
|
// We must ensure that the image is wide enough to support vector
|
||||||
|
// operations.
|
||||||
|
const int vector_size = natural_vector_size<uint8_t>();
|
||||||
|
Halide::Expr min_y_width =
|
||||||
|
Halide::min(src_y.dim(0).extent(), dst_y_output.dim(0).extent());
|
||||||
|
dst_y_func.specialize(min_y_width >= vector_size).vectorize(x, vector_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
HALIDE_REGISTER_GENERATOR(GrayResize, gray_resize_generator)
|
63
mediapipe/util/frame_buffer/halide/gray_rotate_generator.cc
Normal file
63
mediapipe/util/frame_buffer/halide/gray_rotate_generator.cc
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
// Copyright 2023 The MediaPipe Authors.
|
||||||
|
//
|
||||||
|
// 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 "Halide.h"
|
||||||
|
#include "mediapipe/util/frame_buffer/halide/common.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using ::mediapipe::frame_buffer::halide::common::rotate;
|
||||||
|
|
||||||
|
class GrayRotate : public Halide::Generator<GrayRotate> {
|
||||||
|
public:
|
||||||
|
Var x{"x"}, y{"y"};
|
||||||
|
|
||||||
|
// Input<Buffer> because that allows us to apply constraints on stride, etc.
|
||||||
|
Input<Buffer<uint8_t, 2>> src_y{"src_y"};
|
||||||
|
|
||||||
|
// Rotation angle in degrees counter-clockwise. Must be in {0, 90, 180, 270}.
|
||||||
|
Input<int> rotation_angle{"rotation_angle", 0};
|
||||||
|
|
||||||
|
Output<Func> dst_y{"dst_y", UInt(8), 2};
|
||||||
|
|
||||||
|
void generate();
|
||||||
|
void schedule();
|
||||||
|
};
|
||||||
|
|
||||||
|
void GrayRotate::generate() {
|
||||||
|
const Halide::Expr width = src_y.dim(0).extent();
|
||||||
|
const Halide::Expr height = src_y.dim(1).extent();
|
||||||
|
|
||||||
|
rotate(src_y, dst_y, width, height, rotation_angle);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GrayRotate::schedule() {
|
||||||
|
Halide::Func dst_y_func = dst_y;
|
||||||
|
dst_y_func.specialize(rotation_angle == 0).reorder(x, y);
|
||||||
|
dst_y_func.specialize(rotation_angle == 90).reorder(y, x);
|
||||||
|
dst_y_func.specialize(rotation_angle == 180).reorder(x, y);
|
||||||
|
dst_y_func.specialize(rotation_angle == 270).reorder(y, x);
|
||||||
|
|
||||||
|
// Y plane dimensions start at zero. We could additionally constrain the
|
||||||
|
// extent to be even, but that doesn't seem to have any benefit.
|
||||||
|
Halide::OutputImageParam dst_y_output = dst_y_func.output_buffer();
|
||||||
|
src_y.dim(0).set_min(0);
|
||||||
|
src_y.dim(1).set_min(0);
|
||||||
|
dst_y_output.dim(0).set_min(0);
|
||||||
|
dst_y_output.dim(1).set_min(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
HALIDE_REGISTER_GENERATOR(GrayRotate, gray_rotate_generator)
|
84
mediapipe/util/frame_buffer/halide/rgb_flip_generator.cc
Normal file
84
mediapipe/util/frame_buffer/halide/rgb_flip_generator.cc
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
// Copyright 2023 The MediaPipe Authors.
|
||||||
|
//
|
||||||
|
// 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 "Halide.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using ::Halide::_;
|
||||||
|
|
||||||
|
class RgbFlip : public Halide::Generator<RgbFlip> {
|
||||||
|
public:
|
||||||
|
Var x{"x"}, y{"y"};
|
||||||
|
|
||||||
|
// Input<Buffer> because that allows us to apply constraints on stride, etc.
|
||||||
|
Input<Buffer<uint8_t, 3>> src_rgb{"src_rgb"};
|
||||||
|
// Flip vertically if true; flips horizontally (mirroring) otherwise.
|
||||||
|
Input<bool> flip_vertical{"flip_vertical", false};
|
||||||
|
|
||||||
|
Output<Func> dst_rgb{"dst_rgb", UInt(8), 3};
|
||||||
|
|
||||||
|
void generate();
|
||||||
|
void schedule();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void flip(Func input, Func result, Expr width, Expr height, Expr vertical);
|
||||||
|
};
|
||||||
|
|
||||||
|
void RgbFlip::flip(Halide::Func input, Halide::Func result, Halide::Expr width,
|
||||||
|
Halide::Expr height, Halide::Expr vertical) {
|
||||||
|
Halide::Func flip_x, flip_y;
|
||||||
|
flip_x(x, y, _) = input(width - x - 1, y, _);
|
||||||
|
flip_y(x, y, _) = input(x, height - y - 1, _);
|
||||||
|
|
||||||
|
result(x, y, _) = select(vertical, flip_y(x, y, _), flip_x(x, y, _));
|
||||||
|
}
|
||||||
|
|
||||||
|
void RgbFlip::generate() {
|
||||||
|
const Halide::Expr width = src_rgb.dim(0).extent();
|
||||||
|
const Halide::Expr height = src_rgb.dim(1).extent();
|
||||||
|
|
||||||
|
// Flip each of the RGB planes independently.
|
||||||
|
flip(src_rgb, dst_rgb, width, height, flip_vertical);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RgbFlip::schedule() {
|
||||||
|
Halide::Func dst_rgb_func = dst_rgb;
|
||||||
|
Halide::Var c = dst_rgb_func.args()[2];
|
||||||
|
Halide::OutputImageParam rgb_output = dst_rgb_func.output_buffer();
|
||||||
|
|
||||||
|
// Iterate over channel in the innermost loop, then x, then y.
|
||||||
|
dst_rgb_func.reorder(c, x, y);
|
||||||
|
|
||||||
|
// RGB planes starts at index zero in every dimension and destination bounds
|
||||||
|
// must match.
|
||||||
|
src_rgb.dim(0).set_min(0);
|
||||||
|
src_rgb.dim(1).set_min(0);
|
||||||
|
src_rgb.dim(2).set_min(0);
|
||||||
|
rgb_output.dim(0).set_bounds(0, src_rgb.dim(0).extent());
|
||||||
|
rgb_output.dim(1).set_bounds(0, src_rgb.dim(1).extent());
|
||||||
|
rgb_output.dim(2).set_bounds(0, src_rgb.dim(2).extent());
|
||||||
|
|
||||||
|
// Require that the input/output buffer be interleaved and tightly-
|
||||||
|
// packed; that is, either RGBRGBRGB[...] or RGBARGBARGBA[...],
|
||||||
|
// without gaps between pixels.
|
||||||
|
src_rgb.dim(0).set_stride(src_rgb.dim(2).extent());
|
||||||
|
src_rgb.dim(2).set_stride(1);
|
||||||
|
rgb_output.dim(0).set_stride(rgb_output.dim(2).extent());
|
||||||
|
rgb_output.dim(2).set_stride(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
HALIDE_REGISTER_GENERATOR(RgbFlip, rgb_flip_generator)
|
65
mediapipe/util/frame_buffer/halide/rgb_gray_generator.cc
Normal file
65
mediapipe/util/frame_buffer/halide/rgb_gray_generator.cc
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
// Copyright 2023 The MediaPipe Authors.
|
||||||
|
//
|
||||||
|
// 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 "Halide.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
class RgbGray : public Halide::Generator<RgbGray> {
|
||||||
|
public:
|
||||||
|
Var x{"x"}, y{"y"}, c{"c"};
|
||||||
|
|
||||||
|
Input<Buffer<uint8_t, 3>> src_rgb{"rgb"};
|
||||||
|
Output<Buffer<uint8_t, 2>> convert{"convert"};
|
||||||
|
|
||||||
|
void generate();
|
||||||
|
void schedule();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Integer math versions of the full-range JFIF RGB-Y coefficients.
|
||||||
|
// Y = 0.2990*R + 0.5870*G + 0.1140*B
|
||||||
|
// See https://www.w3.org/Graphics/JPEG/jfif3.pdf. These coefficients are
|
||||||
|
// similar to, but not identical, to those used in Android.
|
||||||
|
Halide::Expr rgby(Halide::Expr r, Halide::Expr g, Halide::Expr b) {
|
||||||
|
r = Halide::cast<int32_t>(r);
|
||||||
|
g = Halide::cast<int32_t>(g);
|
||||||
|
b = Halide::cast<int32_t>(b);
|
||||||
|
return (19595 * r + 38470 * g + 7474 * b + 32768) >> 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RgbGray::generate() {
|
||||||
|
Halide::Func gray("gray");
|
||||||
|
gray(x, y) = rgby(src_rgb(x, y, 0), src_rgb(x, y, 1), src_rgb(x, y, 2));
|
||||||
|
convert(x, y) = Halide::saturating_cast<uint8_t>(gray(x, y));
|
||||||
|
}
|
||||||
|
|
||||||
|
void RgbGray::schedule() {
|
||||||
|
// RGB images starts at index zero in every dimension.
|
||||||
|
src_rgb.dim(0).set_min(0);
|
||||||
|
src_rgb.dim(1).set_min(0);
|
||||||
|
src_rgb.dim(2).set_min(0);
|
||||||
|
|
||||||
|
// Require that the input buffer be interleaved and tightly-packed;
|
||||||
|
// with no gaps between pixels.
|
||||||
|
src_rgb.dim(0).set_stride(src_rgb.dim(2).extent());
|
||||||
|
src_rgb.dim(2).set_stride(1);
|
||||||
|
|
||||||
|
// Grayscale images starts at index zero in every dimension.
|
||||||
|
convert.dim(0).set_min(0);
|
||||||
|
convert.dim(1).set_min(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
HALIDE_REGISTER_GENERATOR(RgbGray, rgb_gray_generator)
|
85
mediapipe/util/frame_buffer/halide/rgb_resize_generator.cc
Normal file
85
mediapipe/util/frame_buffer/halide/rgb_resize_generator.cc
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
// Copyright 2023 The MediaPipe Authors.
|
||||||
|
//
|
||||||
|
// 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 "Halide.h"
|
||||||
|
#include "mediapipe/util/frame_buffer/halide/common.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using ::Halide::BoundaryConditions::repeat_edge;
|
||||||
|
using ::mediapipe::frame_buffer::halide::common::resize_bilinear_int;
|
||||||
|
|
||||||
|
class RgbResize : public Halide::Generator<RgbResize> {
|
||||||
|
public:
|
||||||
|
Var x{"x"}, y{"y"};
|
||||||
|
|
||||||
|
Input<Buffer<uint8_t, 3>> src_rgb{"src_rgb"};
|
||||||
|
Input<float> scale_x{"scale_x", 1.0f, 0.0f, 1024.0f};
|
||||||
|
Input<float> scale_y{"scale_y", 1.0f, 0.0f, 1024.0f};
|
||||||
|
|
||||||
|
Output<Func> dst_rgb{"dst_rgb", UInt(8), 3};
|
||||||
|
|
||||||
|
void generate();
|
||||||
|
void schedule();
|
||||||
|
};
|
||||||
|
|
||||||
|
void RgbResize::generate() {
|
||||||
|
// Resize each of the RGB planes independently.
|
||||||
|
resize_bilinear_int(repeat_edge(src_rgb), dst_rgb, scale_x, scale_y);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RgbResize::schedule() {
|
||||||
|
Halide::Func dst_rgb_func = dst_rgb;
|
||||||
|
Halide::Var c = dst_rgb_func.args()[2];
|
||||||
|
Halide::OutputImageParam rgb_output = dst_rgb_func.output_buffer();
|
||||||
|
Halide::Expr input_rgb_channels = src_rgb.dim(2).extent();
|
||||||
|
Halide::Expr output_rgb_channels = rgb_output.dim(2).extent();
|
||||||
|
Halide::Expr min_width =
|
||||||
|
Halide::min(src_rgb.dim(0).extent(), rgb_output.dim(0).extent());
|
||||||
|
|
||||||
|
// Specialize the generated code for RGB and RGBA (input and output channels
|
||||||
|
// must match); further, specialize the vectorized implementation so it only
|
||||||
|
// runs on images wide enough to support it.
|
||||||
|
const int vector_size = natural_vector_size<uint8_t>();
|
||||||
|
const Expr channel_specializations[] = {
|
||||||
|
input_rgb_channels == 3 && output_rgb_channels == 3,
|
||||||
|
input_rgb_channels == 4 && output_rgb_channels == 4,
|
||||||
|
};
|
||||||
|
dst_rgb_func.reorder(c, x, y);
|
||||||
|
for (const Expr& channel_specialization : channel_specializations) {
|
||||||
|
dst_rgb_func.specialize(channel_specialization && min_width >= vector_size)
|
||||||
|
.unroll(c)
|
||||||
|
.vectorize(x, vector_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Require that the input/output buffer be interleaved and tightly-
|
||||||
|
// packed; that is, either RGBRGBRGB[...] or RGBARGBARGBA[...],
|
||||||
|
// without gaps between pixels.
|
||||||
|
src_rgb.dim(0).set_stride(input_rgb_channels);
|
||||||
|
src_rgb.dim(2).set_stride(1);
|
||||||
|
rgb_output.dim(0).set_stride(output_rgb_channels);
|
||||||
|
rgb_output.dim(2).set_stride(1);
|
||||||
|
|
||||||
|
// RGB planes starts at index zero in every dimension.
|
||||||
|
src_rgb.dim(0).set_min(0);
|
||||||
|
src_rgb.dim(1).set_min(0);
|
||||||
|
src_rgb.dim(2).set_min(0);
|
||||||
|
rgb_output.dim(0).set_min(0);
|
||||||
|
rgb_output.dim(1).set_min(0);
|
||||||
|
rgb_output.dim(2).set_min(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
HALIDE_REGISTER_GENERATOR(RgbResize, rgb_resize_generator)
|
64
mediapipe/util/frame_buffer/halide/rgb_rgb_generator.cc
Normal file
64
mediapipe/util/frame_buffer/halide/rgb_rgb_generator.cc
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
// Copyright 2023 The MediaPipe Authors.
|
||||||
|
//
|
||||||
|
// 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 <cstdint>
|
||||||
|
|
||||||
|
#include "Halide.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Convert rgb_buffer between 3 and 4 channels. When converting from 3 channels
|
||||||
|
// to 4 channels, the alpha value is always 255.
|
||||||
|
class RgbRgb : public Halide::Generator<RgbRgb> {
|
||||||
|
public:
|
||||||
|
Var x{"x"}, y{"y"}, c{"c"};
|
||||||
|
|
||||||
|
Input<Buffer<uint8_t, 3>> src_rgb{"src_rgb"};
|
||||||
|
Output<Buffer<uint8_t, 3>> dst_rgb{"dst_rgb"};
|
||||||
|
|
||||||
|
void generate();
|
||||||
|
void schedule();
|
||||||
|
};
|
||||||
|
|
||||||
|
void RgbRgb::generate() {
|
||||||
|
// We use Halide::clamp to avoid evaluating src_rgb(x, y, c) when c == 3 and
|
||||||
|
// the src_rgb only has c <= 2 (rgb -> rgba conversion case).
|
||||||
|
dst_rgb(x, y, c) =
|
||||||
|
Halide::select(c == 3, 255, src_rgb(x, y, Halide::clamp(c, 0, 2)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void RgbRgb::schedule() {
|
||||||
|
Halide::Expr input_rgb_channels = src_rgb.dim(2).extent();
|
||||||
|
Halide::Expr output_rgb_channels = dst_rgb.dim(2).extent();
|
||||||
|
|
||||||
|
// The source buffer starts at zero in every dimension and requires an
|
||||||
|
// interleaved format.
|
||||||
|
src_rgb.dim(0).set_min(0);
|
||||||
|
src_rgb.dim(1).set_min(0);
|
||||||
|
src_rgb.dim(2).set_min(0);
|
||||||
|
src_rgb.dim(0).set_stride(input_rgb_channels);
|
||||||
|
src_rgb.dim(2).set_stride(1);
|
||||||
|
|
||||||
|
// The destination buffer starts at zero in every dimension and requires an
|
||||||
|
// interleaved format.
|
||||||
|
dst_rgb.dim(0).set_min(0);
|
||||||
|
dst_rgb.dim(1).set_min(0);
|
||||||
|
dst_rgb.dim(2).set_min(0);
|
||||||
|
dst_rgb.dim(0).set_stride(output_rgb_channels);
|
||||||
|
dst_rgb.dim(2).set_stride(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
HALIDE_REGISTER_GENERATOR(RgbRgb, rgb_rgb_generator)
|
76
mediapipe/util/frame_buffer/halide/rgb_rotate_generator.cc
Normal file
76
mediapipe/util/frame_buffer/halide/rgb_rotate_generator.cc
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
// Copyright 2023 The MediaPipe Authors.
|
||||||
|
//
|
||||||
|
// 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 "Halide.h"
|
||||||
|
#include "mediapipe/util/frame_buffer/halide/common.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using ::mediapipe::frame_buffer::halide::common::rotate;
|
||||||
|
|
||||||
|
class RgbRotate : public Halide::Generator<RgbRotate> {
|
||||||
|
public:
|
||||||
|
Var x{"x"}, y{"y"};
|
||||||
|
|
||||||
|
// Input<Buffer> because that allows us to apply constraints on stride, etc.
|
||||||
|
Input<Buffer<uint8_t, 3>> src_rgb{"src_rgb"};
|
||||||
|
// Rotation angle in degrees counter-clockwise. Must be in {0, 90, 180, 270}.
|
||||||
|
Input<int> rotation_angle{"rotation_angle", 0};
|
||||||
|
|
||||||
|
Output<Func> dst_rgb{"dst_rgb", UInt(8), 3};
|
||||||
|
|
||||||
|
void generate();
|
||||||
|
void schedule();
|
||||||
|
};
|
||||||
|
|
||||||
|
void RgbRotate::generate() {
|
||||||
|
const Halide::Expr width = src_rgb.dim(0).extent();
|
||||||
|
const Halide::Expr height = src_rgb.dim(1).extent();
|
||||||
|
|
||||||
|
// Rotate each of the RGB planes independently.
|
||||||
|
rotate(src_rgb, dst_rgb, width, height, rotation_angle);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RgbRotate::schedule() {
|
||||||
|
// TODO: Remove specialization for (angle == 0) since that is
|
||||||
|
// a no-op and callers should simply skip rotation. Doing so would cause
|
||||||
|
// a bounds assertion crash if called with angle=0, however.
|
||||||
|
Halide::Func dst_rgb_func = dst_rgb;
|
||||||
|
Halide::Var c = dst_rgb_func.args()[2];
|
||||||
|
Halide::OutputImageParam rgb_output = dst_rgb_func.output_buffer();
|
||||||
|
dst_rgb_func.specialize(rotation_angle == 0).reorder(c, x, y);
|
||||||
|
dst_rgb_func.specialize(rotation_angle == 90).reorder(c, y, x);
|
||||||
|
dst_rgb_func.specialize(rotation_angle == 180).reorder(c, x, y);
|
||||||
|
dst_rgb_func.specialize(rotation_angle == 270).reorder(c, y, x);
|
||||||
|
|
||||||
|
// RGB planes starts at index zero in every dimension.
|
||||||
|
src_rgb.dim(0).set_min(0);
|
||||||
|
src_rgb.dim(1).set_min(0);
|
||||||
|
src_rgb.dim(2).set_min(0);
|
||||||
|
rgb_output.dim(0).set_min(0);
|
||||||
|
rgb_output.dim(1).set_min(0);
|
||||||
|
rgb_output.dim(2).set_min(0);
|
||||||
|
|
||||||
|
// Require that the input/output buffer be interleaved and tightly-
|
||||||
|
// packed; that is, either RGBRGBRGB[...] or RGBARGBARGBA[...],
|
||||||
|
// without gaps between pixels.
|
||||||
|
src_rgb.dim(0).set_stride(src_rgb.dim(2).extent());
|
||||||
|
src_rgb.dim(2).set_stride(1);
|
||||||
|
rgb_output.dim(0).set_stride(rgb_output.dim(2).extent());
|
||||||
|
rgb_output.dim(2).set_stride(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
HALIDE_REGISTER_GENERATOR(RgbRotate, rgb_rotate_generator)
|
101
mediapipe/util/frame_buffer/halide/rgb_yuv_generator.cc
Normal file
101
mediapipe/util/frame_buffer/halide/rgb_yuv_generator.cc
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
// Copyright 2023 The MediaPipe Authors.
|
||||||
|
//
|
||||||
|
// 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 "Halide.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
class RgbYuv : public Halide::Generator<RgbYuv> {
|
||||||
|
public:
|
||||||
|
Var x{"x"}, y{"y"}, c{"c"};
|
||||||
|
|
||||||
|
// Input<Buffer> because that allows us to apply constraints on stride, etc.
|
||||||
|
Input<Buffer<uint8_t, 3>> src_rgb{"rgb"};
|
||||||
|
|
||||||
|
Output<Func> dst_y{"dst_y", UInt(8), 2};
|
||||||
|
Output<Func> dst_uv{"dst_uv", UInt(8), 3};
|
||||||
|
|
||||||
|
void generate();
|
||||||
|
void schedule();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Integer math versions of the full-range JFIF RGB-YUV coefficients.
|
||||||
|
// Y = 0.2990*R + 0.5870*G + 0.1140*B
|
||||||
|
// U = -0.1687*R - 0.3313*G + 0.5000*B + 128
|
||||||
|
// V = 0.5000*R - 0.4187*G - 0.0813*B + 128
|
||||||
|
// See https://www.w3.org/Graphics/JPEG/jfif3.pdf. These coefficients are
|
||||||
|
// similar to, but not identical, to those used in Android.
|
||||||
|
Halide::Tuple rgbyuv(Halide::Expr r, Halide::Expr g, Halide::Expr b) {
|
||||||
|
r = Halide::cast<int32_t>(r);
|
||||||
|
g = Halide::cast<int32_t>(g);
|
||||||
|
b = Halide::cast<int32_t>(b);
|
||||||
|
return {
|
||||||
|
(19595 * r + 38470 * g + 7474 * b + 32768) >> 16,
|
||||||
|
((-11056 * r - 21712 * g + 32768 * b + 32768) >> 16) + 128,
|
||||||
|
((32768 * r - 27440 * g - 5328 * b + 32768) >> 16) + 128,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void RgbYuv::generate() {
|
||||||
|
Halide::Func yuv_tuple("yuv_tuple");
|
||||||
|
yuv_tuple(x, y) =
|
||||||
|
rgbyuv(src_rgb(x, y, 0), src_rgb(x, y, 1), src_rgb(x, y, 2));
|
||||||
|
|
||||||
|
// Y values are copied one-for-one; UV values are sampled 1/4.
|
||||||
|
// TODO: Take the average UV values across the 2x2 block.
|
||||||
|
dst_y(x, y) = Halide::saturating_cast<uint8_t>(yuv_tuple(x, y)[0]);
|
||||||
|
dst_uv(x, y, c) = Halide::saturating_cast<uint8_t>(Halide::select(
|
||||||
|
c == 0, yuv_tuple(x * 2, y * 2)[2], yuv_tuple(x * 2, y * 2)[1]));
|
||||||
|
// NOTE: uv channel indices above assume NV21; this can be abstracted out
|
||||||
|
// by twiddling strides in calling code.
|
||||||
|
}
|
||||||
|
|
||||||
|
void RgbYuv::schedule() {
|
||||||
|
// RGB images starts at index zero in every dimension.
|
||||||
|
src_rgb.dim(0).set_min(0);
|
||||||
|
src_rgb.dim(1).set_min(0);
|
||||||
|
src_rgb.dim(2).set_min(0);
|
||||||
|
|
||||||
|
// Require that the input buffer be interleaved and tightly-packed;
|
||||||
|
// that is, either RGBRGBRGB[...] or RGBARGBARGBA[...], without gaps
|
||||||
|
// between pixels.
|
||||||
|
src_rgb.dim(0).set_stride(src_rgb.dim(2).extent());
|
||||||
|
src_rgb.dim(2).set_stride(1);
|
||||||
|
|
||||||
|
// Y plane dimensions start at zero. We could additionally constrain the
|
||||||
|
// extent to be even, but that doesn't seem to have any benefit.
|
||||||
|
Halide::Func dst_y_func = dst_y;
|
||||||
|
Halide::OutputImageParam dst_y_output = dst_y_func.output_buffer();
|
||||||
|
dst_y_output.dim(0).set_min(0);
|
||||||
|
dst_y_output.dim(1).set_min(0);
|
||||||
|
|
||||||
|
// UV plane has two channels and is half the size of the Y plane in X/Y.
|
||||||
|
Halide::Func dst_uv_func = dst_uv;
|
||||||
|
Halide::OutputImageParam dst_uv_output = dst_uv_func.output_buffer();
|
||||||
|
dst_uv_output.dim(0).set_bounds(0, (dst_y_output.dim(0).extent() + 1) / 2);
|
||||||
|
dst_uv_output.dim(1).set_bounds(0, (dst_y_output.dim(1).extent() + 1) / 2);
|
||||||
|
dst_uv_output.dim(2).set_bounds(0, 2);
|
||||||
|
|
||||||
|
// UV channel processing should be loop unrolled.
|
||||||
|
dst_uv_func.reorder(c, x, y);
|
||||||
|
dst_uv_func.unroll(c);
|
||||||
|
|
||||||
|
// Remove default memory layout constraints and accept/produce generic UV
|
||||||
|
// (including semi-planar and planar).
|
||||||
|
dst_uv_output.dim(0).set_stride(Expr());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
HALIDE_REGISTER_GENERATOR(RgbYuv, rgb_yuv_generator)
|
90
mediapipe/util/frame_buffer/halide/yuv_flip_generator.cc
Normal file
90
mediapipe/util/frame_buffer/halide/yuv_flip_generator.cc
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
// Copyright 2023 The MediaPipe Authors.
|
||||||
|
//
|
||||||
|
// 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 "Halide.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using ::Halide::_;
|
||||||
|
|
||||||
|
class YuvFlip : public Halide::Generator<YuvFlip> {
|
||||||
|
public:
|
||||||
|
Var x{"x"}, y{"y"};
|
||||||
|
|
||||||
|
// Input<Buffer> because that allows us to apply constraints on stride, etc.
|
||||||
|
Input<Buffer<uint8_t, 2>> src_y{"src_y"};
|
||||||
|
Input<Buffer<uint8_t, 3>> src_uv{"src_uv"};
|
||||||
|
// Flip vertically if true; flips horizontally (mirroring) otherwise.
|
||||||
|
Input<bool> flip_vertical{"flip_vertical", false};
|
||||||
|
|
||||||
|
Output<Func> dst_y{"dst_y", UInt(8), 2};
|
||||||
|
Output<Func> dst_uv{"dst_uv", UInt(8), 3};
|
||||||
|
|
||||||
|
void generate();
|
||||||
|
void schedule();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void flip(Func input, Func result, Expr width, Expr height, Expr vertical);
|
||||||
|
};
|
||||||
|
|
||||||
|
void YuvFlip::flip(Halide::Func input, Halide::Func result, Halide::Expr width,
|
||||||
|
Halide::Expr height, Halide::Expr vertical) {
|
||||||
|
Halide::Func flip_x, flip_y;
|
||||||
|
flip_x(x, y, _) = input(width - x - 1, y, _);
|
||||||
|
flip_y(x, y, _) = input(x, height - y - 1, _);
|
||||||
|
|
||||||
|
result(x, y, _) = select(vertical, flip_y(x, y, _), flip_x(x, y, _));
|
||||||
|
}
|
||||||
|
|
||||||
|
void YuvFlip::generate() {
|
||||||
|
const Halide::Expr width = src_y.dim(0).extent();
|
||||||
|
const Halide::Expr height = src_y.dim(1).extent();
|
||||||
|
|
||||||
|
// Flip each of the YUV planes independently.
|
||||||
|
flip(src_y, dst_y, width, height, flip_vertical);
|
||||||
|
flip(src_uv, dst_uv, (width + 1) / 2, (height + 1) / 2, flip_vertical);
|
||||||
|
}
|
||||||
|
|
||||||
|
void YuvFlip::schedule() {
|
||||||
|
Halide::Func dst_y_func = dst_y;
|
||||||
|
Halide::Func dst_uv_func = dst_uv;
|
||||||
|
Halide::Var c = dst_uv_func.args()[2];
|
||||||
|
dst_uv_func.unroll(c);
|
||||||
|
dst_uv_func.reorder(c, x, y);
|
||||||
|
|
||||||
|
// Y plane dimensions start at zero and destination bounds must match.
|
||||||
|
Halide::OutputImageParam dst_y_output = dst_y_func.output_buffer();
|
||||||
|
src_y.dim(0).set_min(0);
|
||||||
|
src_y.dim(1).set_min(0);
|
||||||
|
dst_y_output.dim(0).set_bounds(0, src_y.dim(0).extent());
|
||||||
|
dst_y_output.dim(1).set_bounds(0, src_y.dim(1).extent());
|
||||||
|
|
||||||
|
// UV plane has two channels and is half the size of the Y plane in X/Y.
|
||||||
|
Halide::OutputImageParam dst_uv_output = dst_uv_func.output_buffer();
|
||||||
|
src_uv.dim(0).set_bounds(0, (src_y.dim(0).extent() + 1) / 2);
|
||||||
|
src_uv.dim(1).set_bounds(0, (src_y.dim(1).extent() + 1) / 2);
|
||||||
|
src_uv.dim(2).set_bounds(0, 2);
|
||||||
|
dst_uv_output.dim(0).set_bounds(0, (dst_y_output.dim(0).extent() + 1) / 2);
|
||||||
|
dst_uv_output.dim(1).set_bounds(0, (dst_y_output.dim(1).extent() + 1) / 2);
|
||||||
|
dst_uv_output.dim(2).set_bounds(0, 2);
|
||||||
|
|
||||||
|
// Remove default memory layout constraints and accept/produce generic UV
|
||||||
|
// (including semi-planar and planar).
|
||||||
|
src_uv.dim(0).set_stride(Expr());
|
||||||
|
dst_uv_output.dim(0).set_stride(Expr());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
HALIDE_REGISTER_GENERATOR(YuvFlip, yuv_flip_generator)
|
91
mediapipe/util/frame_buffer/halide/yuv_resize_generator.cc
Normal file
91
mediapipe/util/frame_buffer/halide/yuv_resize_generator.cc
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
// Copyright 2023 The MediaPipe Authors.
|
||||||
|
//
|
||||||
|
// 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 "Halide.h"
|
||||||
|
#include "mediapipe/util/frame_buffer/halide/common.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using ::Halide::BoundaryConditions::repeat_edge;
|
||||||
|
using ::mediapipe::frame_buffer::halide::common::is_interleaved;
|
||||||
|
using ::mediapipe::frame_buffer::halide::common::is_planar;
|
||||||
|
using ::mediapipe::frame_buffer::halide::common::resize_bilinear_int;
|
||||||
|
|
||||||
|
class YuvResize : public Halide::Generator<YuvResize> {
|
||||||
|
public:
|
||||||
|
Var x{"x"}, y{"y"};
|
||||||
|
|
||||||
|
Input<Buffer<uint8_t, 2>> src_y{"src_y"};
|
||||||
|
Input<Buffer<uint8_t, 3>> src_uv{"src_uv"};
|
||||||
|
Input<float> scale_x{"scale_x", 1.0f, 0.0f, 1024.0f};
|
||||||
|
Input<float> scale_y{"scale_y", 1.0f, 0.0f, 1024.0f};
|
||||||
|
|
||||||
|
Output<Func> dst_y{"dst_y", UInt(8), 2};
|
||||||
|
Output<Func> dst_uv{"dst_uv", UInt(8), 3};
|
||||||
|
|
||||||
|
void generate();
|
||||||
|
void schedule();
|
||||||
|
};
|
||||||
|
|
||||||
|
void YuvResize::generate() {
|
||||||
|
// Resize each of the YUV planes independently.
|
||||||
|
resize_bilinear_int(repeat_edge(src_y), dst_y, scale_x, scale_y);
|
||||||
|
resize_bilinear_int(repeat_edge(src_uv), dst_uv, scale_x, scale_y);
|
||||||
|
}
|
||||||
|
|
||||||
|
void YuvResize::schedule() {
|
||||||
|
// Y plane dimensions start at zero. We could additionally constrain the
|
||||||
|
// extent to be even, but that doesn't seem to have any benefit.
|
||||||
|
Halide::Func dst_y_func = dst_y;
|
||||||
|
Halide::OutputImageParam dst_y_output = dst_y_func.output_buffer();
|
||||||
|
src_y.dim(0).set_min(0);
|
||||||
|
src_y.dim(1).set_min(0);
|
||||||
|
dst_y_output.dim(0).set_min(0);
|
||||||
|
dst_y_output.dim(1).set_min(0);
|
||||||
|
|
||||||
|
// UV plane has two channels and is half the size of the Y plane in X/Y.
|
||||||
|
Halide::Func dst_uv_func = dst_uv;
|
||||||
|
Halide::OutputImageParam dst_uv_output = dst_uv_func.output_buffer();
|
||||||
|
src_uv.dim(0).set_bounds(0, (src_y.dim(0).extent() + 1) / 2);
|
||||||
|
src_uv.dim(1).set_bounds(0, (src_y.dim(1).extent() + 1) / 2);
|
||||||
|
src_uv.dim(2).set_bounds(0, 2);
|
||||||
|
dst_uv_output.dim(0).set_bounds(0, (dst_y_output.dim(0).extent() + 1) / 2);
|
||||||
|
dst_uv_output.dim(1).set_bounds(0, (dst_y_output.dim(1).extent() + 1) / 2);
|
||||||
|
dst_uv_output.dim(2).set_bounds(0, 2);
|
||||||
|
|
||||||
|
// With bilinear filtering enabled, Y plane resize is profitably vectorizable
|
||||||
|
// though we must ensure that the image is wide enough to support vector
|
||||||
|
// operations.
|
||||||
|
const int vector_size = natural_vector_size<uint8_t>();
|
||||||
|
Halide::Expr min_y_width =
|
||||||
|
Halide::min(src_y.dim(0).extent(), dst_y_output.dim(0).extent());
|
||||||
|
dst_y_func.specialize(min_y_width >= vector_size).vectorize(x, vector_size);
|
||||||
|
|
||||||
|
// Remove default memory layout constraints and generate specialized
|
||||||
|
// fast-path implementations when both UV source and output are either
|
||||||
|
// planar or interleaved. Everything else falls onto a slow path.
|
||||||
|
src_uv.dim(0).set_stride(Expr());
|
||||||
|
dst_uv_output.dim(0).set_stride(Expr());
|
||||||
|
|
||||||
|
Halide::Var c = dst_uv_func.args()[2];
|
||||||
|
dst_uv_func
|
||||||
|
.specialize(is_interleaved(src_uv) && is_interleaved(dst_uv_output))
|
||||||
|
.reorder(c, x, y)
|
||||||
|
.unroll(c);
|
||||||
|
dst_uv_func.specialize(is_planar(src_uv) && is_planar(dst_uv_output));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
HALIDE_REGISTER_GENERATOR(YuvResize, yuv_resize_generator)
|
110
mediapipe/util/frame_buffer/halide/yuv_rgb_generator.cc
Normal file
110
mediapipe/util/frame_buffer/halide/yuv_rgb_generator.cc
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
// Copyright 2023 The MediaPipe Authors.
|
||||||
|
//
|
||||||
|
// 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 "Halide.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
class YuvRgb : public Halide::Generator<YuvRgb> {
|
||||||
|
public:
|
||||||
|
Var x{"x"}, y{"y"}, c{"c"};
|
||||||
|
|
||||||
|
// Input<Buffer> because that allows us to apply constraints on stride, etc.
|
||||||
|
Input<Buffer<uint8_t, 2>> src_y{"src_y"};
|
||||||
|
Input<Buffer<uint8_t, 3>> src_uv{"src_uv"};
|
||||||
|
Input<bool> halve{"halve", false};
|
||||||
|
|
||||||
|
Output<Func> rgb{"rgb", UInt(8), 3};
|
||||||
|
|
||||||
|
void generate();
|
||||||
|
void schedule();
|
||||||
|
};
|
||||||
|
|
||||||
|
Halide::Expr demux(Halide::Expr c, Halide::Tuple values) {
|
||||||
|
return select(c == 0, values[0], c == 1, values[1], c == 2, values[2], 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Integer math versions of the full-range JFIF YUV-RGB coefficients.
|
||||||
|
// R = Y' + 1.40200*(V-128)
|
||||||
|
// G = Y' - 0.34414*(U-128) - 0.71414*(V-128)
|
||||||
|
// B = Y' + 1.77200*(U-128)
|
||||||
|
// See https://www.w3.org/Graphics/JPEG/jfif3.pdf. These coefficients are
|
||||||
|
// similar to, but not identical, to those used in Android.
|
||||||
|
Halide::Tuple yuvrgb(Halide::Expr y, Halide::Expr u, Halide::Expr v) {
|
||||||
|
y = Halide::cast<int32_t>(y);
|
||||||
|
u = Halide::cast<int32_t>(u) - 128;
|
||||||
|
v = Halide::cast<int32_t>(v) - 128;
|
||||||
|
return {
|
||||||
|
y + ((91881 * v + 32768) >> 16),
|
||||||
|
y - ((22544 * u + 46802 * v + 32768) >> 16),
|
||||||
|
y + ((116130 * u + 32768) >> 16),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void YuvRgb::generate() {
|
||||||
|
// Each 2x2 block of Y pixels shares the same UV values, so UV-coordinates
|
||||||
|
// advance half as slowly as Y-coordinates. When taking advantage of the
|
||||||
|
// "free" 2x downsampling, use every UV value but skip every other Y.
|
||||||
|
Halide::Expr yx = select(halve, 2 * x, x), yy = select(halve, 2 * y, y);
|
||||||
|
Halide::Expr uvx = select(halve, x, x / 2), uvy = select(halve, y, y / 2);
|
||||||
|
|
||||||
|
rgb(x, y, c) = Halide::saturating_cast<uint8_t>(demux(
|
||||||
|
c, yuvrgb(src_y(yx, yy), src_uv(uvx, uvy, 1), src_uv(uvx, uvy, 0))));
|
||||||
|
// NOTE: uv channel indices above assume NV21; this can be abstracted out
|
||||||
|
// by twiddling strides in calling code.
|
||||||
|
}
|
||||||
|
|
||||||
|
void YuvRgb::schedule() {
|
||||||
|
// Y plane dimensions start at zero. We could additionally constrain the
|
||||||
|
// extent to be even, but that doesn't seem to have any benefit.
|
||||||
|
src_y.dim(0).set_min(0);
|
||||||
|
src_y.dim(1).set_min(0);
|
||||||
|
|
||||||
|
// UV plane has two channels and is half the size of the Y plane in X/Y.
|
||||||
|
src_uv.dim(0).set_bounds(0, (src_y.dim(0).extent() + 1) / 2);
|
||||||
|
src_uv.dim(1).set_bounds(0, (src_y.dim(1).extent() + 1) / 2);
|
||||||
|
src_uv.dim(2).set_bounds(0, 2);
|
||||||
|
|
||||||
|
// Remove default memory layout constraints on the UV source so that we
|
||||||
|
// accept generic UV (including semi-planar and planar).
|
||||||
|
//
|
||||||
|
// TODO: Investigate whether it's worth specializing the cross-
|
||||||
|
// product of [semi-]planar and RGB/RGBA; this would result in 9 codepaths.
|
||||||
|
src_uv.dim(0).set_stride(Expr());
|
||||||
|
|
||||||
|
Halide::Func rgb_func = rgb;
|
||||||
|
Halide::OutputImageParam rgb_output = rgb_func.output_buffer();
|
||||||
|
Halide::Expr rgb_channels = rgb_output.dim(2).extent();
|
||||||
|
|
||||||
|
// Specialize the generated code for RGB and RGBA.
|
||||||
|
const int vector_size = natural_vector_size<uint8_t>();
|
||||||
|
rgb_func.reorder(c, x, y);
|
||||||
|
rgb_func.specialize(rgb_channels == 3).unroll(c).vectorize(x, vector_size);
|
||||||
|
rgb_func.specialize(rgb_channels == 4).unroll(c).vectorize(x, vector_size);
|
||||||
|
|
||||||
|
// Require that the output buffer be interleaved and tightly-packed;
|
||||||
|
// that is, either RGBRGBRGB[...] or RGBARGBARGBA[...], without gaps
|
||||||
|
// between pixels.
|
||||||
|
rgb_output.dim(0).set_stride(rgb_channels);
|
||||||
|
rgb_output.dim(2).set_stride(1);
|
||||||
|
|
||||||
|
// RGB output starts at index zero in every dimension.
|
||||||
|
rgb_output.dim(0).set_min(0);
|
||||||
|
rgb_output.dim(1).set_min(0);
|
||||||
|
rgb_output.dim(2).set_min(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
HALIDE_REGISTER_GENERATOR(YuvRgb, yuv_rgb_generator)
|
91
mediapipe/util/frame_buffer/halide/yuv_rotate_generator.cc
Normal file
91
mediapipe/util/frame_buffer/halide/yuv_rotate_generator.cc
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
// Copyright 2023 The MediaPipe Authors.
|
||||||
|
//
|
||||||
|
// 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 "Halide.h"
|
||||||
|
#include "mediapipe/util/frame_buffer/halide/common.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using ::mediapipe::frame_buffer::halide::common::rotate;
|
||||||
|
|
||||||
|
class YuvRotate : public Halide::Generator<YuvRotate> {
|
||||||
|
public:
|
||||||
|
Var x{"x"}, y{"y"};
|
||||||
|
|
||||||
|
// Input<Buffer> because that allows us to apply constraints on stride, etc.
|
||||||
|
Input<Buffer<uint8_t, 2>> src_y{"src_y"};
|
||||||
|
Input<Buffer<uint8_t, 3>> src_uv{"src_uv"};
|
||||||
|
// Rotation angle in degrees counter-clockwise. Must be in {0, 90, 180, 270}.
|
||||||
|
Input<int> rotation_angle{"rotation_angle", 0};
|
||||||
|
|
||||||
|
Output<Func> dst_y{"dst_y", UInt(8), 2};
|
||||||
|
Output<Func> dst_uv{"dst_uv", UInt(8), 3};
|
||||||
|
|
||||||
|
void generate();
|
||||||
|
void schedule();
|
||||||
|
};
|
||||||
|
|
||||||
|
void YuvRotate::generate() {
|
||||||
|
const Halide::Expr width = src_y.dim(0).extent();
|
||||||
|
const Halide::Expr height = src_y.dim(1).extent();
|
||||||
|
|
||||||
|
// Rotate each of the YUV planes independently.
|
||||||
|
rotate(src_y, dst_y, width, height, rotation_angle);
|
||||||
|
rotate(src_uv, dst_uv, (width + 1) / 2, (height + 1) / 2, rotation_angle);
|
||||||
|
}
|
||||||
|
|
||||||
|
void YuvRotate::schedule() {
|
||||||
|
// TODO: Remove specialization for (angle == 0) since that is
|
||||||
|
// a no-op and callers should simply skip rotation. Doing so would cause
|
||||||
|
// a bounds assertion crash if called with angle=0, however.
|
||||||
|
Halide::Func dst_y_func = dst_y;
|
||||||
|
dst_y_func.specialize(rotation_angle == 0).reorder(x, y);
|
||||||
|
dst_y_func.specialize(rotation_angle == 90).reorder(y, x);
|
||||||
|
dst_y_func.specialize(rotation_angle == 180).reorder(x, y);
|
||||||
|
dst_y_func.specialize(rotation_angle == 270).reorder(y, x);
|
||||||
|
|
||||||
|
Halide::Func dst_uv_func = dst_uv;
|
||||||
|
Halide::Var c = dst_uv_func.args()[2];
|
||||||
|
dst_uv_func.unroll(c);
|
||||||
|
dst_uv_func.specialize(rotation_angle == 0).reorder(c, x, y);
|
||||||
|
dst_uv_func.specialize(rotation_angle == 90).reorder(c, y, x);
|
||||||
|
dst_uv_func.specialize(rotation_angle == 180).reorder(c, x, y);
|
||||||
|
dst_uv_func.specialize(rotation_angle == 270).reorder(c, y, x);
|
||||||
|
|
||||||
|
// Y plane dimensions start at zero. We could additionally constrain the
|
||||||
|
// extent to be even, but that doesn't seem to have any benefit.
|
||||||
|
Halide::OutputImageParam dst_y_output = dst_y_func.output_buffer();
|
||||||
|
src_y.dim(0).set_min(0);
|
||||||
|
src_y.dim(1).set_min(0);
|
||||||
|
dst_y_output.dim(0).set_min(0);
|
||||||
|
dst_y_output.dim(1).set_min(0);
|
||||||
|
|
||||||
|
// UV plane has two channels and is half the size of the Y plane in X/Y.
|
||||||
|
Halide::OutputImageParam dst_uv_output = dst_uv_func.output_buffer();
|
||||||
|
src_uv.dim(0).set_bounds(0, (src_y.dim(0).extent() + 1) / 2);
|
||||||
|
src_uv.dim(1).set_bounds(0, (src_y.dim(1).extent() + 1) / 2);
|
||||||
|
src_uv.dim(2).set_bounds(0, 2);
|
||||||
|
dst_uv_output.dim(0).set_bounds(0, (dst_y_output.dim(0).extent() + 1) / 2);
|
||||||
|
dst_uv_output.dim(1).set_bounds(0, (dst_y_output.dim(1).extent() + 1) / 2);
|
||||||
|
dst_uv_output.dim(2).set_bounds(0, 2);
|
||||||
|
|
||||||
|
// Remove default memory layout constraints and accept/produce generic UV
|
||||||
|
// (including semi-planar and planar).
|
||||||
|
src_uv.dim(0).set_stride(Expr());
|
||||||
|
dst_uv_output.dim(0).set_stride(Expr());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
HALIDE_REGISTER_GENERATOR(YuvRotate, yuv_rotate_generator)
|
132
mediapipe/util/frame_buffer/rgb_buffer.cc
Normal file
132
mediapipe/util/frame_buffer/rgb_buffer.cc
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
// Copyright 2023 The MediaPipe Authors.
|
||||||
|
//
|
||||||
|
// 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 "mediapipe/util/frame_buffer/rgb_buffer.h"
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "mediapipe/util/frame_buffer/buffer_common.h"
|
||||||
|
#include "mediapipe/util/frame_buffer/gray_buffer.h"
|
||||||
|
#include "mediapipe/util/frame_buffer/halide/rgb_flip_halide.h"
|
||||||
|
#include "mediapipe/util/frame_buffer/halide/rgb_gray_halide.h"
|
||||||
|
#include "mediapipe/util/frame_buffer/halide/rgb_resize_halide.h"
|
||||||
|
#include "mediapipe/util/frame_buffer/halide/rgb_rgb_halide.h"
|
||||||
|
#include "mediapipe/util/frame_buffer/halide/rgb_rotate_halide.h"
|
||||||
|
#include "mediapipe/util/frame_buffer/halide/rgb_yuv_halide.h"
|
||||||
|
#include "mediapipe/util/frame_buffer/yuv_buffer.h"
|
||||||
|
|
||||||
|
namespace mediapipe {
|
||||||
|
namespace frame_buffer {
|
||||||
|
|
||||||
|
RgbBuffer::RgbBuffer(uint8_t* data, int width, int height, bool alpha)
|
||||||
|
: owned_buffer_(nullptr) {
|
||||||
|
Initialize(data, width, height, alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
RgbBuffer::RgbBuffer(uint8_t* data, int width, int height, int row_stride,
|
||||||
|
bool alpha) {
|
||||||
|
const int channels = alpha ? 4 : 3;
|
||||||
|
const halide_dimension_t dimensions[3] = {{/*m=*/0, width, channels},
|
||||||
|
{/*m=*/0, height, row_stride},
|
||||||
|
{/*m=*/0, channels, 1}};
|
||||||
|
buffer_ = Halide::Runtime::Buffer<uint8_t>(data, /*d=*/3, dimensions);
|
||||||
|
}
|
||||||
|
|
||||||
|
RgbBuffer::RgbBuffer(int width, int height, bool alpha)
|
||||||
|
: owned_buffer_(new uint8_t[ByteSize(width, height, alpha)]) {
|
||||||
|
Initialize(owned_buffer_.get(), width, height, alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
RgbBuffer::RgbBuffer(const RgbBuffer& other) : buffer_(other.buffer_) {
|
||||||
|
// Never copy owned_buffer; ownership remains with the source of the copy.
|
||||||
|
}
|
||||||
|
|
||||||
|
RgbBuffer::RgbBuffer(RgbBuffer&& other) { *this = std::move(other); }
|
||||||
|
|
||||||
|
RgbBuffer& RgbBuffer::operator=(const RgbBuffer& other) {
|
||||||
|
if (this != &other) {
|
||||||
|
buffer_ = other.buffer_;
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
RgbBuffer& RgbBuffer::operator=(RgbBuffer&& other) {
|
||||||
|
if (this != &other) {
|
||||||
|
owned_buffer_ = std::move(other.owned_buffer_);
|
||||||
|
buffer_ = other.buffer_;
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
RgbBuffer::~RgbBuffer() {}
|
||||||
|
|
||||||
|
bool RgbBuffer::Crop(int x0, int y0, int x1, int y1) {
|
||||||
|
// Twiddle the buffer start and extents to crop images.
|
||||||
|
return common::crop_buffer(x0, y0, x1, y1, buffer());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RgbBuffer::Resize(RgbBuffer* output) {
|
||||||
|
if (output->channels() > channels()) {
|
||||||
|
// Fail fast; the Halide implementation would otherwise output garbage
|
||||||
|
// alpha values (i.e. duplicate the blue channel into alpha).
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const int result = rgb_resize_halide(
|
||||||
|
buffer(), static_cast<float>(width()) / output->width(),
|
||||||
|
static_cast<float>(height()) / output->height(), output->buffer());
|
||||||
|
return result == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RgbBuffer::Rotate(int angle, RgbBuffer* output) {
|
||||||
|
const int result = rgb_rotate_halide(buffer(), angle, output->buffer());
|
||||||
|
return result == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RgbBuffer::FlipHorizontally(RgbBuffer* output) {
|
||||||
|
const int result = rgb_flip_halide(buffer(),
|
||||||
|
false, // horizontal
|
||||||
|
output->buffer());
|
||||||
|
return result == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RgbBuffer::FlipVertically(RgbBuffer* output) {
|
||||||
|
const int result = rgb_flip_halide(buffer(),
|
||||||
|
true, // vertical
|
||||||
|
output->buffer());
|
||||||
|
return result == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RgbBuffer::Convert(YuvBuffer* output) {
|
||||||
|
const int result =
|
||||||
|
rgb_yuv_halide(buffer(), output->y_buffer(), output->uv_buffer());
|
||||||
|
return result == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RgbBuffer::Convert(GrayBuffer* output) {
|
||||||
|
const int result = rgb_gray_halide(buffer(), output->buffer());
|
||||||
|
return result == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RgbBuffer::Convert(RgbBuffer* output) {
|
||||||
|
const int result = rgb_rgb_halide(buffer(), output->buffer());
|
||||||
|
return result == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RgbBuffer::Initialize(uint8_t* data, int width, int height, bool alpha) {
|
||||||
|
const int channels = alpha ? 4 : 3;
|
||||||
|
buffer_ = Halide::Runtime::Buffer<uint8_t>::make_interleaved(
|
||||||
|
data, width, height, channels);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace frame_buffer
|
||||||
|
} // namespace mediapipe
|
139
mediapipe/util/frame_buffer/rgb_buffer.h
Normal file
139
mediapipe/util/frame_buffer/rgb_buffer.h
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
// Copyright 2023 The MediaPipe Authors.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#ifndef MEDIAPIPE_UTIL_FRAME_BUFFER_RGB_BUFFER_H_
|
||||||
|
#define MEDIAPIPE_UTIL_FRAME_BUFFER_RGB_BUFFER_H_
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "HalideBuffer.h"
|
||||||
|
#include "HalideRuntime.h"
|
||||||
|
#include "mediapipe/util/frame_buffer/gray_buffer.h"
|
||||||
|
#include "mediapipe/util/frame_buffer/yuv_buffer.h"
|
||||||
|
|
||||||
|
namespace mediapipe {
|
||||||
|
namespace frame_buffer {
|
||||||
|
|
||||||
|
// RgbBuffer represents a view over an interleaved RGB/RGBA image.
|
||||||
|
//
|
||||||
|
// RgbBuffers may be copied and moved efficiently; their backing buffers are
|
||||||
|
// shared and never deep copied.
|
||||||
|
//
|
||||||
|
// RgbBuffer requires a minimum image width depending on the natural vector
|
||||||
|
// size of the platform, e.g., 16px. This is not validated by RgbBuffer.
|
||||||
|
class RgbBuffer {
|
||||||
|
public:
|
||||||
|
// Returns the size (in bytes) of an RGB/RGBA image of the given dimensions
|
||||||
|
// without padding.
|
||||||
|
static int ByteSize(int width, int height, bool alpha) {
|
||||||
|
return width * height * (alpha ? 4 : 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Builds a RgbBuffer using the given backing buffer and dimensions.
|
||||||
|
//
|
||||||
|
// Does not take ownership of the backing buffer (provided in 'data').
|
||||||
|
RgbBuffer(uint8_t* data, int width, int height, bool alpha);
|
||||||
|
|
||||||
|
// Builds a RgbBuffer using the given backing buffer and dimensions.
|
||||||
|
// 'row_stride' must be greater than or equal to 'width'. Padding bytes are at
|
||||||
|
// the end of each row, following the image bytes.
|
||||||
|
//
|
||||||
|
// Does not take ownership of the backing buffer (provided in 'data').
|
||||||
|
RgbBuffer(uint8_t* data, int width, int height, int row_stride, bool alpha);
|
||||||
|
|
||||||
|
// Builds a RgbBuffer using the given dimensions.
|
||||||
|
//
|
||||||
|
// The underlying backing buffer is allocated and owned by this RgbBuffer.
|
||||||
|
RgbBuffer(int width, int height, bool alpha);
|
||||||
|
|
||||||
|
// RgbBuffer is copyable. The source retains ownership of its backing buffer.
|
||||||
|
RgbBuffer(const RgbBuffer& other);
|
||||||
|
// RgbBuffer is moveable. The source loses ownership of any backing buffers.
|
||||||
|
RgbBuffer(RgbBuffer&& other);
|
||||||
|
// RgbBuffer is assignable.
|
||||||
|
RgbBuffer& operator=(const RgbBuffer& other);
|
||||||
|
RgbBuffer& operator=(RgbBuffer&& other);
|
||||||
|
|
||||||
|
~RgbBuffer();
|
||||||
|
|
||||||
|
// Performs an in-place crop. Modifies this buffer so that the new extent
|
||||||
|
// matches that of the given crop rectangle -- (x0, y0) becomes (0, 0) and
|
||||||
|
// the new width and height are x1 - x0 + 1 and y1 - y0 + 1, respectively.
|
||||||
|
bool Crop(int x0, int y0, int x1, int y1);
|
||||||
|
|
||||||
|
// Resize this image to match the dimensions of the given output RgbBuffer
|
||||||
|
// and places the result into its backing buffer.
|
||||||
|
//
|
||||||
|
// Performs a resize with bilinear interpolation (over four source pixels).
|
||||||
|
// Resizing with an RGB source buffer and RGBA destination is currently
|
||||||
|
// unsupported.
|
||||||
|
bool Resize(RgbBuffer* output);
|
||||||
|
|
||||||
|
// Rotate this image into the given buffer by the given angle (90, 180, 270).
|
||||||
|
//
|
||||||
|
// Rotation is specified in degrees counter-clockwise such that when rotating
|
||||||
|
// by 90 degrees, the top-right corner of the source becomes the top-left of
|
||||||
|
// the output. The output buffer must have its height and width swapped when
|
||||||
|
// rotating by 90 or 270.
|
||||||
|
//
|
||||||
|
// Any angle values other than (90, 180, 270) are invalid.
|
||||||
|
bool Rotate(int angle, RgbBuffer* output);
|
||||||
|
|
||||||
|
// Flip this image horizontally/vertically into the given buffer. Both buffer
|
||||||
|
// dimensions and formats must match (this method does not convert RGB-to-RGBA
|
||||||
|
// nor RGBA-to-RGB).
|
||||||
|
bool FlipHorizontally(RgbBuffer* output);
|
||||||
|
bool FlipVertically(RgbBuffer* output);
|
||||||
|
|
||||||
|
// Performs a RGB-to-YUV color format conversion and places the result
|
||||||
|
// in the given output YuvBuffer. Both buffer dimensions must match.
|
||||||
|
bool Convert(YuvBuffer* output);
|
||||||
|
|
||||||
|
// Performs a RGB to grayscale format conversion.
|
||||||
|
bool Convert(GrayBuffer* output);
|
||||||
|
|
||||||
|
// Performs a rgb to rgba / rgba to rgb format conversion.
|
||||||
|
bool Convert(RgbBuffer* output);
|
||||||
|
|
||||||
|
// Release ownership of the owned backing buffer.
|
||||||
|
uint8_t* Release() { return owned_buffer_.release(); }
|
||||||
|
|
||||||
|
// Returns the halide_buffer_t* for the image.
|
||||||
|
const halide_buffer_t* buffer() const { return buffer_.raw_buffer(); }
|
||||||
|
// Returns the halide_buffer_t* for the image.
|
||||||
|
halide_buffer_t* buffer() { return buffer_.raw_buffer(); }
|
||||||
|
|
||||||
|
// Returns the image width.
|
||||||
|
const int width() const { return buffer_.dim(0).extent(); }
|
||||||
|
// Returns the image height.
|
||||||
|
const int height() const { return buffer_.dim(1).extent(); }
|
||||||
|
// Returns the number of color channels (3, or 4 if RGBA).
|
||||||
|
const int channels() const { return buffer_.dim(2).extent(); }
|
||||||
|
// Returns the image row stride.
|
||||||
|
const int row_stride() const { return buffer_.dim(1).stride(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void Initialize(uint8_t* data, int width, int height, bool alpha);
|
||||||
|
|
||||||
|
// Non-NULL iff this RgbBuffer owns its backing buffer.
|
||||||
|
std::unique_ptr<uint8_t[]> owned_buffer_;
|
||||||
|
|
||||||
|
// Backing buffer: layout is always width x height x channel (interleaved).
|
||||||
|
Halide::Runtime::Buffer<uint8_t> buffer_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace frame_buffer
|
||||||
|
} // namespace mediapipe
|
||||||
|
|
||||||
|
#endif // MEDIAPIPE_UTIL_FRAME_BUFFER_RGB_BUFFER_H_
|
606
mediapipe/util/frame_buffer/rgb_buffer_test.cc
Normal file
606
mediapipe/util/frame_buffer/rgb_buffer_test.cc
Normal file
|
@ -0,0 +1,606 @@
|
||||||
|
// Copyright 2023 The MediaPipe Authors.
|
||||||
|
//
|
||||||
|
// 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 "mediapipe/util/frame_buffer/rgb_buffer.h"
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "absl/log/log.h"
|
||||||
|
#include "mediapipe/framework/port/gmock.h"
|
||||||
|
#include "mediapipe/framework/port/gtest.h"
|
||||||
|
#include "mediapipe/util/frame_buffer/gray_buffer.h"
|
||||||
|
#include "mediapipe/util/frame_buffer/yuv_buffer.h"
|
||||||
|
|
||||||
|
// The default implementation of halide_error calls abort(), which we don't
|
||||||
|
// want. Instead, log the error and let the filter invocation fail.
|
||||||
|
extern "C" void halide_error(void*, const char* message) {
|
||||||
|
LOG(ERROR) << "Halide Error: " << message;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace mediapipe {
|
||||||
|
namespace frame_buffer {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Fill a halide_buffer_t channel with the given value.
|
||||||
|
void Fill(halide_buffer_t* buffer, int channel, int value) {
|
||||||
|
for (int y = 0; y < buffer->dim[1].extent; ++y) {
|
||||||
|
for (int x = 0; x < buffer->dim[0].extent; ++x) {
|
||||||
|
buffer->host[buffer->dim[1].stride * y + buffer->dim[0].stride * x +
|
||||||
|
buffer->dim[2].stride * channel] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill an RgbBuffer with (0, 0, 0). Fills the alpha channel if present.
|
||||||
|
void Fill(RgbBuffer* buffer) {
|
||||||
|
for (int c = 0; c < buffer->channels(); ++c) {
|
||||||
|
Fill(buffer->buffer(), c, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a padded RGB buffer. The metadata are defined as width: 4, height: 2,
|
||||||
|
// row_stride: 18, channels: 3.
|
||||||
|
RgbBuffer GetPaddedRgbBuffer() {
|
||||||
|
static uint8_t rgb_buffer_with_padding[] = {
|
||||||
|
10, 20, 30, 20, 30, 40, 30, 40, 50, 40, 50, 60, 0, 0, 0, 0, 0, 0,
|
||||||
|
20, 40, 60, 40, 60, 80, 60, 80, 100, 80, 100, 120, 0, 0, 0, 0, 0, 0};
|
||||||
|
return RgbBuffer(rgb_buffer_with_padding,
|
||||||
|
/*width=*/4, /*height=*/2,
|
||||||
|
/*row_stride=*/18, /*alpha=*/false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a padded RGB buffer. The metadata are defined as width: 4, height: 2,
|
||||||
|
// row_stride: 24, channels: 4.
|
||||||
|
RgbBuffer GetPaddedRgbaBuffer() {
|
||||||
|
static uint8_t rgb_buffer_with_padding[] = {
|
||||||
|
10, 20, 30, 255, 20, 30, 40, 255, 30, 40, 50, 255, 40, 50, 60, 255,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 20, 40, 60, 255, 40, 60, 80, 255,
|
||||||
|
60, 80, 100, 255, 80, 100, 120, 255, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||||
|
return RgbBuffer(rgb_buffer_with_padding,
|
||||||
|
/*width=*/4, /*height=*/2,
|
||||||
|
/*row_stride=*/24, /*alpha=*/true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Consider move these helper methods into a util class.
|
||||||
|
// Returns true if the data in the two arrays are the same. Otherwise, return
|
||||||
|
// false.
|
||||||
|
bool CompareArray(const uint8_t* lhs_ptr, const uint8_t* rhs_ptr, int width,
|
||||||
|
int height) {
|
||||||
|
for (int i = 0; i < height; ++i) {
|
||||||
|
for (int j = 0; j < width; ++j) {
|
||||||
|
if (lhs_ptr[i * width + j] != rhs_ptr[i * width + j]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if the halide buffers of two input GrayBuffer are identical.
|
||||||
|
// Otherwise, returns false;
|
||||||
|
bool CompareBuffer(const GrayBuffer& lhs, const GrayBuffer& rhs) {
|
||||||
|
if (lhs.width() != rhs.width() || lhs.height() != rhs.height()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const uint8_t* reference_ptr = const_cast<GrayBuffer&>(lhs).buffer()->host;
|
||||||
|
const uint8_t* converted_ptr = const_cast<GrayBuffer&>(rhs).buffer()->host;
|
||||||
|
return CompareArray(reference_ptr, converted_ptr, lhs.width(), lhs.height());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if the halide buffers of two input RgbBuffer are identical.
|
||||||
|
// Otherwise, returns false;
|
||||||
|
bool CompareBuffer(const RgbBuffer& lhs, const RgbBuffer& rhs) {
|
||||||
|
if (lhs.width() != rhs.width() || lhs.height() != rhs.height() ||
|
||||||
|
lhs.row_stride() != rhs.row_stride() ||
|
||||||
|
lhs.channels() != rhs.channels()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const uint8_t* reference_ptr = const_cast<RgbBuffer&>(lhs).buffer()->host;
|
||||||
|
const uint8_t* converted_ptr = const_cast<RgbBuffer&>(rhs).buffer()->host;
|
||||||
|
return CompareArray(reference_ptr, converted_ptr, lhs.row_stride(),
|
||||||
|
lhs.height());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if the halide buffers of two input YuvBuffer are identical.
|
||||||
|
// Otherwise, returns false;
|
||||||
|
bool CompareBuffer(const YuvBuffer& lhs, const YuvBuffer& rhs) {
|
||||||
|
if (lhs.width() != rhs.width() || lhs.height() != rhs.height()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const uint8_t* reference_ptr = const_cast<YuvBuffer&>(lhs).y_buffer()->host;
|
||||||
|
const uint8_t* converted_ptr = const_cast<YuvBuffer&>(rhs).y_buffer()->host;
|
||||||
|
if (!CompareArray(reference_ptr, converted_ptr, lhs.width(), lhs.height())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
reference_ptr = const_cast<YuvBuffer&>(lhs).uv_buffer()->host;
|
||||||
|
converted_ptr = const_cast<YuvBuffer&>(rhs).uv_buffer()->host;
|
||||||
|
return CompareArray(reference_ptr, converted_ptr, lhs.width(),
|
||||||
|
lhs.height() / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RgbBufferTest, Properties) {
|
||||||
|
RgbBuffer rgb(2, 8, false), rgba(2, 8, true);
|
||||||
|
EXPECT_EQ(2, rgb.width());
|
||||||
|
EXPECT_EQ(8, rgb.height());
|
||||||
|
EXPECT_EQ(3, rgb.channels());
|
||||||
|
|
||||||
|
EXPECT_EQ(2, rgba.width());
|
||||||
|
EXPECT_EQ(8, rgba.height());
|
||||||
|
EXPECT_EQ(4, rgba.channels());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RgbBufferTest, PropertiesOfPaddedRgb) {
|
||||||
|
RgbBuffer rgb_buffer = GetPaddedRgbBuffer();
|
||||||
|
EXPECT_EQ(rgb_buffer.width(), 4);
|
||||||
|
EXPECT_EQ(rgb_buffer.height(), 2);
|
||||||
|
EXPECT_EQ(rgb_buffer.row_stride(), 18);
|
||||||
|
EXPECT_EQ(rgb_buffer.channels(), 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RgbBufferTest, PropertiesOfPaddedRgba) {
|
||||||
|
RgbBuffer rgb_buffer = GetPaddedRgbaBuffer();
|
||||||
|
EXPECT_EQ(rgb_buffer.width(), 4);
|
||||||
|
EXPECT_EQ(rgb_buffer.height(), 2);
|
||||||
|
EXPECT_EQ(rgb_buffer.row_stride(), 24);
|
||||||
|
EXPECT_EQ(rgb_buffer.channels(), 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RgbBufferTest, Release) {
|
||||||
|
RgbBuffer source(8, 8, true);
|
||||||
|
delete[] source.Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RgbBufferTest, Assign) {
|
||||||
|
RgbBuffer source(8, 8, false);
|
||||||
|
RgbBuffer sink(nullptr, 0, 0, false);
|
||||||
|
sink = source;
|
||||||
|
EXPECT_EQ(8, sink.width());
|
||||||
|
EXPECT_EQ(8, sink.height());
|
||||||
|
EXPECT_EQ(3, sink.channels());
|
||||||
|
|
||||||
|
sink = RgbBuffer(16, 16, true);
|
||||||
|
EXPECT_EQ(16, sink.width());
|
||||||
|
EXPECT_EQ(16, sink.height());
|
||||||
|
EXPECT_EQ(4, sink.channels());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RgbBufferTest, MoveAssign) {
|
||||||
|
RgbBuffer source(8, 8, false);
|
||||||
|
RgbBuffer sink(nullptr, 0, 0, true);
|
||||||
|
sink = std::move(source);
|
||||||
|
EXPECT_EQ(nullptr, source.Release());
|
||||||
|
EXPECT_EQ(8, sink.width());
|
||||||
|
EXPECT_EQ(8, sink.height());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RgbBufferTest, MoveConstructor) {
|
||||||
|
RgbBuffer source(8, 8, false);
|
||||||
|
RgbBuffer sink(std::move(source));
|
||||||
|
EXPECT_EQ(nullptr, source.Release());
|
||||||
|
EXPECT_EQ(8, sink.width());
|
||||||
|
EXPECT_EQ(8, sink.height());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RgbBufferTest, RgbCrop) {
|
||||||
|
RgbBuffer source(8, 8, false);
|
||||||
|
EXPECT_TRUE(source.Crop(2, 2, 6, 6));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RgbBufferTest, RgbaCrop) {
|
||||||
|
RgbBuffer source(8, 8, true);
|
||||||
|
EXPECT_TRUE(source.Crop(2, 2, 6, 6));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some operations expect images with a platform-dependent minimum width
|
||||||
|
// because their implementations are vectorized.
|
||||||
|
|
||||||
|
TEST(RgbBufferTest, RgbResize) {
|
||||||
|
RgbBuffer source(128, 8, false);
|
||||||
|
RgbBuffer result(32, 4, false);
|
||||||
|
Fill(&source);
|
||||||
|
EXPECT_TRUE(source.Resize(&result));
|
||||||
|
|
||||||
|
// Test odd result sizes too.
|
||||||
|
source = RgbBuffer(64, 16, false);
|
||||||
|
result = RgbBuffer(32, 7, false);
|
||||||
|
Fill(&source);
|
||||||
|
EXPECT_TRUE(source.Resize(&result));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RgbBufferTest, RgbaResize) {
|
||||||
|
RgbBuffer source(128, 8, true);
|
||||||
|
RgbBuffer result(32, 4, true);
|
||||||
|
Fill(&source);
|
||||||
|
EXPECT_TRUE(source.Resize(&result));
|
||||||
|
|
||||||
|
// Test odd result sizes too.
|
||||||
|
source = RgbBuffer(64, 16, true);
|
||||||
|
result = RgbBuffer(32, 7, true);
|
||||||
|
Fill(&source);
|
||||||
|
EXPECT_TRUE(source.Resize(&result));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: RGB-to-RGBA conversion currently doesn't work.
|
||||||
|
TEST(RgbBufferTest, RgbResizeDifferentFormat) {
|
||||||
|
RgbBuffer source(128, 8, false);
|
||||||
|
RgbBuffer result(16, 4, true);
|
||||||
|
Fill(&source);
|
||||||
|
EXPECT_FALSE(source.Resize(&result));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RgbBufferTest, RgbaResizeDifferentFormat) {
|
||||||
|
RgbBuffer source(128, 8, true);
|
||||||
|
RgbBuffer result(16, 4, false);
|
||||||
|
Fill(&source);
|
||||||
|
EXPECT_TRUE(source.Resize(&result));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RgbBufferTest, PaddedRgbResize) {
|
||||||
|
const int target_width = 2;
|
||||||
|
const int target_height = 1;
|
||||||
|
RgbBuffer source = GetPaddedRgbBuffer();
|
||||||
|
RgbBuffer result(target_width, target_height, /*alpha=*/false);
|
||||||
|
|
||||||
|
ASSERT_TRUE(source.Resize(&result));
|
||||||
|
EXPECT_EQ(result.width(), target_width);
|
||||||
|
EXPECT_EQ(result.height(), target_height);
|
||||||
|
EXPECT_EQ(result.channels(), 3);
|
||||||
|
EXPECT_EQ(result.row_stride(), target_width * /*pixel_stride=*/3);
|
||||||
|
|
||||||
|
uint8_t rgb_data[] = {10, 20, 30, 30, 40, 50};
|
||||||
|
RgbBuffer rgb_buffer =
|
||||||
|
RgbBuffer(rgb_data, target_width, target_height, /*alpha=*/false);
|
||||||
|
EXPECT_TRUE(CompareBuffer(rgb_buffer, result));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RgbBufferTest, PaddedRgbaResize) {
|
||||||
|
const int target_width = 2;
|
||||||
|
const int target_height = 1;
|
||||||
|
RgbBuffer source = GetPaddedRgbaBuffer();
|
||||||
|
RgbBuffer result(target_width, target_height, /*alpha=*/true);
|
||||||
|
|
||||||
|
ASSERT_TRUE(source.Resize(&result));
|
||||||
|
EXPECT_EQ(result.width(), target_width);
|
||||||
|
EXPECT_EQ(result.height(), target_height);
|
||||||
|
EXPECT_EQ(result.channels(), 4);
|
||||||
|
EXPECT_EQ(result.row_stride(), target_width * /*pixel_stride=*/4);
|
||||||
|
|
||||||
|
uint8_t rgb_data[] = {10, 20, 30, 255, 30, 40, 50, 255};
|
||||||
|
RgbBuffer rgb_buffer =
|
||||||
|
RgbBuffer(rgb_data, target_width, target_height, /*alpha=*/true);
|
||||||
|
EXPECT_TRUE(CompareBuffer(rgb_buffer, result));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RgbBufferTest, RgbRotateCheckSize) {
|
||||||
|
RgbBuffer source(4, 8, false);
|
||||||
|
RgbBuffer result(8, 4, false);
|
||||||
|
Fill(&source);
|
||||||
|
EXPECT_TRUE(source.Rotate(90, &result));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RgbBufferTest, RgbRotateCheckData) {
|
||||||
|
uint8_t* data = new uint8_t[12];
|
||||||
|
data[0] = data[1] = data[2] = 1; // Pixel 1
|
||||||
|
data[3] = data[4] = data[5] = 2; // Pixel 2
|
||||||
|
data[6] = data[7] = data[8] = 3; // Pixel 3
|
||||||
|
data[9] = data[10] = data[11] = 4; // Pixel 4
|
||||||
|
RgbBuffer source(data, 2, 2, false);
|
||||||
|
RgbBuffer result(2, 2, false);
|
||||||
|
source.Rotate(90, &result);
|
||||||
|
EXPECT_EQ(2, result.buffer()->host[0]);
|
||||||
|
EXPECT_EQ(4, result.buffer()->host[3]);
|
||||||
|
EXPECT_EQ(1, result.buffer()->host[6]);
|
||||||
|
EXPECT_EQ(3, result.buffer()->host[9]);
|
||||||
|
delete[] data;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RgbBufferTest, RgbRotateDifferentFormat) {
|
||||||
|
RgbBuffer source(4, 8, true);
|
||||||
|
RgbBuffer result(8, 4, false);
|
||||||
|
Fill(&source);
|
||||||
|
EXPECT_TRUE(source.Rotate(90, &result));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: RGB-to-RGBA conversion currently doesn't work.
|
||||||
|
TEST(RgbBufferTest, RgbRotateDifferentFormatFail) {
|
||||||
|
RgbBuffer source(4, 8, false);
|
||||||
|
RgbBuffer result(8, 4, true);
|
||||||
|
Fill(&source);
|
||||||
|
EXPECT_FALSE(source.Rotate(90, &result));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RgbBufferTest, RgbaRotate) {
|
||||||
|
RgbBuffer source(4, 8, true);
|
||||||
|
RgbBuffer result(8, 4, true);
|
||||||
|
Fill(&source);
|
||||||
|
EXPECT_TRUE(source.Rotate(90, &result));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RgbBufferTest, RgbaRotateDifferentFormat) {
|
||||||
|
RgbBuffer source(4, 8, true);
|
||||||
|
RgbBuffer result(8, 4, false);
|
||||||
|
Fill(&source);
|
||||||
|
EXPECT_TRUE(source.Rotate(90, &result));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: RGB-to-RGBA conversion currently doesn't work.
|
||||||
|
TEST(RgbBufferTest, RgbaRotateDifferentFormatFail) {
|
||||||
|
RgbBuffer source(4, 8, false);
|
||||||
|
RgbBuffer result(8, 4, true);
|
||||||
|
Fill(&source);
|
||||||
|
EXPECT_FALSE(source.Rotate(90, &result));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RgbBufferTest, PaddedRgbRotateCheckData) {
|
||||||
|
const int target_width = 2;
|
||||||
|
const int target_height = 4;
|
||||||
|
RgbBuffer source = GetPaddedRgbBuffer();
|
||||||
|
RgbBuffer result(target_width, target_height, /*alpha=*/false);
|
||||||
|
|
||||||
|
ASSERT_TRUE(source.Rotate(/*angle=*/90, &result));
|
||||||
|
EXPECT_EQ(result.width(), target_width);
|
||||||
|
EXPECT_EQ(result.height(), target_height);
|
||||||
|
EXPECT_EQ(result.channels(), 3);
|
||||||
|
EXPECT_EQ(result.row_stride(), target_width * /*pixel_stride=*/3);
|
||||||
|
|
||||||
|
uint8_t rgb_data[] = {40, 50, 60, 80, 100, 120, 30, 40, 50, 60, 80, 100,
|
||||||
|
20, 30, 40, 40, 60, 80, 10, 20, 30, 20, 40, 60};
|
||||||
|
RgbBuffer rgb_buffer =
|
||||||
|
RgbBuffer(rgb_data, target_width, target_height, /*alpha=*/false);
|
||||||
|
EXPECT_TRUE(CompareBuffer(rgb_buffer, result));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RgbBufferTest, PaddedRgbaRotateCheckData) {
|
||||||
|
const int target_width = 2;
|
||||||
|
const int target_height = 4;
|
||||||
|
RgbBuffer result(target_width, target_height, /*alpha=*/true);
|
||||||
|
RgbBuffer source = GetPaddedRgbaBuffer();
|
||||||
|
|
||||||
|
ASSERT_TRUE(source.Rotate(/*angle=*/90, &result));
|
||||||
|
EXPECT_EQ(result.width(), target_width);
|
||||||
|
EXPECT_EQ(result.height(), target_height);
|
||||||
|
EXPECT_EQ(result.channels(), 4);
|
||||||
|
EXPECT_EQ(result.row_stride(), target_width * /*pixel_stride=*/4);
|
||||||
|
|
||||||
|
uint8_t rgb_data[] = {40, 50, 60, 255, 80, 100, 120, 255, 30, 40, 50,
|
||||||
|
255, 60, 80, 100, 255, 20, 30, 40, 255, 40, 60,
|
||||||
|
80, 255, 10, 20, 30, 255, 20, 40, 60, 255};
|
||||||
|
RgbBuffer rgb_buffer =
|
||||||
|
RgbBuffer(rgb_data, target_width, target_height, /*alpha=*/true);
|
||||||
|
EXPECT_TRUE(CompareBuffer(rgb_buffer, result));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RgbBufferTest, RgbaFlip) {
|
||||||
|
RgbBuffer source(16, 16, true);
|
||||||
|
RgbBuffer result(16, 16, true);
|
||||||
|
Fill(&source);
|
||||||
|
EXPECT_TRUE(source.FlipHorizontally(&result));
|
||||||
|
EXPECT_TRUE(source.FlipVertically(&result));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: Neither RGBA-to-RGB nor RGB-to-RGBA conversion currently works.
|
||||||
|
TEST(RgbBufferTest, RgbaFlipDifferentFormatFail) {
|
||||||
|
RgbBuffer source(16, 16, false);
|
||||||
|
RgbBuffer result(16, 16, true);
|
||||||
|
Fill(&source);
|
||||||
|
Fill(&result);
|
||||||
|
EXPECT_FALSE(source.FlipHorizontally(&result));
|
||||||
|
EXPECT_FALSE(result.FlipHorizontally(&source));
|
||||||
|
EXPECT_FALSE(source.FlipVertically(&result));
|
||||||
|
EXPECT_FALSE(result.FlipVertically(&source));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RgbBufferTest, PaddedRgbFlipHorizontally) {
|
||||||
|
const int target_width = 4;
|
||||||
|
const int target_height = 2;
|
||||||
|
RgbBuffer result(target_width, target_height, /*alpha=*/false);
|
||||||
|
RgbBuffer source = GetPaddedRgbBuffer();
|
||||||
|
|
||||||
|
ASSERT_TRUE(source.FlipHorizontally(&result));
|
||||||
|
EXPECT_EQ(result.width(), target_width);
|
||||||
|
EXPECT_EQ(result.height(), target_height);
|
||||||
|
EXPECT_EQ(result.channels(), 3);
|
||||||
|
EXPECT_EQ(result.row_stride(), target_width * /*pixel_stride=*/3);
|
||||||
|
|
||||||
|
uint8_t rgb_data[] = {40, 50, 60, 30, 40, 50, 20, 30, 40, 10, 20, 30,
|
||||||
|
80, 100, 120, 60, 80, 100, 40, 60, 80, 20, 40, 60};
|
||||||
|
RgbBuffer rgb_buffer =
|
||||||
|
RgbBuffer(rgb_data, target_width, target_height, /*alpha=*/false);
|
||||||
|
EXPECT_TRUE(CompareBuffer(rgb_buffer, result));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RgbBufferTest, PaddedRgbaFlipHorizontally) {
|
||||||
|
const int target_width = 4;
|
||||||
|
const int target_height = 2;
|
||||||
|
RgbBuffer result(target_width, target_height, /*alpha=*/true);
|
||||||
|
RgbBuffer source = GetPaddedRgbaBuffer();
|
||||||
|
|
||||||
|
ASSERT_TRUE(source.FlipHorizontally(&result));
|
||||||
|
EXPECT_EQ(result.width(), target_width);
|
||||||
|
EXPECT_EQ(result.height(), target_height);
|
||||||
|
EXPECT_EQ(result.channels(), 4);
|
||||||
|
EXPECT_EQ(result.row_stride(), target_width * /*pixel_stride=*/4);
|
||||||
|
|
||||||
|
uint8_t rgb_data[] = {40, 50, 60, 255, 30, 40, 50, 255, 20, 30, 40,
|
||||||
|
255, 10, 20, 30, 255, 80, 100, 120, 255, 60, 80,
|
||||||
|
100, 255, 40, 60, 80, 255, 20, 40, 60, 255};
|
||||||
|
RgbBuffer rgb_buffer =
|
||||||
|
RgbBuffer(rgb_data, target_width, target_height, /*alpha=*/true);
|
||||||
|
EXPECT_TRUE(CompareBuffer(rgb_buffer, result));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RgbBufferTest, RgbConvertNv21) {
|
||||||
|
RgbBuffer source(32, 8, false);
|
||||||
|
YuvBuffer result(32, 8, YuvBuffer::NV21);
|
||||||
|
Fill(&source);
|
||||||
|
EXPECT_TRUE(source.Convert(&result));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RgbBufferTest, RgbaConvertNv21) {
|
||||||
|
RgbBuffer source(32, 8, true);
|
||||||
|
YuvBuffer result(32, 8, YuvBuffer::NV21);
|
||||||
|
Fill(&source);
|
||||||
|
EXPECT_TRUE(source.Convert(&result));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RgbBufferTest, PaddedRgbConvertNv21) {
|
||||||
|
const int target_width = 4;
|
||||||
|
const int target_height = 2;
|
||||||
|
YuvBuffer result(target_width, target_height, YuvBuffer::NV21);
|
||||||
|
RgbBuffer source = GetPaddedRgbBuffer();
|
||||||
|
|
||||||
|
ASSERT_TRUE(source.Convert(&result));
|
||||||
|
EXPECT_EQ(result.width(), target_width);
|
||||||
|
EXPECT_EQ(result.height(), target_height);
|
||||||
|
|
||||||
|
uint8_t yuv_data[] = {18, 28, 38, 48, 36, 56, 76, 96, 122, 135, 122, 135};
|
||||||
|
YuvBuffer yuv_buffer =
|
||||||
|
YuvBuffer(yuv_data, target_width, target_height, YuvBuffer::NV21);
|
||||||
|
EXPECT_TRUE(CompareBuffer(yuv_buffer, result));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RgbBufferTest, PaddedRgbaConvertNv21) {
|
||||||
|
const int target_width = 4;
|
||||||
|
const int target_height = 2;
|
||||||
|
YuvBuffer result(target_width, target_height, YuvBuffer::NV21);
|
||||||
|
RgbBuffer source = GetPaddedRgbaBuffer();
|
||||||
|
|
||||||
|
ASSERT_TRUE(source.Convert(&result));
|
||||||
|
EXPECT_EQ(result.width(), target_width);
|
||||||
|
EXPECT_EQ(result.height(), target_height);
|
||||||
|
|
||||||
|
uint8_t yuv_data[] = {18, 28, 38, 48, 36, 56, 76, 96, 122, 135, 122, 135};
|
||||||
|
YuvBuffer yuv_buffer =
|
||||||
|
YuvBuffer(yuv_data, target_width, target_height, YuvBuffer::NV21);
|
||||||
|
EXPECT_TRUE(CompareBuffer(yuv_buffer, result));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RgbBufferTest, RgbConvertGray) {
|
||||||
|
uint8_t* data = new uint8_t[6];
|
||||||
|
data[0] = 200;
|
||||||
|
data[1] = 100;
|
||||||
|
data[2] = 0;
|
||||||
|
data[3] = 0;
|
||||||
|
data[4] = 200;
|
||||||
|
data[5] = 100;
|
||||||
|
RgbBuffer source(data, 2, 1, false);
|
||||||
|
GrayBuffer result(2, 1);
|
||||||
|
EXPECT_TRUE(source.Convert(&result));
|
||||||
|
EXPECT_EQ(118, result.buffer()->host[0]);
|
||||||
|
EXPECT_EQ(129, result.buffer()->host[1]);
|
||||||
|
delete[] data;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RgbBufferTest, RgbaConvertGray) {
|
||||||
|
uint8_t* data = new uint8_t[8];
|
||||||
|
data[0] = 200;
|
||||||
|
data[1] = 100;
|
||||||
|
data[2] = 0;
|
||||||
|
data[3] = 1;
|
||||||
|
data[4] = 0;
|
||||||
|
data[5] = 200;
|
||||||
|
data[6] = 100;
|
||||||
|
data[7] = 50;
|
||||||
|
RgbBuffer source(data, 2, 1, true);
|
||||||
|
GrayBuffer result(2, 1);
|
||||||
|
EXPECT_TRUE(source.Convert(&result));
|
||||||
|
EXPECT_EQ(118, result.buffer()->host[0]);
|
||||||
|
EXPECT_EQ(129, result.buffer()->host[1]);
|
||||||
|
delete[] data;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RgbBufferTest, PaddedRgbConvertGray) {
|
||||||
|
const int target_width = 4;
|
||||||
|
const int target_height = 2;
|
||||||
|
GrayBuffer result(target_width, target_height);
|
||||||
|
RgbBuffer source = GetPaddedRgbBuffer();
|
||||||
|
|
||||||
|
ASSERT_TRUE(source.Convert(&result));
|
||||||
|
EXPECT_EQ(result.width(), target_width);
|
||||||
|
EXPECT_EQ(result.height(), target_height);
|
||||||
|
|
||||||
|
uint8_t gray_data[] = {18, 28, 38, 48, 36, 56, 76, 96};
|
||||||
|
GrayBuffer gray_buffer = GrayBuffer(gray_data, target_width, target_height);
|
||||||
|
EXPECT_TRUE(CompareBuffer(gray_buffer, result));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RgbBufferTest, PaddedRgbaConvertGray) {
|
||||||
|
const int target_width = 4;
|
||||||
|
const int target_height = 2;
|
||||||
|
GrayBuffer result(target_width, target_height);
|
||||||
|
RgbBuffer source = GetPaddedRgbaBuffer();
|
||||||
|
|
||||||
|
ASSERT_TRUE(source.Convert(&result));
|
||||||
|
EXPECT_EQ(result.width(), target_width);
|
||||||
|
EXPECT_EQ(result.height(), target_height);
|
||||||
|
|
||||||
|
uint8_t gray_data[] = {18, 28, 38, 48, 36, 56, 76, 96};
|
||||||
|
GrayBuffer gray_buffer = GrayBuffer(gray_data, target_width, target_height);
|
||||||
|
EXPECT_TRUE(CompareBuffer(gray_buffer, result));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RgbBufferTest, RgbConvertRgba) {
|
||||||
|
constexpr int kWidth = 2, kHeight = 1;
|
||||||
|
uint8_t rgb_data[] = {200, 100, 50, 100, 50, 20};
|
||||||
|
RgbBuffer source(rgb_data, kWidth, kHeight, false);
|
||||||
|
RgbBuffer result(kWidth, kHeight, true);
|
||||||
|
|
||||||
|
ASSERT_TRUE(source.Convert(&result));
|
||||||
|
|
||||||
|
uint8_t rgba_data[] = {200, 100, 50, 255, 100, 50, 20, 255};
|
||||||
|
RgbBuffer rgba_buffer = RgbBuffer(rgba_data, kWidth, kHeight, true);
|
||||||
|
EXPECT_TRUE(CompareBuffer(rgba_buffer, result));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RgbBufferTest, PaddedRgbConvertRgba) {
|
||||||
|
constexpr int kWidth = 4, kHeight = 2;
|
||||||
|
RgbBuffer source = GetPaddedRgbBuffer();
|
||||||
|
RgbBuffer result(kWidth, kHeight, true);
|
||||||
|
ASSERT_TRUE(source.Convert(&result));
|
||||||
|
|
||||||
|
uint8_t rgba_data[]{10, 20, 30, 255, 20, 30, 40, 255, 30, 40, 50,
|
||||||
|
255, 40, 50, 60, 255, 20, 40, 60, 255, 40, 60,
|
||||||
|
80, 255, 60, 80, 100, 255, 80, 100, 120, 255};
|
||||||
|
RgbBuffer rgba_buffer = RgbBuffer(rgba_data, kWidth, kHeight, true);
|
||||||
|
EXPECT_TRUE(CompareBuffer(rgba_buffer, result));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RgbBufferTest, RgbaConvertRgb) {
|
||||||
|
constexpr int kWidth = 2, kHeight = 1;
|
||||||
|
uint8_t rgba_data[] = {200, 100, 50, 30, 100, 50, 20, 70};
|
||||||
|
RgbBuffer source(rgba_data, kWidth, kHeight, true);
|
||||||
|
RgbBuffer result(kWidth, kHeight, false);
|
||||||
|
|
||||||
|
ASSERT_TRUE(source.Convert(&result));
|
||||||
|
|
||||||
|
uint8_t rgb_data[] = {200, 100, 50, 100, 50, 20};
|
||||||
|
RgbBuffer rgb_buffer = RgbBuffer(rgb_data, kWidth, kHeight, false);
|
||||||
|
EXPECT_TRUE(CompareBuffer(rgb_buffer, result));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RgbBufferTest, PaddedRgbaConvertRgb) {
|
||||||
|
constexpr int kWidth = 4, kHeight = 2;
|
||||||
|
RgbBuffer source = GetPaddedRgbaBuffer();
|
||||||
|
RgbBuffer result(kWidth, kHeight, false);
|
||||||
|
|
||||||
|
ASSERT_TRUE(source.Convert(&result));
|
||||||
|
|
||||||
|
uint8_t rgb_data[] = {10, 20, 30, 20, 30, 40, 30, 40, 50, 40, 50, 60,
|
||||||
|
20, 40, 60, 40, 60, 80, 60, 80, 100, 80, 100, 120};
|
||||||
|
RgbBuffer rgb_buffer = RgbBuffer(rgb_data, kWidth, kHeight, false);
|
||||||
|
EXPECT_TRUE(CompareBuffer(rgb_buffer, result));
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
} // namespace frame_buffer
|
||||||
|
} // namespace mediapipe
|
152
mediapipe/util/frame_buffer/yuv_buffer.cc
Normal file
152
mediapipe/util/frame_buffer/yuv_buffer.cc
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
// Copyright 2023 The MediaPipe Authors.
|
||||||
|
//
|
||||||
|
// 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 "mediapipe/util/frame_buffer/yuv_buffer.h"
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "mediapipe/util/frame_buffer/buffer_common.h"
|
||||||
|
#include "mediapipe/util/frame_buffer/halide/yuv_flip_halide.h"
|
||||||
|
#include "mediapipe/util/frame_buffer/halide/yuv_resize_halide.h"
|
||||||
|
#include "mediapipe/util/frame_buffer/halide/yuv_rgb_halide.h"
|
||||||
|
#include "mediapipe/util/frame_buffer/halide/yuv_rotate_halide.h"
|
||||||
|
#include "mediapipe/util/frame_buffer/rgb_buffer.h"
|
||||||
|
|
||||||
|
namespace mediapipe {
|
||||||
|
namespace frame_buffer {
|
||||||
|
|
||||||
|
YuvBuffer::YuvBuffer(uint8_t* y_plane, uint8_t* u_plane, uint8_t* v_plane,
|
||||||
|
int width, int height, int row_stride_y, int row_stride_uv,
|
||||||
|
int pixel_stride_uv) {
|
||||||
|
// Initialize the buffer shapes: {min, extent, stride} per dimension.
|
||||||
|
// TODO: Ensure that width is less than or equal to row stride.
|
||||||
|
const halide_dimension_t y_dimensions[2] = {
|
||||||
|
{0, width, 1},
|
||||||
|
{0, height, row_stride_y},
|
||||||
|
};
|
||||||
|
y_buffer_ = Halide::Runtime::Buffer<uint8_t>(y_plane, 2, y_dimensions);
|
||||||
|
|
||||||
|
// Note that the Halide implementation expects the planes to be in VU
|
||||||
|
// order, so we point at the V plane first.
|
||||||
|
const halide_dimension_t uv_dimensions[3] = {
|
||||||
|
{0, (width + 1) / 2, pixel_stride_uv},
|
||||||
|
{0, (height + 1) / 2, row_stride_uv},
|
||||||
|
{0, 2, static_cast<int32_t>(u_plane - v_plane)},
|
||||||
|
};
|
||||||
|
uv_buffer_ = Halide::Runtime::Buffer<uint8_t>(v_plane, 3, uv_dimensions);
|
||||||
|
}
|
||||||
|
|
||||||
|
YuvBuffer::YuvBuffer(uint8_t* data, int width, int height, Format format)
|
||||||
|
: owned_buffer_(nullptr) {
|
||||||
|
Initialize(data, width, height, format);
|
||||||
|
}
|
||||||
|
|
||||||
|
YuvBuffer::YuvBuffer(int width, int height, Format format)
|
||||||
|
: owned_buffer_(new uint8_t[ByteSize(width, height)]) {
|
||||||
|
Initialize(owned_buffer_.get(), width, height, format);
|
||||||
|
}
|
||||||
|
|
||||||
|
YuvBuffer::YuvBuffer(const YuvBuffer& other)
|
||||||
|
: y_buffer_(other.y_buffer_), uv_buffer_(other.uv_buffer_) {
|
||||||
|
// Never copy owned_buffer; ownership remains with the source of the copy.
|
||||||
|
}
|
||||||
|
|
||||||
|
YuvBuffer::YuvBuffer(YuvBuffer&& other) { *this = std::move(other); }
|
||||||
|
|
||||||
|
YuvBuffer& YuvBuffer::operator=(const YuvBuffer& other) {
|
||||||
|
if (this != &other) {
|
||||||
|
y_buffer_ = other.y_buffer_;
|
||||||
|
uv_buffer_ = other.uv_buffer_;
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
YuvBuffer& YuvBuffer::operator=(YuvBuffer&& other) {
|
||||||
|
if (this != &other) {
|
||||||
|
owned_buffer_ = std::move(other.owned_buffer_);
|
||||||
|
y_buffer_ = other.y_buffer_;
|
||||||
|
uv_buffer_ = other.uv_buffer_;
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
YuvBuffer::~YuvBuffer() {}
|
||||||
|
|
||||||
|
void YuvBuffer::Initialize(uint8_t* data, int width, int height,
|
||||||
|
Format format) {
|
||||||
|
y_buffer_ = Halide::Runtime::Buffer<uint8_t>(data, width, height);
|
||||||
|
|
||||||
|
uint8_t* uv_data = data + (width * height);
|
||||||
|
switch (format) {
|
||||||
|
case NV21:
|
||||||
|
// Interleaved UV (actually VU order).
|
||||||
|
uv_buffer_ = Halide::Runtime::Buffer<uint8_t>::make_interleaved(
|
||||||
|
uv_data, (width + 1) / 2, (height + 1) / 2, 2);
|
||||||
|
break;
|
||||||
|
case YV12:
|
||||||
|
// Planar UV (actually VU order).
|
||||||
|
uv_buffer_ = Halide::Runtime::Buffer<uint8_t>(uv_data, (width + 1) / 2,
|
||||||
|
(height + 1) / 2, 2);
|
||||||
|
// NOTE: Halide operations have not been tested extensively in this
|
||||||
|
// configuration.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool YuvBuffer::Crop(int x0, int y0, int x1, int y1) {
|
||||||
|
if (x0 & 1 || y0 & 1) {
|
||||||
|
// YUV images must be left-and top-aligned to even X/Y coordinates.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Twiddle the buffer start and extents for each plane to crop images.
|
||||||
|
return (common::crop_buffer(x0, y0, x1, y1, y_buffer()) &&
|
||||||
|
common::crop_buffer(x0 / 2, y0 / 2, x1 / 2, y1 / 2, uv_buffer()));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool YuvBuffer::Resize(YuvBuffer* output) {
|
||||||
|
const int result = yuv_resize_halide(
|
||||||
|
y_buffer(), uv_buffer(), static_cast<float>(width()) / output->width(),
|
||||||
|
static_cast<float>(height()) / output->height(), output->y_buffer(),
|
||||||
|
output->uv_buffer());
|
||||||
|
return result == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool YuvBuffer::Rotate(int angle, YuvBuffer* output) {
|
||||||
|
const int result = yuv_rotate_halide(y_buffer(), uv_buffer(), angle,
|
||||||
|
output->y_buffer(), output->uv_buffer());
|
||||||
|
return result == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool YuvBuffer::FlipHorizontally(YuvBuffer* output) {
|
||||||
|
const int result = yuv_flip_halide(y_buffer(), uv_buffer(),
|
||||||
|
false, // horizontal
|
||||||
|
output->y_buffer(), output->uv_buffer());
|
||||||
|
return result == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool YuvBuffer::FlipVertically(YuvBuffer* output) {
|
||||||
|
const int result = yuv_flip_halide(y_buffer(), uv_buffer(),
|
||||||
|
true, // vertical
|
||||||
|
output->y_buffer(), output->uv_buffer());
|
||||||
|
return result == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool YuvBuffer::Convert(bool halve, RgbBuffer* output) {
|
||||||
|
const int result =
|
||||||
|
yuv_rgb_halide(y_buffer(), uv_buffer(), halve, output->buffer());
|
||||||
|
return result == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace frame_buffer
|
||||||
|
} // namespace mediapipe
|
160
mediapipe/util/frame_buffer/yuv_buffer.h
Normal file
160
mediapipe/util/frame_buffer/yuv_buffer.h
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
// Copyright 2023 The MediaPipe Authors.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#ifndef MEDIAPIPE_UTIL_FRAME_BUFFER_YUV_BUFFER_H_
|
||||||
|
#define MEDIAPIPE_UTIL_FRAME_BUFFER_YUV_BUFFER_H_
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "HalideBuffer.h"
|
||||||
|
#include "HalideRuntime.h"
|
||||||
|
|
||||||
|
namespace mediapipe {
|
||||||
|
namespace frame_buffer {
|
||||||
|
class RgbBuffer;
|
||||||
|
|
||||||
|
// YuvBuffer represents a view over a YUV 4:2:0 image.
|
||||||
|
//
|
||||||
|
// YuvBuffers may be copied and moved efficiently; their backing buffers are
|
||||||
|
// shared and never deep copied.
|
||||||
|
//
|
||||||
|
// YuvBuffer requires a minimum image width depending on the natural vector
|
||||||
|
// size of the platform, e.g., 16px. This is not validated by YuvBuffer.
|
||||||
|
class YuvBuffer {
|
||||||
|
public:
|
||||||
|
// YUV formats. Rather than supporting every possible format, we prioritize
|
||||||
|
// formats with broad hardware/platform support.
|
||||||
|
//
|
||||||
|
// Enum values are FourCC codes; see http://fourcc.org/yuv.php for more.
|
||||||
|
enum Format {
|
||||||
|
NV21 = 0x3132564E, // YUV420SP (VU interleaved)
|
||||||
|
YV12 = 0x32315659, // YUV420P (VU planar)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Returns the size (in bytes) of a YUV image of the given dimensions.
|
||||||
|
static int ByteSize(int width, int height) {
|
||||||
|
// 1 byte per pixel in the Y plane, 2 bytes per 2x2 block in the UV plane.
|
||||||
|
// Dimensions with odd sizes are rounded up.
|
||||||
|
const int y_size = width * height;
|
||||||
|
const int uv_size = ((width + 1) / 2) * ((height + 1) / 2) * 2;
|
||||||
|
return y_size + uv_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Builds a generic YUV420 YuvBuffer with the given backing buffers,
|
||||||
|
// dimensions and strides. Supports both interleaved or planar UV with
|
||||||
|
// custom strides.
|
||||||
|
//
|
||||||
|
// Does not take ownership of any backing buffers, which must be large
|
||||||
|
// enough to fit their contents.
|
||||||
|
YuvBuffer(uint8_t* y_plane, uint8_t* u_plane, uint8_t* v_plane, int width,
|
||||||
|
int height, int row_stride_y, int row_stride_uv,
|
||||||
|
int pixel_stride_uv);
|
||||||
|
|
||||||
|
// Builds a YuvBuffer using the given backing buffer, dimensions, and format.
|
||||||
|
// Expects an NV21- or YV12-format image only.
|
||||||
|
//
|
||||||
|
// Does not take ownership of the backing buffer (provided in 'data'), which
|
||||||
|
// must be sized to hold at least the amount indicated by ByteSize().
|
||||||
|
YuvBuffer(uint8_t* data, int width, int height, Format format);
|
||||||
|
|
||||||
|
// Builds a YuvBuffer using the given dimensions and format. Expects
|
||||||
|
// an NV21- or YV12-format image only.
|
||||||
|
//
|
||||||
|
// The underlying backing buffer is allocated and owned by this YuvBuffer.
|
||||||
|
YuvBuffer(int width, int height, Format format);
|
||||||
|
|
||||||
|
// YuvBuffer is copyable. The source retains ownership of its backing buffers.
|
||||||
|
YuvBuffer(const YuvBuffer& other);
|
||||||
|
// YuvBuffer is moveable. The source loses ownership of any backing buffers.
|
||||||
|
YuvBuffer(YuvBuffer&& other);
|
||||||
|
// YuvBuffer is assignable.
|
||||||
|
YuvBuffer& operator=(const YuvBuffer& other);
|
||||||
|
YuvBuffer& operator=(YuvBuffer&& other);
|
||||||
|
|
||||||
|
~YuvBuffer();
|
||||||
|
|
||||||
|
// Performs an in-place crop. Modifies this buffer so that the new extent
|
||||||
|
// matches that of the given crop rectangle -- (x0, y0) becomes (0, 0) and
|
||||||
|
// the new width and height are x1 - x0 + 1 and y1 - y0 + 1, respectively.
|
||||||
|
//
|
||||||
|
// Note that the top-left corner (x0, y0) coordinates must be even to
|
||||||
|
// maintain alignment between the Y and UV grids.
|
||||||
|
bool Crop(int x0, int y0, int x1, int y1);
|
||||||
|
|
||||||
|
// Resize this image to match the dimensions of the given output YuvBuffer
|
||||||
|
// and places the result into its backing buffer.
|
||||||
|
//
|
||||||
|
// Performs a resize with bilinear interpolation (over four source pixels).
|
||||||
|
bool Resize(YuvBuffer* output);
|
||||||
|
|
||||||
|
// Rotate this image into the given buffer by the given angle (90, 180, 270).
|
||||||
|
//
|
||||||
|
// Rotation is specified in degrees counter-clockwise such that when rotating
|
||||||
|
// by 90 degrees, the top-right corner of the source becomes the top-left of
|
||||||
|
// the output. The output buffer must have its height and width swapped when
|
||||||
|
// rotating by 90 or 270.
|
||||||
|
//
|
||||||
|
// Any angle values other than (90, 180, 270) are invalid.
|
||||||
|
bool Rotate(int angle, YuvBuffer* output);
|
||||||
|
|
||||||
|
// Flip this image horizontally/vertically into the given buffer. Both buffer
|
||||||
|
// dimensions must match.
|
||||||
|
bool FlipHorizontally(YuvBuffer* output);
|
||||||
|
bool FlipVertically(YuvBuffer* output);
|
||||||
|
|
||||||
|
// Performs a YUV-to-RGB color format conversion and places the result
|
||||||
|
// in the given output RgbBuffer. Both buffer dimensions must match.
|
||||||
|
//
|
||||||
|
// When halve is true, the converted output is downsampled by a factor of
|
||||||
|
// two by discarding three of four luminance values in every 2x2 block.
|
||||||
|
bool Convert(bool halve, RgbBuffer* output);
|
||||||
|
|
||||||
|
// Release ownership of the owned backing buffer.
|
||||||
|
uint8_t* Release() { return owned_buffer_.release(); }
|
||||||
|
|
||||||
|
// Returns the halide_buffer_t* for the Y plane.
|
||||||
|
const halide_buffer_t* y_buffer() const { return y_buffer_.raw_buffer(); }
|
||||||
|
// Returns the halide_buffer_t* for the UV plane(s).
|
||||||
|
const halide_buffer_t* uv_buffer() const { return uv_buffer_.raw_buffer(); }
|
||||||
|
// Returns the halide_buffer_t* for the Y plane.
|
||||||
|
halide_buffer_t* y_buffer() { return y_buffer_.raw_buffer(); }
|
||||||
|
// Returns the halide_buffer_t* for the UV plane(s).
|
||||||
|
halide_buffer_t* uv_buffer() { return uv_buffer_.raw_buffer(); }
|
||||||
|
|
||||||
|
// Returns the image width.
|
||||||
|
const int width() const { return y_buffer_.dim(0).extent(); }
|
||||||
|
// Returns the image height.
|
||||||
|
const int height() const { return y_buffer_.dim(1).extent(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void Initialize(uint8_t* data, int width, int height, Format format);
|
||||||
|
|
||||||
|
// Non-NULL iff this YuvBuffer owns its buffer.
|
||||||
|
std::unique_ptr<uint8_t[]> owned_buffer_;
|
||||||
|
|
||||||
|
// Y (luminance) backing buffer: layout is always width x height.
|
||||||
|
Halide::Runtime::Buffer<uint8_t> y_buffer_;
|
||||||
|
|
||||||
|
// UV (chrominance) backing buffer; width/2 x height/2 x 2 (channel).
|
||||||
|
// May be interleaved or planar.
|
||||||
|
//
|
||||||
|
// Note that the planes are in the reverse of the usual order: channel 0 is V
|
||||||
|
// and channel 1 is U.
|
||||||
|
Halide::Runtime::Buffer<uint8_t> uv_buffer_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace frame_buffer
|
||||||
|
} // namespace mediapipe
|
||||||
|
|
||||||
|
#endif // MEDIAPIPE_UTIL_FRAME_BUFFER_YUV_BUFFER_H_
|
251
mediapipe/util/frame_buffer/yuv_buffer_test.cc
Normal file
251
mediapipe/util/frame_buffer/yuv_buffer_test.cc
Normal file
|
@ -0,0 +1,251 @@
|
||||||
|
// Copyright 2023 The MediaPipe Authors.
|
||||||
|
//
|
||||||
|
// 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 "mediapipe/util/frame_buffer/yuv_buffer.h"
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "absl/log/log.h"
|
||||||
|
#include "mediapipe/framework/port/gmock.h"
|
||||||
|
#include "mediapipe/framework/port/gtest.h"
|
||||||
|
#include "mediapipe/util/frame_buffer/rgb_buffer.h"
|
||||||
|
|
||||||
|
// The default implementation of halide_error calls abort(), which we don't
|
||||||
|
// want. Instead, log the error and let the filter invocation fail.
|
||||||
|
extern "C" void halide_error(void*, const char* message) {
|
||||||
|
LOG(ERROR) << "Halide Error: " << message;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace mediapipe {
|
||||||
|
namespace frame_buffer {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Fill a halide_buffer_t channel with the given value.
|
||||||
|
void Fill(halide_buffer_t* buffer, int channel, int value) {
|
||||||
|
for (int y = 0; y < buffer->dim[1].extent; ++y) {
|
||||||
|
for (int x = 0; x < buffer->dim[0].extent; ++x) {
|
||||||
|
buffer->host[buffer->dim[1].stride * y + buffer->dim[0].stride * x +
|
||||||
|
buffer->dim[2].stride * channel] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill a YuvBuffer with the given YUV color.
|
||||||
|
void Fill(YuvBuffer* buffer, uint8_t y, uint8_t u, uint8_t v) {
|
||||||
|
Fill(buffer->y_buffer(), 0, y);
|
||||||
|
Fill(buffer->uv_buffer(), 1, u);
|
||||||
|
Fill(buffer->uv_buffer(), 0, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(YuvBufferTest, Properties) {
|
||||||
|
YuvBuffer yuv(2, 8, YuvBuffer::NV21);
|
||||||
|
EXPECT_EQ(2, yuv.width());
|
||||||
|
EXPECT_EQ(8, yuv.height());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(YuvBufferTest, Release) {
|
||||||
|
YuvBuffer source(8, 8, YuvBuffer::NV21);
|
||||||
|
delete[] source.Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(YuvBufferTest, Assign) {
|
||||||
|
YuvBuffer source(8, 8, YuvBuffer::NV21);
|
||||||
|
YuvBuffer sink(nullptr, 0, 0, YuvBuffer::NV21);
|
||||||
|
sink = source;
|
||||||
|
EXPECT_EQ(8, sink.width());
|
||||||
|
EXPECT_EQ(8, sink.height());
|
||||||
|
|
||||||
|
sink = YuvBuffer(16, 16, YuvBuffer::NV21);
|
||||||
|
EXPECT_EQ(16, sink.width());
|
||||||
|
EXPECT_EQ(16, sink.height());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(YuvBufferTest, MoveAssign) {
|
||||||
|
YuvBuffer source(8, 8, YuvBuffer::NV21);
|
||||||
|
YuvBuffer sink(nullptr, 0, 0, YuvBuffer::NV21);
|
||||||
|
sink = std::move(source);
|
||||||
|
EXPECT_EQ(nullptr, source.Release());
|
||||||
|
EXPECT_EQ(8, sink.width());
|
||||||
|
EXPECT_EQ(8, sink.height());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(YuvBufferTest, MoveConstructor) {
|
||||||
|
YuvBuffer source(8, 8, YuvBuffer::NV21);
|
||||||
|
YuvBuffer sink(std::move(source));
|
||||||
|
EXPECT_EQ(nullptr, source.Release());
|
||||||
|
EXPECT_EQ(8, sink.width());
|
||||||
|
EXPECT_EQ(8, sink.height());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(YuvBufferTest, GenericSemiplanarLayout) {
|
||||||
|
uint8_t y_plane[16], uv_plane[8];
|
||||||
|
YuvBuffer buffer(y_plane, uv_plane, uv_plane + 1, 4, 4, 4, 4, 2);
|
||||||
|
Fill(&buffer, 16, 32, 64);
|
||||||
|
|
||||||
|
for (int i = 0; i < 16; ++i) {
|
||||||
|
EXPECT_EQ(y_plane[i], 16) << i;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < 4; ++i) {
|
||||||
|
EXPECT_EQ(uv_plane[2 * i], 32);
|
||||||
|
EXPECT_EQ(uv_plane[2 * i + 1], 64);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(YuvBufferTest, GenericPlanarLayout) {
|
||||||
|
uint8_t y_plane[16], u_plane[4], v_plane[4];
|
||||||
|
YuvBuffer buffer(y_plane, u_plane, v_plane, 4, 4, 4, 2, 1);
|
||||||
|
Fill(&buffer, 16, 32, 64);
|
||||||
|
|
||||||
|
for (int i = 0; i < 16; ++i) {
|
||||||
|
EXPECT_EQ(y_plane[i], 16) << i;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < 4; ++i) {
|
||||||
|
EXPECT_EQ(u_plane[i], 32);
|
||||||
|
EXPECT_EQ(v_plane[i], 64);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(YuvBufferTest, Nv21Crop) {
|
||||||
|
YuvBuffer source(8, 8, YuvBuffer::NV21);
|
||||||
|
EXPECT_TRUE(source.Crop(2, 2, 6, 6));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(YuvBufferTest, Nv21Resize) {
|
||||||
|
YuvBuffer source(8, 8, YuvBuffer::NV21);
|
||||||
|
YuvBuffer result(4, 4, YuvBuffer::NV21);
|
||||||
|
Fill(&source, 16, 32, 64);
|
||||||
|
EXPECT_TRUE(source.Resize(&result));
|
||||||
|
|
||||||
|
// Test odd result sizes too.
|
||||||
|
source = YuvBuffer(500, 362, YuvBuffer::NV21);
|
||||||
|
result = YuvBuffer(320, 231, YuvBuffer::NV21);
|
||||||
|
Fill(&source, 16, 32, 64);
|
||||||
|
EXPECT_TRUE(source.Resize(&result));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(YuvBufferTest, Nv21ResizeDifferentFormat) {
|
||||||
|
YuvBuffer source(8, 8, YuvBuffer::NV21);
|
||||||
|
YuvBuffer result(4, 4, YuvBuffer::YV12);
|
||||||
|
Fill(&source, 16, 32, 64);
|
||||||
|
EXPECT_TRUE(source.Resize(&result));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(YuvBufferTest, Nv21Rotate) {
|
||||||
|
YuvBuffer source(4, 8, YuvBuffer::NV21);
|
||||||
|
YuvBuffer result(8, 4, YuvBuffer::NV21);
|
||||||
|
Fill(&source, 16, 32, 64);
|
||||||
|
EXPECT_TRUE(source.Rotate(90, &result));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(YuvBufferTest, Nv21RotateDifferentFormat) {
|
||||||
|
YuvBuffer source(8, 8, YuvBuffer::NV21);
|
||||||
|
YuvBuffer result(8, 8, YuvBuffer::YV12);
|
||||||
|
Fill(&source, 16, 32, 64);
|
||||||
|
EXPECT_TRUE(source.Rotate(90, &result));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(YuvBufferTest, Nv21RotateFailBounds) {
|
||||||
|
// Expect failure if the destination doesn't have the correct bounds.
|
||||||
|
YuvBuffer source(4, 8, YuvBuffer::NV21);
|
||||||
|
YuvBuffer result(4, 8, YuvBuffer::NV21);
|
||||||
|
Fill(&source, 16, 32, 64);
|
||||||
|
EXPECT_FALSE(source.Rotate(90, &result));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(YuvBufferTest, Nv21Flip) {
|
||||||
|
YuvBuffer source(16, 16, YuvBuffer::NV21);
|
||||||
|
YuvBuffer result(16, 16, YuvBuffer::NV21);
|
||||||
|
Fill(&source, 16, 32, 64);
|
||||||
|
EXPECT_TRUE(source.FlipHorizontally(&result));
|
||||||
|
EXPECT_TRUE(source.FlipVertically(&result));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(YuvBufferTest, Nv21FlipDifferentFormat) {
|
||||||
|
YuvBuffer source(16, 16, YuvBuffer::NV21);
|
||||||
|
YuvBuffer result(16, 16, YuvBuffer::YV12);
|
||||||
|
Fill(&source, 16, 32, 64);
|
||||||
|
EXPECT_TRUE(source.FlipHorizontally(&result));
|
||||||
|
EXPECT_TRUE(source.FlipVertically(&result));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(YuvBufferTest, Nv21ConvertRgb) {
|
||||||
|
// Note that RGB conversion expects at least images of width >= 32 because
|
||||||
|
// the implementation is vectorized.
|
||||||
|
YuvBuffer source(32, 8, YuvBuffer::NV21);
|
||||||
|
Fill(&source, 52, 170, 90);
|
||||||
|
|
||||||
|
RgbBuffer result_rgb(32, 8, false);
|
||||||
|
EXPECT_TRUE(source.Convert(false, &result_rgb));
|
||||||
|
|
||||||
|
RgbBuffer result_rgba(32, 8, true);
|
||||||
|
EXPECT_TRUE(source.Convert(false, &result_rgba));
|
||||||
|
|
||||||
|
uint8_t* pixels = result_rgba.buffer()->host;
|
||||||
|
ASSERT_TRUE(pixels);
|
||||||
|
EXPECT_EQ(pixels[0], 0);
|
||||||
|
EXPECT_EQ(pixels[1], 65);
|
||||||
|
EXPECT_EQ(pixels[2], 126);
|
||||||
|
EXPECT_EQ(pixels[3], 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(YuvBufferTest, Nv21ConvertRgbCropped) {
|
||||||
|
// Note that RGB conversion expects at least images of width >= 32 because
|
||||||
|
// the implementation is vectorized.
|
||||||
|
YuvBuffer source(1024, 768, YuvBuffer::NV21);
|
||||||
|
Fill(&source, 52, 170, 90);
|
||||||
|
|
||||||
|
// YUV images must be left-and top-aligned to even X/Y coordinates,
|
||||||
|
// regardless of whether the target image has even or odd width/height.
|
||||||
|
EXPECT_FALSE(source.Crop(1, 1, 512, 384));
|
||||||
|
EXPECT_FALSE(source.Crop(1, 1, 511, 383));
|
||||||
|
|
||||||
|
YuvBuffer source1(source);
|
||||||
|
EXPECT_TRUE(source1.Crop(64, 64, 512, 384));
|
||||||
|
RgbBuffer result_rgb(source1.width(), source1.height(), false);
|
||||||
|
EXPECT_TRUE(source1.Convert(false, &result_rgb));
|
||||||
|
|
||||||
|
YuvBuffer source2(source);
|
||||||
|
EXPECT_TRUE(source2.Crop(64, 64, 511, 383));
|
||||||
|
RgbBuffer result_rgba(source2.width(), source2.height(), true);
|
||||||
|
EXPECT_TRUE(source2.Convert(false, &result_rgba));
|
||||||
|
|
||||||
|
uint8_t* pixels = result_rgba.buffer()->host;
|
||||||
|
ASSERT_TRUE(pixels);
|
||||||
|
EXPECT_EQ(pixels[0], 0);
|
||||||
|
EXPECT_EQ(pixels[1], 65);
|
||||||
|
EXPECT_EQ(pixels[2], 126);
|
||||||
|
EXPECT_EQ(pixels[3], 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(YuvBufferTest, Nv21ConvertRgbHalve) {
|
||||||
|
YuvBuffer source(64, 8, YuvBuffer::NV21);
|
||||||
|
Fill(&source, 52, 170, 90);
|
||||||
|
|
||||||
|
RgbBuffer result_rgb(32, 4, false);
|
||||||
|
EXPECT_TRUE(source.Convert(true, &result_rgb));
|
||||||
|
|
||||||
|
RgbBuffer result_rgba(32, 4, true);
|
||||||
|
EXPECT_TRUE(source.Convert(true, &result_rgba));
|
||||||
|
|
||||||
|
uint8_t* pixels = result_rgba.buffer()->host;
|
||||||
|
ASSERT_TRUE(pixels);
|
||||||
|
EXPECT_EQ(pixels[0], 0);
|
||||||
|
EXPECT_EQ(pixels[1], 65);
|
||||||
|
EXPECT_EQ(pixels[2], 126);
|
||||||
|
EXPECT_EQ(pixels[3], 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace frame_buffer
|
||||||
|
} // namespace mediapipe
|
70
third_party/halide.BUILD
vendored
Normal file
70
third_party/halide.BUILD
vendored
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
# Copyright 2023 The MediaPipe Authors.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
load("@halide//:halide.bzl", "halide_language_copts")
|
||||||
|
|
||||||
|
licenses(["notice"])
|
||||||
|
|
||||||
|
package(
|
||||||
|
default_visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_library(
|
||||||
|
name = "language",
|
||||||
|
hdrs = ["include/Halide.h"],
|
||||||
|
copts = halide_language_copts(),
|
||||||
|
includes = ["include"],
|
||||||
|
deps = [
|
||||||
|
":runtime",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_library(
|
||||||
|
name = "runtime",
|
||||||
|
hdrs = glob([
|
||||||
|
"include/HalideRuntime*.h",
|
||||||
|
"include/HalideBuffer*.h",
|
||||||
|
]),
|
||||||
|
includes = ["include"],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_library(
|
||||||
|
name = "lib_halide_static",
|
||||||
|
srcs = select({
|
||||||
|
"@mediapipe//mediapipe:windows": [
|
||||||
|
"lib/Release/Halide.lib",
|
||||||
|
"bin/Release/Halide.dll",
|
||||||
|
],
|
||||||
|
"//conditions:default": [
|
||||||
|
"lib/libHalide.a",
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_library(
|
||||||
|
name = "gengen",
|
||||||
|
srcs = [
|
||||||
|
"share/Halide/tools/GenGen.cpp",
|
||||||
|
],
|
||||||
|
includes = [
|
||||||
|
"include",
|
||||||
|
"share/Halide/tools",
|
||||||
|
],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
":language",
|
||||||
|
":lib_halide_static",
|
||||||
|
],
|
||||||
|
)
|
1
third_party/halide/BUILD
vendored
Normal file
1
third_party/halide/BUILD
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
# This empty BUILD file is required to make Bazel treat this directory as a package.
|
40
third_party/halide/BUILD.bazel
vendored
Normal file
40
third_party/halide/BUILD.bazel
vendored
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
# Copyright 2023 The MediaPipe Authors.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
load("@halide//:halide.bzl", "halide_library_runtimes")
|
||||||
|
|
||||||
|
licenses(["notice"])
|
||||||
|
|
||||||
|
package(
|
||||||
|
default_visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
|
|
||||||
|
halide_library_runtimes()
|
||||||
|
|
||||||
|
# Aliases to platform-specific targets.
|
||||||
|
[
|
||||||
|
alias(
|
||||||
|
name = target_name,
|
||||||
|
actual = select({
|
||||||
|
"//conditions:default": "@linux_halide//:" + target_name,
|
||||||
|
"@mediapipe//mediapipe:macos": "@macos_halide//:" + target_name,
|
||||||
|
"@mediapipe//mediapipe:windows": "@windows_halide//:" + target_name,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
for target_name in [
|
||||||
|
"language",
|
||||||
|
"runtime",
|
||||||
|
"gengen",
|
||||||
|
]
|
||||||
|
]
|
875
third_party/halide/halide.bzl
vendored
Normal file
875
third_party/halide/halide.bzl
vendored
Normal file
|
@ -0,0 +1,875 @@
|
||||||
|
# Copyright 2023 The MediaPipe 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.
|
||||||
|
|
||||||
|
"""Bazel build rules for Halide."""
|
||||||
|
|
||||||
|
load("@bazel_skylib//lib:collections.bzl", "collections")
|
||||||
|
load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "use_cpp_toolchain")
|
||||||
|
|
||||||
|
def halide_language_copts():
|
||||||
|
_common_opts = [
|
||||||
|
"-fPIC",
|
||||||
|
"-frtti",
|
||||||
|
"-Wno-conversion",
|
||||||
|
"-Wno-sign-compare",
|
||||||
|
]
|
||||||
|
_posix_opts = [
|
||||||
|
"$(STACK_FRAME_UNLIMITED)",
|
||||||
|
"-fno-exceptions",
|
||||||
|
"-funwind-tables",
|
||||||
|
"-fvisibility-inlines-hidden",
|
||||||
|
]
|
||||||
|
_msvc_opts = [
|
||||||
|
"-D_CRT_SECURE_NO_WARNINGS",
|
||||||
|
"/MD",
|
||||||
|
]
|
||||||
|
return _common_opts + select({
|
||||||
|
"//conditions:default": _posix_opts,
|
||||||
|
"@mediapipe//mediapipe:windows": _msvc_opts,
|
||||||
|
})
|
||||||
|
|
||||||
|
def halide_language_linkopts():
|
||||||
|
_linux_opts = [
|
||||||
|
"-ldl",
|
||||||
|
"-lpthread",
|
||||||
|
"-lz",
|
||||||
|
"-rdynamic",
|
||||||
|
]
|
||||||
|
_osx_opts = [
|
||||||
|
"-lz",
|
||||||
|
"-Wl,-stack_size",
|
||||||
|
"-Wl,1000000",
|
||||||
|
]
|
||||||
|
_msvc_opts = []
|
||||||
|
return select({
|
||||||
|
"//conditions:default": _linux_opts,
|
||||||
|
"@mediapipe//mediapipe:macos": _osx_opts,
|
||||||
|
"@mediapipe//mediapipe:windows": _msvc_opts,
|
||||||
|
})
|
||||||
|
|
||||||
|
def halide_runtime_linkopts():
|
||||||
|
""" Return the linkopts needed when linking against halide_library_runtime.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List to be used for linkopts.
|
||||||
|
"""
|
||||||
|
_posix_opts = [
|
||||||
|
"-ldl",
|
||||||
|
"-lpthread",
|
||||||
|
]
|
||||||
|
_android_opts = [
|
||||||
|
"-llog",
|
||||||
|
]
|
||||||
|
_msvc_opts = []
|
||||||
|
|
||||||
|
return select({
|
||||||
|
"//conditions:default": _posix_opts,
|
||||||
|
"@mediapipe//mediapipe:android": _android_opts,
|
||||||
|
"@mediapipe//mediapipe:windows": _msvc_opts,
|
||||||
|
})
|
||||||
|
|
||||||
|
# Map of halide-target-base -> config_settings
|
||||||
|
_HALIDE_TARGET_CONFIG_SETTINGS_MAP = {
|
||||||
|
# Android
|
||||||
|
"arm-32-android": ["@mediapipe//mediapipe:android_arm"],
|
||||||
|
"arm-64-android": ["@mediapipe//mediapipe:android_arm64"],
|
||||||
|
"x86-32-android": ["@mediapipe//mediapipe:android_x86"],
|
||||||
|
"x86-64-android": ["@mediapipe//mediapipe:android_x86_64"],
|
||||||
|
# iOS
|
||||||
|
"arm-32-ios": ["@mediapipe//mediapipe:ios_armv7"],
|
||||||
|
"arm-64-ios": ["@mediapipe//mediapipe:ios_arm64", "@mediapipe//mediapipe:ios_arm64e"],
|
||||||
|
# OSX (or iOS simulator)
|
||||||
|
"x86-64-osx": [
|
||||||
|
"@mediapipe//mediapipe:macos_x86_64",
|
||||||
|
"@mediapipe//mediapipe:ios_x86_64",
|
||||||
|
# TODO: these should map to "x86-32-osx", but that causes Kokoro to fail.
|
||||||
|
"@mediapipe//mediapipe:macos_i386",
|
||||||
|
"@mediapipe//mediapipe:ios_i386",
|
||||||
|
],
|
||||||
|
"arm-64-osx": ["@mediapipe//mediapipe:macos_arm64"],
|
||||||
|
# Windows
|
||||||
|
"x86-64-windows": ["@mediapipe//mediapipe:windows"],
|
||||||
|
# Linux
|
||||||
|
"x86-64-linux": ["//conditions:default"],
|
||||||
|
}
|
||||||
|
|
||||||
|
_HALIDE_TARGET_MAP_DEFAULT = {
|
||||||
|
"x86-64-linux": [
|
||||||
|
"x86-64-linux-sse41-avx-avx2-fma",
|
||||||
|
"x86-64-linux-sse41",
|
||||||
|
"x86-64-linux",
|
||||||
|
],
|
||||||
|
"x86-64-osx": [
|
||||||
|
"x86-64-osx-sse41-avx-avx2-fma",
|
||||||
|
"x86-64-osx-sse41",
|
||||||
|
"x86-64-osx",
|
||||||
|
],
|
||||||
|
"x86-64-windows": [
|
||||||
|
"x86-64-windows-sse41-avx-avx2-fma",
|
||||||
|
"x86-64-windows-sse41",
|
||||||
|
"x86-64-windows",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
def halide_library_default_target_map():
|
||||||
|
return _HALIDE_TARGET_MAP_DEFAULT
|
||||||
|
|
||||||
|
# Alphabetizes the features part of the target to make sure they always match no
|
||||||
|
# matter the concatenation order of the target string pieces.
|
||||||
|
def _canonicalize_target(halide_target):
|
||||||
|
if halide_target == "host":
|
||||||
|
return halide_target
|
||||||
|
if "," in halide_target:
|
||||||
|
fail("Multitarget may not be specified here")
|
||||||
|
tokens = halide_target.split("-")
|
||||||
|
if len(tokens) < 3:
|
||||||
|
fail("Illegal target: %s" % halide_target)
|
||||||
|
|
||||||
|
# rejoin the tokens with the features sorted
|
||||||
|
return "-".join(tokens[0:3] + sorted(tokens[3:]))
|
||||||
|
|
||||||
|
# Converts comma and dash separators to underscore and alphabetizes
|
||||||
|
# the features part of the target to make sure they always match no
|
||||||
|
# matter the concatenation order of the target string pieces.
|
||||||
|
def _halide_target_to_bazel_rule_name(multitarget):
|
||||||
|
subtargets = multitarget.split(",")
|
||||||
|
subtargets = [_canonicalize_target(st).replace("-", "_") for st in subtargets]
|
||||||
|
return "_".join(subtargets)
|
||||||
|
|
||||||
|
# The second argument is True if there is a separate file generated
|
||||||
|
# for each subtarget of a multitarget output, False if not. The third
|
||||||
|
# argument is True if the output is a directory (vs. a single file).
|
||||||
|
# The fourth argument is a list of output group(s) that the files should
|
||||||
|
# be added to.
|
||||||
|
|
||||||
|
_is_multi = True
|
||||||
|
_is_single = False
|
||||||
|
_is_file = False
|
||||||
|
|
||||||
|
_output_extensions = {
|
||||||
|
"assembly": ("s", _is_multi, _is_file, []),
|
||||||
|
"bitcode": ("bc", _is_multi, _is_file, ["generated_bitcode"]),
|
||||||
|
"c_header": ("h", _is_single, _is_file, ["generated_headers"]),
|
||||||
|
"c_source": ("halide_generated.cpp", _is_multi, _is_file, []),
|
||||||
|
"compiler_log": ("halide_compiler_log", _is_single, _is_file, ["generated_object", "generated_compiler_log"]),
|
||||||
|
"cpp_stub": ("stub.h", _is_single, _is_file, []),
|
||||||
|
"featurization": ("featurization", _is_multi, _is_file, []),
|
||||||
|
"llvm_assembly": ("ll", _is_multi, _is_file, []),
|
||||||
|
"object": ("o", _is_single, _is_file, ["generated_object"]),
|
||||||
|
"python_extension": ("py.cpp", _is_single, _is_file, []),
|
||||||
|
"registration": ("registration.cpp", _is_single, _is_file, ["generated_registration"]),
|
||||||
|
"schedule": ("schedule.h", _is_single, _is_file, []),
|
||||||
|
"static_library": ("a", _is_single, _is_file, ["generated_object"]),
|
||||||
|
"stmt": ("stmt", _is_multi, _is_file, []),
|
||||||
|
"stmt_html": ("stmt.html", _is_multi, _is_file, []),
|
||||||
|
}
|
||||||
|
|
||||||
|
def _add_output_file(f, fmt, output_files, output_dict, verbose_extra_outputs, verbose_output_paths):
|
||||||
|
if fmt in verbose_extra_outputs:
|
||||||
|
verbose_output_paths.append(f.path)
|
||||||
|
output_files.append(f)
|
||||||
|
if fmt in _output_extensions:
|
||||||
|
for group in _output_extensions[fmt][3]:
|
||||||
|
output_dict.setdefault(group, []).append(f)
|
||||||
|
|
||||||
|
HalideFunctionNameInfo = provider(fields = ["function_name"])
|
||||||
|
HalideGeneratorBinaryInfo = provider(fields = ["generator_binary"])
|
||||||
|
HalideGeneratorNameInfo = provider(fields = ["generator_name_"])
|
||||||
|
HalideGeneratorParamsInfo = provider(fields = ["generator_params"])
|
||||||
|
HalideLibraryNameInfo = provider(fields = ["library_name"])
|
||||||
|
HalideTargetFeaturesInfo = provider(fields = ["target_features"])
|
||||||
|
|
||||||
|
def _gengen_closure_impl(ctx):
|
||||||
|
return [
|
||||||
|
HalideGeneratorBinaryInfo(generator_binary = ctx.attr.generator_binary),
|
||||||
|
HalideGeneratorNameInfo(generator_name_ = ctx.attr.generator_name_),
|
||||||
|
]
|
||||||
|
|
||||||
|
_gengen_closure = rule(
|
||||||
|
implementation = _gengen_closure_impl,
|
||||||
|
attrs = {
|
||||||
|
"generator_binary": attr.label(
|
||||||
|
executable = True,
|
||||||
|
allow_files = True,
|
||||||
|
mandatory = True,
|
||||||
|
cfg = "exec",
|
||||||
|
),
|
||||||
|
# "generator_name" is apparently reserved by Bazel for attrs in rules
|
||||||
|
"generator_name_": attr.string(mandatory = True),
|
||||||
|
},
|
||||||
|
provides = [HalideGeneratorBinaryInfo, HalideGeneratorNameInfo],
|
||||||
|
)
|
||||||
|
|
||||||
|
def _halide_library_instance_impl(ctx):
|
||||||
|
generator_binary = ctx.attr.generator_closure[HalideGeneratorBinaryInfo].generator_binary if ctx.attr.generator_closure else ""
|
||||||
|
generator_name = ctx.attr.generator_closure[HalideGeneratorNameInfo].generator_name_ if ctx.attr.generator_closure else ""
|
||||||
|
return [
|
||||||
|
HalideFunctionNameInfo(function_name = ctx.attr.function_name),
|
||||||
|
HalideGeneratorBinaryInfo(generator_binary = generator_binary),
|
||||||
|
HalideGeneratorNameInfo(generator_name_ = generator_name),
|
||||||
|
HalideGeneratorParamsInfo(generator_params = ctx.attr.generator_params),
|
||||||
|
HalideLibraryNameInfo(library_name = ctx.attr.library_name),
|
||||||
|
HalideTargetFeaturesInfo(target_features = ctx.attr.target_features),
|
||||||
|
]
|
||||||
|
|
||||||
|
_halide_library_instance = rule(
|
||||||
|
implementation = _halide_library_instance_impl,
|
||||||
|
attrs = {
|
||||||
|
"function_name": attr.string(),
|
||||||
|
"generator_closure": attr.label(
|
||||||
|
cfg = "exec",
|
||||||
|
providers = [HalideGeneratorBinaryInfo, HalideGeneratorNameInfo],
|
||||||
|
),
|
||||||
|
"generator_params": attr.string_list(),
|
||||||
|
"library_name": attr.string(),
|
||||||
|
"target_features": attr.string_list(),
|
||||||
|
},
|
||||||
|
provides = [
|
||||||
|
HalideFunctionNameInfo,
|
||||||
|
HalideGeneratorBinaryInfo,
|
||||||
|
HalideGeneratorNameInfo,
|
||||||
|
HalideGeneratorParamsInfo,
|
||||||
|
HalideLibraryNameInfo,
|
||||||
|
HalideTargetFeaturesInfo,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
def _gengen_impl(ctx):
|
||||||
|
if _has_dupes(ctx.attr.requested_outputs):
|
||||||
|
fail("Duplicate values in outputs: " + str(ctx.attr.requested_outputs))
|
||||||
|
|
||||||
|
function_name = ctx.attr.function_name[HalideFunctionNameInfo].function_name if ctx.attr.function_name else ""
|
||||||
|
generator_binary = ctx.attr.generator_binary[HalideGeneratorBinaryInfo].generator_binary if ctx.attr.generator_binary else ""
|
||||||
|
generator_name_ = ctx.attr.generator_name_[HalideGeneratorNameInfo].generator_name_ if ctx.attr.generator_name_ else ""
|
||||||
|
generator_params = ctx.attr.generator_params[HalideGeneratorParamsInfo].generator_params if ctx.attr.generator_params else []
|
||||||
|
library_name = ctx.attr.library_name[HalideLibraryNameInfo].library_name if ctx.attr.library_name else ""
|
||||||
|
target_features = ctx.attr.target_features[HalideTargetFeaturesInfo].target_features if ctx.attr.target_features else []
|
||||||
|
|
||||||
|
for gp in generator_params:
|
||||||
|
if " " in gp:
|
||||||
|
fail("%s: Entries in generator_params must not contain spaces." % library_name)
|
||||||
|
|
||||||
|
# Escape backslashes and double quotes.
|
||||||
|
generator_params = [gp.replace("\\", '\\\\"').replace('"', '\\"') for gp in generator_params]
|
||||||
|
|
||||||
|
execution_requirements = {}
|
||||||
|
|
||||||
|
# --- Calculate the output type(s) we're going to produce (and which ones should be verbose)
|
||||||
|
quiet_extra_outputs = []
|
||||||
|
verbose_extra_outputs = []
|
||||||
|
if ctx.attr.consider_halide_extra_outputs:
|
||||||
|
if "halide_extra_outputs" in ctx.var:
|
||||||
|
verbose_extra_outputs = ctx.var.get("halide_extra_outputs", "").split(",")
|
||||||
|
if "halide_extra_outputs_quiet" in ctx.var:
|
||||||
|
quiet_extra_outputs = ctx.var.get("halide_extra_outputs_quiet", "").split(",")
|
||||||
|
requested_outputs = sorted(collections.uniq(ctx.attr.requested_outputs +
|
||||||
|
verbose_extra_outputs +
|
||||||
|
quiet_extra_outputs))
|
||||||
|
|
||||||
|
# --- Assemble halide_target, adding extra features if necessary
|
||||||
|
base_target = ctx.attr.halide_base_target
|
||||||
|
if "," in base_target:
|
||||||
|
fail("halide_base_target should never be a multitarget")
|
||||||
|
if len(base_target.split("-")) != 3:
|
||||||
|
fail("halide_base_target should have exactly 3 components")
|
||||||
|
|
||||||
|
target_features = target_features + ctx.var.get("halide_target_features", "").split(",")
|
||||||
|
|
||||||
|
if "no_runtime" in target_features:
|
||||||
|
fail("Specifying 'no_runtime' in halide_target_features is not supported; " +
|
||||||
|
"please add 'add_halide_runtime_deps = False' to the halide_library() rule instead.")
|
||||||
|
|
||||||
|
for san in ["asan", "msan", "tsan"]:
|
||||||
|
if san in target_features:
|
||||||
|
fail("halide_library doesn't support '%s' in halide_target_features; please build with --config=%s instead." % (san, san))
|
||||||
|
|
||||||
|
# Append the features common to everything.
|
||||||
|
target_features.append("c_plus_plus_name_mangling")
|
||||||
|
target_features.append("no_runtime")
|
||||||
|
|
||||||
|
# Make it all neat and tidy.
|
||||||
|
target_features = sorted(collections.uniq(target_features))
|
||||||
|
|
||||||
|
# Get the multitarget list (if any) from halide_target_map
|
||||||
|
halide_targets = ctx.attr.halide_target_map.get(base_target, [base_target])
|
||||||
|
|
||||||
|
# Add the extra features to all of them
|
||||||
|
halide_targets = _add_features_to_all(halide_targets, target_features)
|
||||||
|
|
||||||
|
leaf_name = ctx.attr.filename.split("/")[-1]
|
||||||
|
|
||||||
|
output_files = []
|
||||||
|
output_dict = {}
|
||||||
|
verbose_output_paths = []
|
||||||
|
inputs = []
|
||||||
|
|
||||||
|
env = {
|
||||||
|
"HL_DEBUG_CODEGEN": str(ctx.var.get("halide_debug_codegen", 0)),
|
||||||
|
# --define halide_llvm_args=-time-passes is a typical usage
|
||||||
|
"HL_LLVM_ARGS": str(ctx.var.get("halide_llvm_args", "")),
|
||||||
|
}
|
||||||
|
|
||||||
|
be_very_quiet = ctx.var.get("halide_experimental_quiet", False) # I'm hunting wabbit...
|
||||||
|
|
||||||
|
# --- Calculate the final set of output files
|
||||||
|
for fmt in requested_outputs:
|
||||||
|
if fmt not in _output_extensions:
|
||||||
|
fail("Unknown Halide output '%s'; known outputs are %s" %
|
||||||
|
(fmt, sorted(_output_extensions.keys())))
|
||||||
|
ext, is_multiple, is_dir, _ = _output_extensions[fmt]
|
||||||
|
|
||||||
|
# Special-case Windows file extensions
|
||||||
|
if "windows" in halide_targets[-1]:
|
||||||
|
if ext == "o":
|
||||||
|
ext = "obj"
|
||||||
|
if ext == "a":
|
||||||
|
ext = "lib"
|
||||||
|
if is_multiple and len(halide_targets) > 1:
|
||||||
|
for h in halide_targets:
|
||||||
|
suffix = _canonicalize_target(h)
|
||||||
|
name = "%s-%s.%s" % (ctx.attr.filename, suffix, ext)
|
||||||
|
f = ctx.actions.declare_directory(name) if is_dir else ctx.actions.declare_file(name)
|
||||||
|
_add_output_file(f, fmt, output_files, output_dict, verbose_extra_outputs, verbose_output_paths)
|
||||||
|
else:
|
||||||
|
name = "%s.%s" % (ctx.attr.filename, ext)
|
||||||
|
f = ctx.actions.declare_directory(name) if is_dir else ctx.actions.declare_file(name)
|
||||||
|
_add_output_file(f, fmt, output_files, output_dict, verbose_extra_outputs, verbose_output_paths)
|
||||||
|
|
||||||
|
# --- Progress message(s), including log info about any 'extra' files being output due to --define halide_extra_output
|
||||||
|
progress_message = "Executing generator %s with target (%s) args (%s)." % (
|
||||||
|
generator_name_,
|
||||||
|
",".join(halide_targets),
|
||||||
|
" ".join(generator_params),
|
||||||
|
)
|
||||||
|
|
||||||
|
for f in output_files:
|
||||||
|
if any([f.path.endswith(suf) for suf in [".h", ".a", ".o", ".lib", ".registration.cpp", ".bc", ".halide_compiler_log"]]):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# If an extra output was specified via --define halide_extra_outputs=foo on the command line,
|
||||||
|
# add to the progress message (so that it is ephemeral and doesn't clog stdout).
|
||||||
|
#
|
||||||
|
# (Trailing space is intentional since Starlark will append a period to the end,
|
||||||
|
# making copy-n-paste harder than it might otherwise be...)
|
||||||
|
if not be_very_quiet:
|
||||||
|
extra_msg = "Emitting extra Halide output: %s " % f.path
|
||||||
|
progress_message += "\n" + extra_msg
|
||||||
|
if f.path in verbose_output_paths:
|
||||||
|
# buildifier: disable=print
|
||||||
|
print(extra_msg)
|
||||||
|
|
||||||
|
# --- Construct the arguments list for the Generator
|
||||||
|
arguments = ctx.actions.args()
|
||||||
|
arguments.add("-o", output_files[0].dirname)
|
||||||
|
if ctx.attr.generate_runtime:
|
||||||
|
arguments.add("-r", leaf_name)
|
||||||
|
if len(halide_targets) > 1:
|
||||||
|
fail("Only one halide_target allowed when using generate_runtime")
|
||||||
|
if function_name:
|
||||||
|
fail("halide_function_name not allowed when using generate_runtime")
|
||||||
|
else:
|
||||||
|
arguments.add("-g", generator_name_)
|
||||||
|
arguments.add("-n", leaf_name)
|
||||||
|
if function_name:
|
||||||
|
arguments.add("-f", function_name)
|
||||||
|
|
||||||
|
if requested_outputs:
|
||||||
|
arguments.add_joined("-e", requested_outputs, join_with = ",")
|
||||||
|
|
||||||
|
# Can't use add_joined(), as it will insert a space after target=
|
||||||
|
arguments.add("target=%s" % (",".join(halide_targets)))
|
||||||
|
if generator_params:
|
||||||
|
for p in generator_params:
|
||||||
|
for s in ["target"]:
|
||||||
|
if p.startswith("%s=" % s):
|
||||||
|
fail("You cannot specify %s in the generator_params parameter in bazel." % s)
|
||||||
|
arguments.add_all(generator_params)
|
||||||
|
|
||||||
|
show_gen_arg = ctx.var.get("halide_show_generator_command", "")
|
||||||
|
|
||||||
|
# If it's an exact match of a fully qualified path, show just that one.
|
||||||
|
# If it's * or "all", match everything.
|
||||||
|
if library_name and show_gen_arg in [library_name, "all", "*"] and not ctx.attr.generate_runtime:
|
||||||
|
# The 'Args' object can be printed, but can't be usefully converted to a string, or iterated,
|
||||||
|
# so we'll reproduce the logic here. We'll also take the opportunity to add or augment
|
||||||
|
# some args to be more useful to whoever runs it (eg, add `-v=1`, add some output files).
|
||||||
|
sg_args = ["-v", "1"]
|
||||||
|
sg_args += ["-o", "/tmp"]
|
||||||
|
sg_args += ["-g", generator_name_]
|
||||||
|
sg_args += ["-n", leaf_name]
|
||||||
|
if function_name:
|
||||||
|
sg_args += ["-f", function_name]
|
||||||
|
if requested_outputs:
|
||||||
|
# Ensure that several commonly-useful output are added
|
||||||
|
ro = sorted(collections.uniq(requested_outputs + ["stmt", "assembly", "llvm_assembly"]))
|
||||||
|
sg_args += ["-e", ",".join(ro)]
|
||||||
|
sg_args.append("target=%s" % (",".join(halide_targets)))
|
||||||
|
|
||||||
|
if generator_params:
|
||||||
|
sg_args += generator_params
|
||||||
|
|
||||||
|
# buildifier: disable=print
|
||||||
|
print(
|
||||||
|
"\n\nTo locally run the Generator for",
|
||||||
|
library_name,
|
||||||
|
"use the command:\n\n",
|
||||||
|
"bazel run -c opt",
|
||||||
|
generator_binary.label,
|
||||||
|
"--",
|
||||||
|
" ".join(sg_args),
|
||||||
|
"\n\n",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Finally... run the Generator.
|
||||||
|
ctx.actions.run(
|
||||||
|
execution_requirements = execution_requirements,
|
||||||
|
arguments = [arguments],
|
||||||
|
env = env,
|
||||||
|
executable = generator_binary.files_to_run.executable,
|
||||||
|
mnemonic = "ExecuteHalideGenerator",
|
||||||
|
inputs = depset(direct = inputs),
|
||||||
|
outputs = output_files,
|
||||||
|
progress_message = progress_message,
|
||||||
|
exec_group = "generator",
|
||||||
|
)
|
||||||
|
|
||||||
|
return [
|
||||||
|
DefaultInfo(files = depset(direct = output_files)),
|
||||||
|
OutputGroupInfo(**output_dict),
|
||||||
|
]
|
||||||
|
|
||||||
|
_gengen = rule(
|
||||||
|
implementation = _gengen_impl,
|
||||||
|
attrs = {
|
||||||
|
"consider_halide_extra_outputs": attr.bool(),
|
||||||
|
"filename": attr.string(),
|
||||||
|
"generate_runtime": attr.bool(default = False),
|
||||||
|
"generator_binary": attr.label(
|
||||||
|
cfg = "exec",
|
||||||
|
providers = [HalideGeneratorBinaryInfo],
|
||||||
|
),
|
||||||
|
# "generator_name" is apparently reserved by Bazel for attrs in rules
|
||||||
|
"generator_name_": attr.label(
|
||||||
|
cfg = "exec",
|
||||||
|
providers = [HalideGeneratorNameInfo],
|
||||||
|
),
|
||||||
|
"halide_base_target": attr.string(),
|
||||||
|
"function_name": attr.label(
|
||||||
|
cfg = "target",
|
||||||
|
providers = [
|
||||||
|
HalideFunctionNameInfo,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
"generator_params": attr.label(
|
||||||
|
cfg = "target",
|
||||||
|
providers = [
|
||||||
|
HalideGeneratorParamsInfo,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
"library_name": attr.label(
|
||||||
|
cfg = "target",
|
||||||
|
providers = [
|
||||||
|
HalideLibraryNameInfo,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
"target_features": attr.label(
|
||||||
|
cfg = "target",
|
||||||
|
providers = [
|
||||||
|
HalideTargetFeaturesInfo,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
"halide_target_map": attr.string_list_dict(),
|
||||||
|
"requested_outputs": attr.string_list(),
|
||||||
|
"_cc_toolchain": attr.label(default = "@bazel_tools//tools/cpp:current_cc_toolchain"),
|
||||||
|
},
|
||||||
|
fragments = ["cpp"],
|
||||||
|
output_to_genfiles = True,
|
||||||
|
toolchains = use_cpp_toolchain(),
|
||||||
|
exec_groups = {
|
||||||
|
"generator": exec_group(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
def _add_target_features(target, features):
|
||||||
|
if "," in target:
|
||||||
|
fail("Cannot use multitarget here")
|
||||||
|
new_target = target.split("-")
|
||||||
|
for f in features:
|
||||||
|
if f and f not in new_target:
|
||||||
|
new_target.append(f)
|
||||||
|
return "-".join(new_target)
|
||||||
|
|
||||||
|
def _add_features_to_all(halide_targets, features):
|
||||||
|
return [_canonicalize_target(_add_target_features(t, features)) for t in halide_targets]
|
||||||
|
|
||||||
|
def _has_dupes(some_list):
|
||||||
|
clean = collections.uniq(some_list)
|
||||||
|
return sorted(some_list) != sorted(clean)
|
||||||
|
|
||||||
|
# Target features which do not affect runtime compatibility.
|
||||||
|
_IRRELEVANT_FEATURES = collections.uniq([
|
||||||
|
"arm_dot_prod",
|
||||||
|
"arm_fp16",
|
||||||
|
"c_plus_plus_name_mangling",
|
||||||
|
"check_unsafe_promises",
|
||||||
|
"embed_bitcode",
|
||||||
|
"enable_llvm_loop_opt",
|
||||||
|
"large_buffers",
|
||||||
|
"no_asserts",
|
||||||
|
"no_bounds_query",
|
||||||
|
"profile",
|
||||||
|
"strict_float",
|
||||||
|
"sve",
|
||||||
|
"sve2",
|
||||||
|
"trace_loads",
|
||||||
|
"trace_pipeline",
|
||||||
|
"trace_realizations",
|
||||||
|
"trace_stores",
|
||||||
|
"user_context",
|
||||||
|
"wasm_sat_float_to_int",
|
||||||
|
"wasm_signext",
|
||||||
|
"wasm_simd128",
|
||||||
|
])
|
||||||
|
|
||||||
|
def _discard_irrelevant_features(halide_target_features = []):
|
||||||
|
return sorted(collections.uniq([f for f in halide_target_features if f not in _IRRELEVANT_FEATURES]))
|
||||||
|
|
||||||
|
def _halide_library_runtime_target_name(halide_target_features = []):
|
||||||
|
return "_".join(["halide_library_runtime"] + _discard_irrelevant_features(halide_target_features))
|
||||||
|
|
||||||
|
def _define_halide_library_runtime(
|
||||||
|
halide_target_features = [],
|
||||||
|
compatible_with = []):
|
||||||
|
target_name = _halide_library_runtime_target_name(halide_target_features)
|
||||||
|
|
||||||
|
if not native.existing_rule("halide_library_runtime.generator"):
|
||||||
|
halide_generator(
|
||||||
|
name = "halide_library_runtime.generator",
|
||||||
|
srcs = [],
|
||||||
|
deps = [],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
condition_deps = {}
|
||||||
|
for base_target, cfgs in _HALIDE_TARGET_CONFIG_SETTINGS_MAP.items():
|
||||||
|
target_features = _discard_irrelevant_features(halide_target_features)
|
||||||
|
halide_target_name = _halide_target_to_bazel_rule_name(base_target)
|
||||||
|
gengen_name = "%s_%s" % (halide_target_name, target_name)
|
||||||
|
|
||||||
|
_halide_library_instance(
|
||||||
|
name = "%s.library_instance" % gengen_name,
|
||||||
|
compatible_with = compatible_with,
|
||||||
|
function_name = "",
|
||||||
|
generator_closure = ":halide_library_runtime.generator_closure",
|
||||||
|
generator_params = [],
|
||||||
|
library_name = "",
|
||||||
|
target_features = target_features,
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
hl_instance = ":%s.library_instance" % gengen_name
|
||||||
|
|
||||||
|
_gengen(
|
||||||
|
name = gengen_name,
|
||||||
|
compatible_with = compatible_with,
|
||||||
|
filename = "%s/%s" % (halide_target_name, target_name),
|
||||||
|
generate_runtime = True,
|
||||||
|
generator_binary = hl_instance,
|
||||||
|
generator_name_ = hl_instance,
|
||||||
|
halide_base_target = base_target,
|
||||||
|
requested_outputs = ["object"],
|
||||||
|
tags = ["manual"],
|
||||||
|
target_features = hl_instance,
|
||||||
|
visibility = ["@halide//:__subpackages__"],
|
||||||
|
)
|
||||||
|
for cfg in cfgs:
|
||||||
|
condition_deps[cfg] = [":%s" % gengen_name]
|
||||||
|
|
||||||
|
deps = []
|
||||||
|
native.cc_library(
|
||||||
|
name = target_name,
|
||||||
|
compatible_with = compatible_with,
|
||||||
|
srcs = select(condition_deps),
|
||||||
|
linkopts = halide_runtime_linkopts(),
|
||||||
|
tags = ["manual"],
|
||||||
|
deps = deps,
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
|
|
||||||
|
return target_name
|
||||||
|
|
||||||
|
def _standard_library_runtime_features():
|
||||||
|
_standard_features = [
|
||||||
|
[],
|
||||||
|
["cuda"],
|
||||||
|
["metal"],
|
||||||
|
["opencl"],
|
||||||
|
["openglcompute"],
|
||||||
|
["openglcompute", "egl"],
|
||||||
|
]
|
||||||
|
return [f for f in _standard_features] + [f + ["debug"] for f in _standard_features]
|
||||||
|
|
||||||
|
def _standard_library_runtime_names():
|
||||||
|
return collections.uniq([_halide_library_runtime_target_name(f) for f in _standard_library_runtime_features()])
|
||||||
|
|
||||||
|
def halide_library_runtimes(compatible_with = []):
|
||||||
|
unused = [
|
||||||
|
_define_halide_library_runtime(f, compatible_with = compatible_with)
|
||||||
|
for f in _standard_library_runtime_features()
|
||||||
|
]
|
||||||
|
unused = unused # unused variable
|
||||||
|
|
||||||
|
def halide_generator(
|
||||||
|
name,
|
||||||
|
srcs,
|
||||||
|
compatible_with = [],
|
||||||
|
copts = [],
|
||||||
|
deps = [],
|
||||||
|
generator_name = "",
|
||||||
|
includes = [],
|
||||||
|
tags = [],
|
||||||
|
testonly = False,
|
||||||
|
visibility = None):
|
||||||
|
if not name.endswith(".generator"):
|
||||||
|
fail("halide_generator rules must end in .generator")
|
||||||
|
|
||||||
|
basename = name[:-10] # strip ".generator" suffix
|
||||||
|
if not generator_name:
|
||||||
|
generator_name = basename
|
||||||
|
|
||||||
|
# Note: This target is public, but should not be needed by the vast
|
||||||
|
# majority of users. Unless you are writing a custom Bazel rule that
|
||||||
|
# involves Halide generation, you most probably won't need to depend on
|
||||||
|
# this rule.
|
||||||
|
native.cc_binary(
|
||||||
|
name = name,
|
||||||
|
copts = copts + halide_language_copts(),
|
||||||
|
linkopts = halide_language_linkopts(),
|
||||||
|
compatible_with = compatible_with,
|
||||||
|
srcs = srcs,
|
||||||
|
deps = [
|
||||||
|
"@halide//:gengen",
|
||||||
|
"@halide//:language",
|
||||||
|
] + deps,
|
||||||
|
tags = ["manual"] + tags,
|
||||||
|
testonly = testonly,
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
|
|
||||||
|
_gengen_closure(
|
||||||
|
name = "%s_closure" % name,
|
||||||
|
generator_binary = name,
|
||||||
|
generator_name_ = generator_name,
|
||||||
|
compatible_with = compatible_with,
|
||||||
|
testonly = testonly,
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# This rule exists to allow us to select() on halide_target_features.
|
||||||
|
def _select_halide_library_runtime_impl(ctx):
|
||||||
|
f = ctx.attr.halide_target_features
|
||||||
|
|
||||||
|
standard_runtimes = {t.label.name: t for t in ctx.attr._standard_runtimes}
|
||||||
|
|
||||||
|
f = sorted(_discard_irrelevant_features(collections.uniq(f)))
|
||||||
|
runtime_name = _halide_library_runtime_target_name(f)
|
||||||
|
if runtime_name not in standard_runtimes:
|
||||||
|
fail(("There is no Halide runtime available for the feature set combination %s. " +
|
||||||
|
"Please use contact information from halide-lang.org to contact the Halide " +
|
||||||
|
"team to add the right combination.") % str(f))
|
||||||
|
|
||||||
|
return standard_runtimes[runtime_name][CcInfo]
|
||||||
|
|
||||||
|
_select_halide_library_runtime = rule(
|
||||||
|
implementation = _select_halide_library_runtime_impl,
|
||||||
|
attrs = {
|
||||||
|
"halide_target_features": attr.string_list(),
|
||||||
|
"_standard_runtimes": attr.label_list(
|
||||||
|
default = ["@halide//:%s" % n for n in _standard_library_runtime_names()],
|
||||||
|
providers = [CcInfo],
|
||||||
|
),
|
||||||
|
},
|
||||||
|
provides = [CcInfo],
|
||||||
|
)
|
||||||
|
|
||||||
|
def halide_library_from_generator(
|
||||||
|
name,
|
||||||
|
generator,
|
||||||
|
add_halide_runtime_deps = True,
|
||||||
|
compatible_with = [],
|
||||||
|
deps = [],
|
||||||
|
function_name = None,
|
||||||
|
generator_params = [],
|
||||||
|
halide_target_features = [],
|
||||||
|
halide_target_map = halide_library_default_target_map(),
|
||||||
|
includes = [],
|
||||||
|
namespace = None,
|
||||||
|
tags = [],
|
||||||
|
testonly = False,
|
||||||
|
visibility = None):
|
||||||
|
if not function_name:
|
||||||
|
function_name = name
|
||||||
|
|
||||||
|
if namespace:
|
||||||
|
function_name = "%s::%s" % (namespace, function_name)
|
||||||
|
|
||||||
|
generator_closure = "%s_closure" % generator
|
||||||
|
|
||||||
|
_halide_library_instance(
|
||||||
|
name = "%s.library_instance" % name,
|
||||||
|
compatible_with = compatible_with,
|
||||||
|
function_name = function_name,
|
||||||
|
generator_closure = generator_closure,
|
||||||
|
generator_params = generator_params,
|
||||||
|
library_name = "//%s:%s" % (native.package_name(), name),
|
||||||
|
target_features = halide_target_features,
|
||||||
|
testonly = testonly,
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
hl_instance = ":%s.library_instance" % name
|
||||||
|
|
||||||
|
condition_deps = {}
|
||||||
|
for base_target, cfgs in _HALIDE_TARGET_CONFIG_SETTINGS_MAP.items():
|
||||||
|
base_target_name = _halide_target_to_bazel_rule_name(base_target)
|
||||||
|
gengen_name = "%s_%s" % (base_target_name, name)
|
||||||
|
_gengen(
|
||||||
|
name = gengen_name,
|
||||||
|
compatible_with = compatible_with,
|
||||||
|
consider_halide_extra_outputs = True,
|
||||||
|
filename = "%s/%s" % (base_target_name, name),
|
||||||
|
function_name = hl_instance,
|
||||||
|
generator_binary = generator_closure,
|
||||||
|
generator_name_ = generator_closure,
|
||||||
|
generator_params = hl_instance,
|
||||||
|
halide_base_target = base_target,
|
||||||
|
halide_target_map = halide_target_map,
|
||||||
|
library_name = hl_instance,
|
||||||
|
requested_outputs = ["static_library"],
|
||||||
|
tags = ["manual"] + tags,
|
||||||
|
target_features = hl_instance,
|
||||||
|
testonly = testonly,
|
||||||
|
)
|
||||||
|
for cfg in cfgs:
|
||||||
|
condition_deps[cfg] = [":%s" % gengen_name]
|
||||||
|
|
||||||
|
# Use a canonical target to build CC, regardless of config detected
|
||||||
|
cc_base_target = "x86-64-linux"
|
||||||
|
|
||||||
|
for output, target_name in [
|
||||||
|
("c_header", "%s_h" % name),
|
||||||
|
("c_source", "%s_cc" % name),
|
||||||
|
]:
|
||||||
|
_gengen(
|
||||||
|
name = target_name,
|
||||||
|
compatible_with = compatible_with,
|
||||||
|
filename = name,
|
||||||
|
function_name = hl_instance,
|
||||||
|
generator_binary = generator_closure,
|
||||||
|
generator_name_ = generator_closure,
|
||||||
|
generator_params = hl_instance,
|
||||||
|
halide_base_target = cc_base_target,
|
||||||
|
library_name = hl_instance,
|
||||||
|
requested_outputs = [output],
|
||||||
|
tags = ["manual"] + tags,
|
||||||
|
target_features = hl_instance,
|
||||||
|
testonly = testonly,
|
||||||
|
)
|
||||||
|
|
||||||
|
_select_halide_library_runtime(
|
||||||
|
name = "%s.halide_library_runtime_deps" % name,
|
||||||
|
halide_target_features = halide_target_features,
|
||||||
|
compatible_with = compatible_with,
|
||||||
|
tags = tags,
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
native.filegroup(
|
||||||
|
name = "%s_object" % name,
|
||||||
|
srcs = select(condition_deps),
|
||||||
|
output_group = "generated_object",
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
compatible_with = compatible_with,
|
||||||
|
tags = tags,
|
||||||
|
testonly = testonly,
|
||||||
|
)
|
||||||
|
|
||||||
|
native.cc_library(
|
||||||
|
name = name,
|
||||||
|
srcs = ["%s_object" % name],
|
||||||
|
hdrs = [
|
||||||
|
":%s_h" % name,
|
||||||
|
],
|
||||||
|
deps = deps +
|
||||||
|
["@halide//:runtime"] + # for HalideRuntime.h, etc
|
||||||
|
([":%s.halide_library_runtime_deps" % name] if add_halide_runtime_deps else []), # for the runtime implementation
|
||||||
|
defines = ["HALIDE_FUNCTION_ATTRS=HALIDE_MUST_USE_RESULT"],
|
||||||
|
compatible_with = compatible_with,
|
||||||
|
includes = includes,
|
||||||
|
tags = tags,
|
||||||
|
testonly = testonly,
|
||||||
|
visibility = visibility,
|
||||||
|
linkstatic = 1,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Return the fully-qualified built target name.
|
||||||
|
return "//%s:%s" % (native.package_name(), name)
|
||||||
|
|
||||||
|
def halide_library(
|
||||||
|
name,
|
||||||
|
srcs = [],
|
||||||
|
add_halide_runtime_deps = True,
|
||||||
|
copts = [],
|
||||||
|
compatible_with = [],
|
||||||
|
filter_deps = [],
|
||||||
|
function_name = None,
|
||||||
|
generator_params = [],
|
||||||
|
generator_deps = [],
|
||||||
|
generator_name = None,
|
||||||
|
halide_target_features = [],
|
||||||
|
halide_target_map = halide_library_default_target_map(),
|
||||||
|
includes = [],
|
||||||
|
namespace = None,
|
||||||
|
tags = [],
|
||||||
|
testonly = False,
|
||||||
|
visibility = None):
|
||||||
|
if not srcs and not generator_deps:
|
||||||
|
fail("halide_library needs at least one of srcs or generator_deps to provide a generator")
|
||||||
|
|
||||||
|
halide_generator(
|
||||||
|
name = "%s.generator" % name,
|
||||||
|
srcs = srcs,
|
||||||
|
compatible_with = compatible_with,
|
||||||
|
generator_name = generator_name,
|
||||||
|
deps = generator_deps,
|
||||||
|
includes = includes,
|
||||||
|
copts = copts,
|
||||||
|
tags = tags,
|
||||||
|
testonly = testonly,
|
||||||
|
visibility = visibility,
|
||||||
|
)
|
||||||
|
|
||||||
|
return halide_library_from_generator(
|
||||||
|
name = name,
|
||||||
|
generator = ":%s.generator" % name,
|
||||||
|
add_halide_runtime_deps = add_halide_runtime_deps,
|
||||||
|
compatible_with = compatible_with,
|
||||||
|
deps = filter_deps,
|
||||||
|
function_name = function_name,
|
||||||
|
generator_params = generator_params,
|
||||||
|
halide_target_features = halide_target_features,
|
||||||
|
halide_target_map = halide_target_map,
|
||||||
|
includes = includes,
|
||||||
|
namespace = namespace,
|
||||||
|
tags = tags,
|
||||||
|
testonly = testonly,
|
||||||
|
visibility = visibility,
|
||||||
|
)
|
Loading…
Reference in New Issue
Block a user