Skip to content

Commit

Permalink
Op_RGB24_32_to_YCbCr: move chroma 4:2:0 sampling position to center (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
farindk committed Jul 12, 2021
1 parent 9453b49 commit 2c8d963
Showing 1 changed file with 150 additions and 16 deletions.
166 changes: 150 additions & 16 deletions libheif/heif_colorconversion.cc
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ using namespace heif;
#define DEBUG_ME 0
#define DEBUG_PIPELINE_CREATION 0

#define USE_CENTER_CHROMA_422 0


std::ostream& operator<<(std::ostream& ostr, heif_colorspace c)
{
switch (c) {
Expand Down Expand Up @@ -1944,6 +1947,25 @@ static inline uint8_t clip_f_u8(float fx)
}


inline void set_chroma_pixels(uint8_t* out_cb, uint8_t* out_cr,
uint8_t r, uint8_t g, uint8_t b,
const RGB_to_YCbCr_coefficients& coeffs,
bool full_range_flag)
{
float cb = r * coeffs.c[1][0] + g * coeffs.c[1][1] + b * coeffs.c[1][2];
float cr = r * coeffs.c[2][0] + g * coeffs.c[2][1] + b * coeffs.c[2][2];

if (full_range_flag) {
*out_cb = clip_f_u8(cb + 128);
*out_cr = clip_f_u8(cr + 128);
}
else {
*out_cb = (uint8_t) clip_f_u8(cb * 0.875f + 128.0f);
*out_cr = (uint8_t) clip_f_u8(cr * 0.875f + 128.0f);
}
}


std::shared_ptr<HeifPixelImage>
Op_RGB24_32_to_YCbCr::convert_colorspace(const std::shared_ptr<const HeifPixelImage>& input,
ColorState target_state,
Expand Down Expand Up @@ -2024,30 +2046,142 @@ Op_RGB24_32_to_YCbCr::convert_colorspace(const std::shared_ptr<const HeifPixelIm
}
}

for (int y = 0; y < height; y += chromaSubV) {
const uint8_t* p = &in_p[y * in_stride];
if (chromaSubH == 1 && chromaSubV == 1) {
// chroma 4:4:4

for (int x = 0; x < width; x += chromaSubH) {
uint8_t r = p[0];
uint8_t g = p[1];
uint8_t b = p[2];
p += bytes_per_pixel * chromaSubH;
for (int y = 0; y < height; y++) {
const uint8_t* p = &in_p[y * in_stride];

float cb = r * coeffs.c[1][0] + g * coeffs.c[1][1] + b * coeffs.c[1][2];
float cr = r * coeffs.c[2][0] + g * coeffs.c[2][1] + b * coeffs.c[2][2];
for (int x = 0; x < width; x++) {
uint8_t r = p[0];
uint8_t g = p[1];
uint8_t b = p[2];
p += bytes_per_pixel;

set_chroma_pixels(out_cb + y * out_cb_stride + x,
out_cr + y * out_cr_stride + x,
r, g, b,
coeffs, full_range_flag);
}
}
}
else if (chromaSubH == 2 && chromaSubV == 2) {
// chroma 4:2:0

if (full_range_flag) {
out_cb[(y / chromaSubV) * out_cb_stride + (x / chromaSubH)] = clip_f_u8(cb + 128);
out_cr[(y / chromaSubV) * out_cr_stride + (x / chromaSubH)] = clip_f_u8(cr + 128);
for (int y = 0; y < (height & ~1); y += 2) {
const uint8_t* p = &in_p[y * in_stride];

for (int x = 0; x < (width & ~1); x += 2) {
uint8_t r = uint8_t((p[0] + p[bytes_per_pixel + 0] + p[in_stride + 0] + p[bytes_per_pixel + in_stride + 0]) / 4);
uint8_t g = uint8_t((p[1] + p[bytes_per_pixel + 1] + p[in_stride + 1] + p[bytes_per_pixel + in_stride + 1]) / 4);
uint8_t b = uint8_t((p[2] + p[bytes_per_pixel + 2] + p[in_stride + 2] + p[bytes_per_pixel + in_stride + 2]) / 4);

p += bytes_per_pixel * 2;

set_chroma_pixels(out_cb + (y / 2) * out_cb_stride + (x / 2),
out_cr + (y / 2) * out_cr_stride + (x / 2),
r, g, b,
coeffs, full_range_flag);
}
else {
out_cb[(y / chromaSubV) * out_cb_stride + (x / chromaSubH)] = (uint8_t) clip_f_u8(cb * 0.875f + 128.0f);
out_cr[(y / chromaSubV) * out_cr_stride + (x / chromaSubH)] = (uint8_t) clip_f_u8(cr * 0.875f + 128.0f);
}

// 4:2:0 right column (if odd width)
if (width & 1) {
int x = width - 1;
const uint8_t* p = &in_p[x * bytes_per_pixel];

for (int y = 0; y < height; y += 2) {
uint8_t r, g, b;
if (y + 1 < height) {
r = uint8_t((p[0] + p[in_stride + 0]) / 2);
g = uint8_t((p[1] + p[in_stride + 1]) / 2);
b = uint8_t((p[2] + p[in_stride + 2]) / 2);
}
else {
r = p[0];
g = p[1];
b = p[2];
}

set_chroma_pixels(out_cb + (y / 2) * out_cb_stride + (x / 2),
out_cr + (y / 2) * out_cr_stride + (x / 2),
r, g, b,
coeffs, full_range_flag);

p += in_stride * 2;
}
}

// 4:2:0 bottom row (if odd height)
if (height & 1) {
int y = height - 1;
const uint8_t* p = &in_p[y * in_stride];

for (int x = 0; x < width; x += 2) {
uint8_t r, g, b;
if (x + 1 < width) {
r = uint8_t((p[0] + p[bytes_per_pixel + 0]) / 2);
g = uint8_t((p[1] + p[bytes_per_pixel + 1]) / 2);
b = uint8_t((p[2] + p[bytes_per_pixel + 2]) / 2);
}
else {
r = p[0];
g = p[1];
b = p[2];
}

set_chroma_pixels(out_cb + (y / 2) * out_cb_stride + (x / 2),
out_cr + (y / 2) * out_cr_stride + (x / 2),
r, g, b,
coeffs, full_range_flag);

p += bytes_per_pixel * 2;
}
}
}
else if (chromaSubH == 2 && chromaSubV == 1) {
// chroma 4:2:2

for (int y = 0; y < height; y++) {
const uint8_t* p = &in_p[y * in_stride];

for (int x = 0; x < width; x += 2) {
uint8_t r, g, b;

// TODO: it still is an open question where the 'correct' chroma sample positions are for 4:2:2
// Since 4:2:2 is primarily used for video content and as there is no way to signal center position for h.265,
// we currently use left-aligned sampling. See the discussion here: https://github.com/strukturag/libheif/issues/521
#if USE_CENTER_CHROMA_422
if (x + 1 < width) {
r = uint8_t((p[0] + p[bytes_per_pixel + 0]) / 2);
g = uint8_t((p[1] + p[bytes_per_pixel + 1]) / 2);
b = uint8_t((p[2] + p[bytes_per_pixel + 2]) / 2);
}
else {
r = p[0];
g = p[1];
b = p[2];
}
#else
r = p[0];
g = p[1];
b = p[2];
#endif

p += bytes_per_pixel * 2;

set_chroma_pixels(out_cb + y * out_cb_stride + (x / 2),
out_cr + y * out_cr_stride + (x / 2),
r, g, b,
coeffs, full_range_flag);
}
}
}


if (has_alpha) {
assert(bytes_per_pixel == 4);

for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
uint8_t a = in_p[y * in_stride + x * 4 + 3];
Expand Down Expand Up @@ -2460,7 +2594,7 @@ Op_to_sdr_planes::convert_colorspace(const std::shared_ptr<const HeifPixelImage>
if (input->has_channel(channel)) {
int input_bits = input->get_bits_per_pixel(channel);

if (input_bits>8) {
if (input_bits > 8) {
int width = input->get_width(channel);
int height = input->get_height(channel);
outimg->add_plane(channel, width, height, 8);
Expand Down

0 comments on commit 2c8d963

Please sign in to comment.