Skip to content

Commit

Permalink
Add utilities to get/set RGBA values for any RGB format. (AOMediaCode…
Browse files Browse the repository at this point in the history
  • Loading branch information
maryla-uc authored Oct 13, 2023
1 parent ce3612b commit 163ee01
Show file tree
Hide file tree
Showing 5 changed files with 244 additions and 11 deletions.
11 changes: 11 additions & 0 deletions include/avif/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,8 @@ typedef struct avifRGBColorSpaceInfo
float maxChannelF; // Same as maxChannel but as a float.
} avifRGBColorSpaceInfo;

avifBool avifGetRGBColorSpaceInfo(const avifRGBImage * rgb, avifRGBColorSpaceInfo * info);

// Information about a YUV color space.
typedef struct avifYUVColorSpaceInfo
{
Expand All @@ -203,12 +205,21 @@ typedef struct avifYUVColorSpaceInfo
avifReformatMode mode; // Appropriate RGB<->YUV conversion mode.
} avifYUVColorSpaceInfo;

avifBool avifGetYUVColorSpaceInfo(const avifImage * image, avifYUVColorSpaceInfo * info);

typedef struct avifReformatState
{
avifRGBColorSpaceInfo rgb;
avifYUVColorSpaceInfo yuv;
} avifReformatState;

// Retrieves the pixel value at position (x, y) expressed as floats in [0, 1]. If the image's format doesn't have alpha,
// rgbaPixel[3] is set to 1.0f.
void avifGetRGBAPixel(const avifRGBImage * src, uint32_t x, uint32_t y, const avifRGBColorSpaceInfo * info, float rgbaPixel[4]);
// Sets the pixel value at position (i, j) from RGBA values expressed as floats in [0, 1]. If the image's format doesn't
// support alpha, rgbaPixel[3] is ignored.
void avifSetRGBAPixel(const avifRGBImage * dst, uint32_t x, uint32_t y, const avifRGBColorSpaceInfo * info, const float rgbaPixel[4]);

// Returns:
// * AVIF_RESULT_OK - Converted successfully with libyuv
// * AVIF_RESULT_NOT_IMPLEMENTED - The fast path for this combination is not implemented with libyuv, use built-in RGB conversion
Expand Down
134 changes: 124 additions & 10 deletions src/reformat.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ struct YUVBlock
float v;
};

static avifBool avifGetRGBColorSpaceInfo(const avifRGBImage * rgb, avifRGBColorSpaceInfo * info)
avifBool avifGetRGBColorSpaceInfo(const avifRGBImage * rgb, avifRGBColorSpaceInfo * info)
{
if ((rgb->depth != 8) && (rgb->depth != 10) && (rgb->depth != 12) && (rgb->depth != 16)) {
return AVIF_FALSE;
Expand Down Expand Up @@ -97,7 +97,7 @@ static avifBool avifGetRGBColorSpaceInfo(const avifRGBImage * rgb, avifRGBColorS
return AVIF_TRUE;
}

static avifBool avifGetYUVColorSpaceInfo(const avifImage * image, avifYUVColorSpaceInfo * info)
avifBool avifGetYUVColorSpaceInfo(const avifImage * image, avifYUVColorSpaceInfo * info)
{
#if defined(AVIF_ENABLE_EXPERIMENTAL_YCGCO_R)
const avifBool useYCgCo = (image->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_YCGCO_RE) ||
Expand Down Expand Up @@ -567,6 +567,20 @@ static void avifStoreRGB8Pixel(avifRGBFormat format, uint8_t R, uint8_t G, uint8
*ptrB = B;
}

static void avifGetRGB565(const uint8_t * ptrR, uint8_t * R, uint8_t * G, uint8_t * B)
{
// References for RGB565 color conversion:
// * https://docs.microsoft.com/en-us/windows/win32/directshow/working-with-16-bit-rgb
// * https://chromium.googlesource.com/libyuv/libyuv/+/331c361581896292fb46c8c6905e41262b7ca95f/source/row_common.cc#185
const uint16_t rgb656 = ((const uint16_t *)ptrR)[0];
const uint16_t r5 = (rgb656 & 0xF800) >> 11;
const uint16_t g6 = (rgb656 & 0x07E0) >> 5;
const uint16_t b5 = (rgb656 & 0x001F);
*R = (uint8_t)((r5 << 3) | (r5 >> 2));
*G = (uint8_t)((g6 << 2) | (g6 >> 4));
*B = (uint8_t)((b5 << 3) | (b5 >> 2));
}

// Note: This function handles alpha (un)multiply.
static avifResult avifImageYUVAnyToRGBAnySlow(const avifImage * image,
avifRGBImage * rgb,
Expand Down Expand Up @@ -1288,6 +1302,16 @@ static avifResult avifImageYUV8ToRGB8Mono(const avifImage * image, avifRGBImage
return AVIF_RESULT_OK;
}

// This constant comes from libyuv. For details, see here:
// https://chromium.googlesource.com/libyuv/libyuv/+/2f87e9a7/source/row_common.cc#3537
#define F16_MULTIPLIER 1.9259299444e-34f

typedef union avifF16
{
float f;
uint32_t u32;
} avifF16;

static avifResult avifRGBImageToF16(avifRGBImage * rgb)
{
avifResult libyuvResult = AVIF_RESULT_NOT_IMPLEMENTED;
Expand All @@ -1299,19 +1323,13 @@ static avifResult avifRGBImageToF16(avifRGBImage * rgb)
}
const uint32_t channelCount = avifRGBFormatChannelCount(rgb->format);
const float scale = 1.0f / ((1 << rgb->depth) - 1);
// This constant comes from libyuv. For details, see here:
// https://chromium.googlesource.com/libyuv/libyuv/+/2f87e9a7/source/row_common.cc#3537
const float multiplier = 1.9259299444e-34f * scale;
const float multiplier = F16_MULTIPLIER * scale;
uint16_t * pixelRowBase = (uint16_t *)rgb->pixels;
const uint32_t stride = rgb->rowBytes >> 1;
for (uint32_t j = 0; j < rgb->height; ++j) {
uint16_t * pixel = pixelRowBase;
for (uint32_t i = 0; i < rgb->width * channelCount; ++i, ++pixel) {
union
{
float f;
uint32_t u32;
} f16;
avifF16 f16;
f16.f = *pixel * multiplier;
*pixel = (uint16_t)(f16.u32 >> 13);
}
Expand Down Expand Up @@ -1714,3 +1732,99 @@ int avifFullToLimitedUV(uint32_t depth, int v)
}
return v;
}

static inline uint16_t avifFloatToF16(float v)
{
avifF16 f16;
f16.f = v * F16_MULTIPLIER;
return (uint16_t)(f16.u32 >> 13);
}

static inline float avifF16ToFloat(uint16_t v)
{
avifF16 f16;
f16.u32 = v << 13;
return f16.f / F16_MULTIPLIER;
}

void avifGetRGBAPixel(const avifRGBImage * src, uint32_t x, uint32_t y, const avifRGBColorSpaceInfo * info, float rgbaPixel[4])
{
assert(src != NULL);
assert(!src->isFloat || src->depth == 16);
assert(src->format != AVIF_RGB_FORMAT_RGB_565 || src->depth == 8);

const uint8_t * const srcPixel = &src->pixels[y * src->rowBytes + x * info->pixelBytes];
if (info->channelBytes > 1) {
uint16_t r = *((uint16_t *)(&srcPixel[info->offsetBytesR]));
uint16_t g = *((uint16_t *)(&srcPixel[info->offsetBytesG]));
uint16_t b = *((uint16_t *)(&srcPixel[info->offsetBytesB]));
uint16_t a = avifRGBFormatHasAlpha(src->format) ? *((uint16_t *)(&srcPixel[info->offsetBytesA])) : (uint16_t)info->maxChannel;
if (src->isFloat) {
rgbaPixel[0] = avifF16ToFloat(r);
rgbaPixel[1] = avifF16ToFloat(g);
rgbaPixel[2] = avifF16ToFloat(b);
rgbaPixel[3] = avifRGBFormatHasAlpha(src->format) ? avifF16ToFloat(a) : 1.0f;
} else {
rgbaPixel[0] = r / info->maxChannelF;
rgbaPixel[1] = g / info->maxChannelF;
rgbaPixel[2] = b / info->maxChannelF;
rgbaPixel[3] = a / info->maxChannelF;
}
} else {
if (src->format == AVIF_RGB_FORMAT_RGB_565) {
uint8_t r, g, b;
avifGetRGB565(&srcPixel[info->offsetBytesR], &r, &g, &b);
rgbaPixel[0] = r / info->maxChannelF;
rgbaPixel[1] = g / info->maxChannelF;
rgbaPixel[2] = b / info->maxChannelF;
rgbaPixel[3] = 1.0f;
} else {
rgbaPixel[0] = srcPixel[info->offsetBytesR] / info->maxChannelF;
rgbaPixel[1] = srcPixel[info->offsetBytesG] / info->maxChannelF;
rgbaPixel[2] = srcPixel[info->offsetBytesB] / info->maxChannelF;
rgbaPixel[3] = avifRGBFormatHasAlpha(src->format) ? (srcPixel[info->offsetBytesA] / info->maxChannelF) : 1.0f;
}
}
}

void avifSetRGBAPixel(const avifRGBImage * dst, uint32_t x, uint32_t y, const avifRGBColorSpaceInfo * info, const float rgbaPixel[4])
{
assert(dst != NULL);
assert(!dst->isFloat || dst->depth == 16);
assert(dst->format != AVIF_RGB_FORMAT_RGB_565 || dst->depth == 8);

uint8_t * const dstPixel = &dst->pixels[y * dst->rowBytes + x * info->pixelBytes];

uint8_t * const ptrR = &dstPixel[info->offsetBytesR];
uint8_t * const ptrG = &dstPixel[info->offsetBytesG];
uint8_t * const ptrB = &dstPixel[info->offsetBytesB];
uint8_t * const ptrA = avifRGBFormatHasAlpha(dst->format) ? &dstPixel[info->offsetBytesA] : NULL;
if (dst->depth > 8) {
if (dst->isFloat) {
*((uint16_t *)ptrR) = avifFloatToF16(rgbaPixel[0]);
*((uint16_t *)ptrG) = avifFloatToF16(rgbaPixel[1]);
*((uint16_t *)ptrB) = avifFloatToF16(rgbaPixel[2]);
if (ptrA) {
*((uint16_t *)ptrA) = avifFloatToF16(rgbaPixel[3]);
}
} else {
*((uint16_t *)ptrR) = (uint16_t)(0.5f + (rgbaPixel[0] * info->maxChannelF));
*((uint16_t *)ptrG) = (uint16_t)(0.5f + (rgbaPixel[1] * info->maxChannelF));
*((uint16_t *)ptrB) = (uint16_t)(0.5f + (rgbaPixel[2] * info->maxChannelF));
if (ptrA) {
*((uint16_t *)ptrA) = (uint16_t)(0.5f + (rgbaPixel[3] * info->maxChannelF));
}
}
} else {
avifStoreRGB8Pixel(dst->format,
(uint8_t)(0.5f + (rgbaPixel[0] * info->maxChannelF)),
(uint8_t)(0.5f + (rgbaPixel[1] * info->maxChannelF)),
(uint8_t)(0.5f + (rgbaPixel[2] * info->maxChannelF)),
ptrR,
ptrG,
ptrB);
if (ptrA) {
*ptrA = (uint8_t)(0.5f + (rgbaPixel[3] * info->maxChannelF));
}
}
}
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ if(AVIF_ENABLE_GTEST)
add_avif_gtest_with_data(avifpng16bittest)
add_avif_gtest(avifprogressivetest)
add_avif_gtest_with_data(avifreadimagetest)
add_avif_gtest(avifrgbtest)
add_avif_gtest(avifrgbtoyuvtest)
add_avif_gtest_with_data(avifscaletest)
add_avif_gtest(avifstreamtest)
Expand Down
107 changes: 107 additions & 0 deletions tests/gtest/avifrgbtest.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// Copyright 2023 Google LLC
// SPDX-License-Identifier: BSD-2-Clause

#include <tuple>

#include "avif/internal.h"
#include "aviftest_helpers.h"
#include "gtest/gtest.h"

using ::testing::Combine;
using ::testing::Values;

namespace libavif {
namespace {

class SetGetRGBATest
: public testing::TestWithParam<std::tuple<
/*rgb_depth=*/int, avifRGBFormat, /*is_float=*/bool>> {};

TEST_P(SetGetRGBATest, SetGetTest) {
const int rgb_depth = std::get<0>(GetParam());
const avifRGBFormat rgb_format = std::get<1>(GetParam());
const bool is_float = std::get<2>(GetParam());

// Unused yuv image, simply needed to initialize the rgb image.
testutil::AvifImagePtr yuv(
avifImageCreate(/*width=*/13, /*height=*/17, 8, AVIF_PIXEL_FORMAT_YUV444),
avifImageDestroy);

testutil::AvifRgbImage rgb(yuv.get(), rgb_depth, rgb_format);
rgb.isFloat = is_float;

avifRGBColorSpaceInfo color_space;
ASSERT_TRUE(avifGetRGBColorSpaceInfo(&rgb, &color_space));

float epsilon = 1.0f / color_space.maxChannelF;
if (rgb_format == AVIF_RGB_FORMAT_RGB_565) {
// Only 5 bits of information per channel except G which has 6.
epsilon = 1.0f / (1 << 5);
} else if (rgb.isFloat) {
epsilon = 0.0005f; // Half precision floats are not that precise.
}

float pixel_read[4];
for (uint32_t j = 0; j < rgb.height; ++j) {
for (uint32_t i = 0; i < rgb.width; ++i) {
// Generate some arbitrary pixel values.
const float pixel_to_write[4] = {
0.0f + static_cast<float>(i) / rgb.width,
0.5f + static_cast<float>(j) / (rgb.height * 2),
1.0f - static_cast<float>(i + j) / ((rgb.width + rgb.height) * 2),
1.0f - static_cast<float>(i) / rgb.width};

avifSetRGBAPixel(&rgb, i, j, &color_space, pixel_to_write);
avifGetRGBAPixel(&rgb, i, j, &color_space, pixel_read);
EXPECT_NEAR(pixel_read[0], pixel_to_write[0], epsilon);
EXPECT_NEAR(pixel_read[1], pixel_to_write[1], epsilon);
EXPECT_NEAR(pixel_read[2], pixel_to_write[2], epsilon);
if (avifRGBFormatHasAlpha(rgb_format)) {
EXPECT_NEAR(pixel_read[3], pixel_to_write[3], epsilon);
} else {
EXPECT_EQ(pixel_read[3], 1.0f);
}
}
}

// Check that 0 maps to 0 and 1.0f maps to 1.0f.
const float pixel_zero[4] = {0.0f, 0.0f, 0.0f, 1.0f};
avifSetRGBAPixel(&rgb, 0, 0, &color_space, pixel_zero);
avifGetRGBAPixel(&rgb, 0, 0, &color_space, pixel_read);
EXPECT_EQ(pixel_read[0], pixel_zero[0]);
EXPECT_EQ(pixel_read[1], pixel_zero[1]);
EXPECT_EQ(pixel_read[2], pixel_zero[2]);
EXPECT_EQ(pixel_read[3], pixel_zero[3]);

const float pixel_one[4] = {1.0f, 1.0f, 1.0f, 1.0f};
avifSetRGBAPixel(&rgb, 0, 0, &color_space, pixel_one);
avifGetRGBAPixel(&rgb, 0, 0, &color_space, pixel_read);
EXPECT_EQ(pixel_read[0], pixel_one[0]);
EXPECT_EQ(pixel_read[1], pixel_one[1]);
EXPECT_EQ(pixel_read[2], pixel_one[2]);
EXPECT_EQ(pixel_read[3], pixel_one[3]);
}

INSTANTIATE_TEST_SUITE_P(
NonFloatNonRgb565, SetGetRGBATest,
Combine(/*rgb_depth=*/Values(8, 10, 12, 16),
Values(AVIF_RGB_FORMAT_RGB, AVIF_RGB_FORMAT_RGBA,
AVIF_RGB_FORMAT_ARGB, AVIF_RGB_FORMAT_BGR,
AVIF_RGB_FORMAT_BGRA, AVIF_RGB_FORMAT_ABGR),
/*is_float=*/Values(false)));

INSTANTIATE_TEST_SUITE_P(Rgb565, SetGetRGBATest,
Combine(/*rgb_depth=*/Values(8),
Values(AVIF_RGB_FORMAT_RGB_565),
/*is_float=*/Values(false)));

INSTANTIATE_TEST_SUITE_P(
Float, SetGetRGBATest,
Combine(/*rgb_depth=*/Values(16),
Values(AVIF_RGB_FORMAT_RGB, AVIF_RGB_FORMAT_RGBA,
AVIF_RGB_FORMAT_ARGB, AVIF_RGB_FORMAT_BGR,
AVIF_RGB_FORMAT_BGRA, AVIF_RGB_FORMAT_ABGR),
/*is_float=*/Values(true)));

} // namespace
} // namespace libavif
2 changes: 1 addition & 1 deletion tests/gtest/avifrgbtoyuvtest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
#include <memory>
#include <tuple>

#include "avif/avif.h"
#include "avif/internal.h"
#include "aviftest_helpers.h"
#include "gtest/gtest.h"

Expand Down

0 comments on commit 163ee01

Please sign in to comment.