From 3df29399c628a035eac8d79998e03e3f4f75bfd9 Mon Sep 17 00:00:00 2001 From: Mark Horvath Date: Fri, 15 Dec 2023 16:08:45 +0100 Subject: [PATCH] [test] API tests for YUV2RGB conversions --- intrinsiccv/include/intrinsiccv.h | 116 ++++++++++++++++++- test/api/test_yuv_to_rgb.cpp | 181 ++++++++++++++++++++++++++++++ 2 files changed, 296 insertions(+), 1 deletion(-) create mode 100644 test/api/test_yuv_to_rgb.cpp diff --git a/intrinsiccv/include/intrinsiccv.h b/intrinsiccv/include/intrinsiccv.h index 64c751c6f..0159e2545 100644 --- a/intrinsiccv/include/intrinsiccv.h +++ b/intrinsiccv/include/intrinsiccv.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Arm Limited and/or its affiliates +// SPDX-FileCopyrightText: 2023-2024 Arm Limited and/or its affiliates // // SPDX-License-Identifier: Apache-2.0 @@ -161,6 +161,35 @@ void INTRINSICCV_C_API(rgba_to_rgb_u8)(const uint8_t *src, size_t src_stride, uint8_t *dst, size_t dst_stride, size_t width, size_t height); +/// Converts an NV12 or NV21 YUV image to RGB. All channels are 8-bit wide. +/// +/// Destination data is filled liked this: +/// | R,G,B | R,G,B | R,G,B | ... +/// Where each letter represents one byte of data, and one pixel is represented +/// by 3 bytes. There is no padding between the pixels. +/// If 4-byte alignment is required then intrinsiccv_yuv_sp_to_rgba_u8 can be +/// used. +/// +/// @param src_y Pointer to the input's Y component. Must be non-null. +/// @param src_y_stride Distance in bytes from the start of one row to the +/// start of the next row for the input's Y component. +/// Must not be less than width * sizeof(u8). +/// @param src_uv Pointer to the input's interleaved UV components. +/// Must be non-null. If the width parameter is odd, the +/// width of this input stream still needs to be even. +/// @param src_uv_stride Distance in bytes from the start of one row to the +/// start of the next row for the input's UV components. +/// Must not be less than +/// __builtin_align_up(width, 2) * sizeof(u8). +/// @param dst Pointer to the destination data. Must be non-null. +/// @param dst_stride Distance in bytes from the start of one row to the +/// start of the next row for the destination data. Must +/// not be less than width * sizeof(type). +/// @param width How many pixels are in a row for the input & output. +/// @param height How many rows are in the input & output. +/// @param is_nv21 If true, input is treated as NV21, otherwise treated +/// as NV12. +/// void INTRINSICCV_C_API(yuv_sp_to_rgb_u8)(const uint8_t *src_y, size_t src_y_stride, const uint8_t *src_uv, @@ -168,6 +197,35 @@ void INTRINSICCV_C_API(yuv_sp_to_rgb_u8)(const uint8_t *src_y, size_t dst_stride, size_t width, size_t height, bool is_nv21); +/// Converts an NV12 or NV21 YUV image to BGR. All channels are 8-bit wide. +/// +/// Destination data is filled liked this: +/// | B,G,R | B,G,R | B,G,R | ... +/// Where each letter represents one byte of data, and one pixel is represented +/// by 3 bytes. There is no padding between the pixels. +/// If 4-byte alignment is required then intrinsiccv_yuv_sp_to_bgra_u8 can be +/// used. +/// +/// @param src_y Pointer to the input's Y component. Must be non-null. +/// @param src_y_stride Distance in bytes from the start of one row to the +/// start of the next row for the input's Y component. +/// Must not be less than width * sizeof(u8). +/// @param src_uv Pointer to the input's interleaved UV components. +/// Must be non-null. If the width parameter is odd, the +/// width of this input stream still needs to be even. +/// @param src_uv_stride Distance in bytes from the start of one row to the +/// start of the next row for the input's UV components. +/// Must not be less than +/// __builtin_align_up(width, 2) * sizeof(u8). +/// @param dst Pointer to the destination data. Must be non-null. +/// @param dst_stride Distance in bytes from the start of one row to the +/// start of the next row for the destination data. Must +/// not be less than width * sizeof(type). +/// @param width How many pixels are in a row for the input & output. +/// @param height How many rows are in the input & output. +/// @param is_nv21 If true, input is treated as NV21, otherwise treated +/// as NV12. +/// void INTRINSICCV_C_API(yuv_sp_to_bgr_u8)(const uint8_t *src_y, size_t src_y_stride, const uint8_t *src_uv, @@ -175,6 +233,34 @@ void INTRINSICCV_C_API(yuv_sp_to_bgr_u8)(const uint8_t *src_y, size_t dst_stride, size_t width, size_t height, bool is_nv21); +/// Converts an NV12 or NV21 YUV image to RGBA. All channels are 8-bit wide. +/// Alpha channel is set to 0xFF. +/// +/// Destination data is filled liked this: +/// | R,G,B,A | R,G,B,A | R,G,B,A | ... +/// Where each letter represents one byte of data, and one pixel is represented +/// by 4 bytes. There is no padding between the pixels. +/// +/// @param src_y Pointer to the input's Y component. Must be non-null. +/// @param src_y_stride Distance in bytes from the start of one row to the +/// start of the next row for the input's Y component. +/// Must not be less than width * sizeof(u8). +/// @param src_uv Pointer to the input's interleaved UV components. +/// Must be non-null. If the width parameter is odd, the +/// width of this input stream still needs to be even. +/// @param src_uv_stride Distance in bytes from the start of one row to the +/// start of the next row for the input's UV components. +/// Must not be less than +/// __builtin_align_up(width, 2) * sizeof(u8). +/// @param dst Pointer to the destination data. Must be non-null. +/// @param dst_stride Distance in bytes from the start of one row to the +/// start of the next row for the destination data. Must +/// not be less than width * sizeof(type). +/// @param width How many pixels are in a row for the input & output. +/// @param height How many rows are in the input & output. +/// @param is_nv21 If true, input is treated as NV21, otherwise treated +/// as NV12. +/// void INTRINSICCV_C_API(yuv_sp_to_rgba_u8)(const uint8_t *src_y, size_t src_y_stride, const uint8_t *src_uv, @@ -182,6 +268,34 @@ void INTRINSICCV_C_API(yuv_sp_to_rgba_u8)(const uint8_t *src_y, size_t dst_stride, size_t width, size_t height, bool is_nv21); +/// Converts an NV12 or NV21 YUV image to BGRA. All channels are 8-bit wide. +/// Alpha channel is set to 0xFF. +/// +/// Destination data is filled liked this: +/// | B,G,R,A | B,G,R,A | B,G,R,A | ... +/// Where each letter represents one byte of data, and one pixel is represented +/// by 4 bytes. There is no padding between the pixels. +/// +/// @param src_y Pointer to the input's Y component. Must be non-null. +/// @param src_y_stride Distance in bytes from the start of one row to the +/// start of the next row for the input's Y component. +/// Must not be less than width * sizeof(u8). +/// @param src_uv Pointer to the input's interleaved UV components. +/// Must be non-null. If the width parameter is odd, the +/// width of this input stream still needs to be even. +/// @param src_uv_stride Distance in bytes from the start of one row to the +/// start of the next row for the input's UV components. +/// Must not be less than +/// __builtin_align_up(width, 2) * sizeof(u8). +/// @param dst Pointer to the destination data. Must be non-null. +/// @param dst_stride Distance in bytes from the start of one row to the +/// start of the next row for the destination data. Must +/// not be less than width * sizeof(type). +/// @param width How many pixels are in a row for the input & output. +/// @param height How many rows are in the input & output. +/// @param is_nv21 If true, input is treated as NV21, otherwise treated +/// as NV12. +/// void INTRINSICCV_C_API(yuv_sp_to_bgra_u8)(const uint8_t *src_y, size_t src_y_stride, const uint8_t *src_uv, diff --git a/test/api/test_yuv_to_rgb.cpp b/test/api/test_yuv_to_rgb.cpp new file mode 100644 index 000000000..03de4d4bf --- /dev/null +++ b/test/api/test_yuv_to_rgb.cpp @@ -0,0 +1,181 @@ +// SPDX-FileCopyrightText: 2024 Arm Limited and/or its affiliates +// +// SPDX-License-Identifier: Apache-2.0 + +#include +#include + +#include "framework/array.h" +#include "framework/utils.h" + +class YuvTest final { + public: + YuvTest(size_t channel_number, bool switch_blue) + : channel_number_(channel_number), switch_blue_(switch_blue) {} + + template + void execute_scalar_test(F impl, bool is_nv21) { + size_t scalar_path_width = 5; + execute_test(impl, scalar_path_width, is_nv21); + } + + template + void execute_vector_test(F impl, bool is_nv21) { + size_t vector_path_width = (2 * test::Options::vector_lanes()) - 3; + execute_test(impl, vector_path_width, is_nv21); + } + + private: + template + void execute_test(F impl, size_t logical_width, bool is_nv21) { + test::Array2D input_y{logical_width, 7}; + input_y.set(0, 0, {10, 20, 255, 199}); + input_y.set(1, 0, {1, 120, 0, 17}); + input_y.set(2, 0, {2, 3, 240, 228}); + input_y.set(6, 0, {7, 11, 128, 129}); + + // the width of the UV input must be even + test::Array2D input_uv{__builtin_align_up(logical_width, 2), 3}; + input_uv.set(0, 0, {100, 130, 255, 255}); + input_uv.set(1, 0, {0, 1, 3, 4}); + input_uv.set(2, 0, {7, 8, 9, 10}); + + test::Array2D expected{logical_width * channel_number_, 6}; + calculate_expected(input_y, input_uv, expected, is_nv21); + + test::Array2D actual{logical_width * channel_number_, 6}; + + impl(input_y.data(), input_y.stride(), input_uv.data(), input_uv.stride(), + actual.data(), actual.stride(), expected.width() / channel_number_, + expected.height(), is_nv21); + + EXPECT_EQ_ARRAY2D(expected, actual); + } + + void calculate_expected(test::Array2D &y_arr, + test::Array2D &uv_arr, + test::Array2D &exp_arr, bool is_nv21) { + for (size_t vindex = 0; vindex < exp_arr.height(); vindex++) { + for (size_t hindex = 0; hindex < exp_arr.width() / channel_number_; + hindex++) { + int32_t y = *y_arr.at(vindex, hindex); + y = std::max(0, y - 16); + int32_t u = + *uv_arr.at(vindex / 2, (hindex - hindex % 2) + (is_nv21 ? 1 : 0)); + u -= 128; + int32_t v = + *uv_arr.at(vindex / 2, (hindex - hindex % 2) + (is_nv21 ? 0 : 1)); + v -= 128; + + int32_t r = ((1220542 * y) + (1673527 * v) + (1 << 19)) >> 20; + int32_t g = + ((1220542 * y) - (409993 * u) - (852492 * v) + (1 << 19)) >> 20; + int32_t b = ((1220542 * y) + (2116026 * u) + (1 << 19)) >> 20; + + uint8_t r_u8 = saturate_cast_s32_to_u8(r); + uint8_t g_u8 = saturate_cast_s32_to_u8(g); + uint8_t b_u8 = saturate_cast_s32_to_u8(b); + + if (switch_blue_) { + std::swap(b_u8, r_u8); + } + + if (channel_number_ == 4) { + exp_arr.set(vindex, hindex * channel_number_, + {r_u8, g_u8, b_u8, 0xff}); + } else { + exp_arr.set(vindex, hindex * channel_number_, {r_u8, g_u8, b_u8}); + } + } + } + } + + static uint8_t saturate_cast_s32_to_u8(int32_t rhs) { + return static_cast( + std::min(std::max(0, rhs), + static_cast(std::numeric_limits::max()))); + } + + size_t channel_number_; + bool switch_blue_; +}; + +TEST(YUV2, RGB_NV12_scalar) { + YuvTest yuv_test(3, false); + yuv_test.execute_scalar_test(intrinsiccv_yuv_sp_to_rgb_u8, false); +} + +TEST(YUV2, RGB_NV12_vector) { + YuvTest yuv_test(3, false); + yuv_test.execute_vector_test(intrinsiccv_yuv_sp_to_rgb_u8, false); +} + +TEST(YUV2, RGB_NV21_scalar) { + YuvTest yuv_test(3, false); + yuv_test.execute_scalar_test(intrinsiccv_yuv_sp_to_rgb_u8, true); +} + +TEST(YUV2, RGB_NV21_vector) { + YuvTest yuv_test(3, false); + yuv_test.execute_vector_test(intrinsiccv_yuv_sp_to_rgb_u8, true); +} + +TEST(YUV2, BGR_NV12_scalar) { + YuvTest yuv_test(3, true); + yuv_test.execute_scalar_test(intrinsiccv_yuv_sp_to_bgr_u8, false); +} + +TEST(YUV2, BGR_NV12_vector) { + YuvTest yuv_test(3, true); + yuv_test.execute_vector_test(intrinsiccv_yuv_sp_to_bgr_u8, false); +} + +TEST(YUV2, BGR_NV21_scalar) { + YuvTest yuv_test(3, true); + yuv_test.execute_scalar_test(intrinsiccv_yuv_sp_to_bgr_u8, true); +} + +TEST(YUV2, BGR_NV21_vector) { + YuvTest yuv_test(3, true); + yuv_test.execute_vector_test(intrinsiccv_yuv_sp_to_bgr_u8, true); +} + +TEST(YUV2, RGBA_NV12_scalar) { + YuvTest yuv_test(4, false); + yuv_test.execute_scalar_test(intrinsiccv_yuv_sp_to_rgba_u8, false); +} + +TEST(YUV2, RGBA_NV12_vector) { + YuvTest yuv_test(4, false); + yuv_test.execute_vector_test(intrinsiccv_yuv_sp_to_rgba_u8, false); +} + +TEST(YUV2, RGBA_NV21_scalar) { + YuvTest yuv_test(4, false); + yuv_test.execute_scalar_test(intrinsiccv_yuv_sp_to_rgba_u8, true); +} + +TEST(YUV2, RGBA_NV21_vector) { + YuvTest yuv_test(4, false); + yuv_test.execute_vector_test(intrinsiccv_yuv_sp_to_rgba_u8, true); +} + +TEST(YUV2, BGRA_NV12_scalar) { + YuvTest yuv_test(4, true); + yuv_test.execute_scalar_test(intrinsiccv_yuv_sp_to_bgra_u8, false); +} + +TEST(YUV2, BGRA_NV12_vector) { + YuvTest yuv_test(4, true); + yuv_test.execute_vector_test(intrinsiccv_yuv_sp_to_bgra_u8, false); +} + +TEST(YUV2, BGRA_NV21_scalar) { + YuvTest yuv_test(4, true); + yuv_test.execute_scalar_test(intrinsiccv_yuv_sp_to_bgra_u8, true); +} + +TEST(YUV2, BGRA_NV21_vector) { + YuvTest yuv_test(4, true); + yuv_test.execute_vector_test(intrinsiccv_yuv_sp_to_bgra_u8, true); +} -- GitLab