libheif/color-conversion/rgb2yuv.cc (647 lines of code) (raw):

/* * HEIF codec. * Copyright (c) 2023 Dirk Farin <dirk.farin@gmail.com> * * This file is part of libheif. * * libheif is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * * libheif is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with libheif. If not, see <http://www.gnu.org/licenses/>. */ #include <cstring> #include <cassert> #include <memory> #include <vector> #include "rgb2yuv.h" #include "nclx.h" #include "common_utils.h" template<class Pixel> std::vector<ColorStateWithCost> Op_RGB_to_YCbCr<Pixel>::state_after_conversion(const ColorState& input_state, const ColorState& target_state, const heif_color_conversion_options& options) const { bool hdr = !std::is_same<Pixel, uint8_t>::value; if ((input_state.bits_per_pixel != 8) != hdr) { return {}; } if (input_state.colorspace != heif_colorspace_RGB || input_state.chroma != heif_chroma_444) { return {}; } int matrix = target_state.nclx_profile.get_matrix_coefficients(); if (matrix == 8 || matrix == 11 || matrix == 14) { return {}; } std::vector<ColorStateWithCost> states; ColorState output_state; // --- convert to YCbCr // This Op only implements the nearest-neighbor downsampling algorithm, but it can still convert to 4:4:4. if (target_state.chroma != heif_chroma_444 && (options.preferred_chroma_downsampling_algorithm == heif_chroma_downsampling_nearest_neighbor || !options.only_use_preferred_chroma_algorithm)) { output_state.colorspace = heif_colorspace_YCbCr; output_state.chroma = target_state.chroma; output_state.has_alpha = input_state.has_alpha; // we simply keep the old alpha plane output_state.bits_per_pixel = input_state.bits_per_pixel; output_state.nclx_profile = target_state.nclx_profile; states.push_back({output_state, SpeedCosts_Unoptimized}); } else { // --- convert to YCbCr 4:4:4 output_state.colorspace = heif_colorspace_YCbCr; output_state.chroma = heif_chroma_444; output_state.has_alpha = input_state.has_alpha; // we simply keep the old alpha plane output_state.bits_per_pixel = input_state.bits_per_pixel; output_state.nclx_profile = target_state.nclx_profile; states.push_back({output_state, SpeedCosts_Unoptimized}); } return states; } template<class Pixel> std::shared_ptr<HeifPixelImage> Op_RGB_to_YCbCr<Pixel>::convert_colorspace(const std::shared_ptr<const HeifPixelImage>& input, const ColorState& input_state, const ColorState& target_state, const heif_color_conversion_options& options) const { bool hdr = !std::is_same<Pixel, uint8_t>::value; int width = input->get_width(); int height = input->get_height(); heif_chroma chroma = target_state.chroma; int subH = chroma_h_subsampling(chroma); int subV = chroma_v_subsampling(chroma); int bpp = input->get_bits_per_pixel(heif_channel_R); if ((bpp != 8) != hdr) { return nullptr; } bool has_alpha = input->has_channel(heif_channel_Alpha); if (has_alpha && input->get_bits_per_pixel(heif_channel_Alpha) != bpp) { return nullptr; } auto outimg = std::make_shared<HeifPixelImage>(); outimg->create(width, height, heif_colorspace_YCbCr, chroma); int cwidth = (width + subH - 1) / subH; int cheight = (height + subV - 1) / subV; if (!outimg->add_plane(heif_channel_Y, width, height, bpp) || !outimg->add_plane(heif_channel_Cb, cwidth, cheight, bpp) || !outimg->add_plane(heif_channel_Cr, cwidth, cheight, bpp)) { return nullptr; } if (has_alpha) { if (!outimg->add_plane(heif_channel_Alpha, width, height, bpp)) { return nullptr; } } const Pixel* in_r, * in_g, * in_b, * in_a; int in_r_stride = 0, in_g_stride = 0, in_b_stride = 0, in_a_stride = 0; Pixel* out_y, * out_cb, * out_cr, * out_a; int out_y_stride = 0, out_cb_stride = 0, out_cr_stride = 0, out_a_stride = 0; in_r = (const Pixel*) input->get_plane(heif_channel_R, &in_r_stride); in_g = (const Pixel*) input->get_plane(heif_channel_G, &in_g_stride); in_b = (const Pixel*) input->get_plane(heif_channel_B, &in_b_stride); out_y = (Pixel*) outimg->get_plane(heif_channel_Y, &out_y_stride); out_cb = (Pixel*) outimg->get_plane(heif_channel_Cb, &out_cb_stride); out_cr = (Pixel*) outimg->get_plane(heif_channel_Cr, &out_cr_stride); if (has_alpha) { in_a = (const Pixel*) input->get_plane(heif_channel_Alpha, &in_a_stride); out_a = (Pixel*) outimg->get_plane(heif_channel_Alpha, &out_a_stride); } else { in_a = nullptr; out_a = nullptr; } if (hdr) { in_r_stride /= 2; in_g_stride /= 2; in_b_stride /= 2; in_a_stride /= 2; out_y_stride /= 2; out_cb_stride /= 2; out_cr_stride /= 2; out_a_stride /= 2; } uint16_t halfRange = (uint16_t) (1 << (bpp - 1)); int32_t fullRange = (1 << bpp) - 1; float limited_range_offset = static_cast<float>(16 << (bpp - 8)); int matrix_coeffs = 2; RGB_to_YCbCr_coefficients coeffs = RGB_to_YCbCr_coefficients::defaults(); bool full_range_flag = true; full_range_flag = target_state.nclx_profile.get_full_range_flag(); matrix_coeffs = target_state.nclx_profile.get_matrix_coefficients(); coeffs = get_RGB_to_YCbCr_coefficients(target_state.nclx_profile.get_matrix_coefficients(), target_state.nclx_profile.get_colour_primaries()); int x, y; for (y = 0; y < height; y++) { for (x = 0; x < width; x++) { if (matrix_coeffs == 0) { if (full_range_flag) { out_y[y * out_y_stride + x] = in_g[y * in_g_stride + x]; } else { float v = (((in_g[y * in_g_stride + x] * 219.0f) / 256) + limited_range_offset); out_y[y * out_y_stride + x] = (Pixel) clip_f_u16(v, fullRange); } } else { float r = in_r[y * in_r_stride + x]; float g = in_g[y * in_g_stride + x]; float b = in_b[y * in_b_stride + x]; float v = r * coeffs.c[0][0] + g * coeffs.c[0][1] + b * coeffs.c[0][2]; if (!full_range_flag) { v = (((v * 219) / 256) + limited_range_offset); } Pixel pix = (Pixel) clip_f_u16(v, fullRange); out_y[y * out_y_stride + x] = pix; } } } for (y = 0; y < height; y += subV) { for (x = 0; x < width; x += subH) { if (matrix_coeffs == 0) { if (full_range_flag) { out_cb[(y / subV) * out_cb_stride + (x / subH)] = in_b[y * in_b_stride + x]; out_cr[(y / subV) * out_cb_stride + (x / subH)] = in_r[y * in_b_stride + x]; } else { out_cb[(y / subV) * out_cb_stride + (x / subH)] = (Pixel) clip_f_u16( ((in_b[y * in_b_stride + x] * 224.0f) / 256) + limited_range_offset, fullRange); out_cr[(y / subV) * out_cb_stride + (x / subH)] = (Pixel) clip_f_u16( ((in_r[y * in_b_stride + x] * 224.0f) / 256) + limited_range_offset, fullRange); } } else { float r = in_r[y * in_r_stride + x]; float g = in_g[y * in_g_stride + x]; float b = in_b[y * in_b_stride + x]; if (subH > 1 || subV > 1) { int x2 = (x + 1 < width && subH == 2 && subV == 2) ? x + 1 : x; // subV==2 -> Do not center for 4:2:2 (see comment in Op_RGB24_32_to_YCbCr, github issue #521) int y2 = (y + 1 < height && subV == 2) ? y + 1 : y; r += in_r[y * in_r_stride + x2]; g += in_g[y * in_g_stride + x2]; b += in_b[y * in_b_stride + x2]; r += in_r[y2 * in_r_stride + x]; g += in_g[y2 * in_g_stride + x]; b += in_b[y2 * in_b_stride + x]; r += in_r[y2 * in_r_stride + x2]; g += in_g[y2 * in_g_stride + x2]; b += in_b[y2 * in_b_stride + x2]; r *= 0.25f; g *= 0.25f; b *= 0.25f; } float cb, cr; cb = r * coeffs.c[1][0] + g * coeffs.c[1][1] + b * coeffs.c[1][2]; cr = r * coeffs.c[2][0] + g * coeffs.c[2][1] + b * coeffs.c[2][2]; if (!full_range_flag) { cb = (cb * 224) / 256; cr = (cr * 224) / 256; } out_cb[(y / subV) * out_cb_stride + (x / subH)] = (Pixel) clip_f_u16(cb + halfRange, fullRange); out_cr[(y / subV) * out_cr_stride + (x / subH)] = (Pixel) clip_f_u16(cr + halfRange, fullRange); } } } if (has_alpha) { int copyWidth = (hdr ? width * 2 : width); for (y = 0; y < height; y++) { memcpy(&out_a[y * out_a_stride], &in_a[y * in_a_stride], copyWidth); } } return outimg; } template class Op_RGB_to_YCbCr<uint8_t>; template class Op_RGB_to_YCbCr<uint16_t>; std::vector<ColorStateWithCost> Op_RRGGBBxx_HDR_to_YCbCr420::state_after_conversion(const ColorState& input_state, const ColorState& target_state, const heif_color_conversion_options& options) const { // this Op only implements the nearest-neighbor algorithm if (target_state.chroma != heif_chroma_444) { if (options.preferred_chroma_downsampling_algorithm != heif_chroma_downsampling_nearest_neighbor && options.only_use_preferred_chroma_algorithm) { return {}; } } if (input_state.colorspace != heif_colorspace_RGB || !(input_state.chroma == heif_chroma_interleaved_RRGGBB_BE || input_state.chroma == heif_chroma_interleaved_RRGGBB_LE || input_state.chroma == heif_chroma_interleaved_RRGGBBAA_BE || input_state.chroma == heif_chroma_interleaved_RRGGBBAA_LE) || input_state.bits_per_pixel == 8) { return {}; } int matrix = target_state.nclx_profile.get_matrix_coefficients(); if (matrix == 0 || matrix == 8 || matrix == 11 || matrix == 14) { return {}; } if (!target_state.nclx_profile.get_full_range_flag()) { return {}; } if (target_state.chroma != heif_chroma_420) { return {}; } std::vector<ColorStateWithCost> states; ColorState output_state; // --- convert to YCbCr output_state.colorspace = heif_colorspace_YCbCr; output_state.chroma = heif_chroma_420; output_state.has_alpha = input_state.has_alpha; // we generate an alpha plane if the source contains data output_state.bits_per_pixel = input_state.bits_per_pixel; output_state.nclx_profile = target_state.nclx_profile; states.push_back({output_state, SpeedCosts_Unoptimized}); return states; } std::shared_ptr<HeifPixelImage> Op_RRGGBBxx_HDR_to_YCbCr420::convert_colorspace(const std::shared_ptr<const HeifPixelImage>& input, const ColorState& input_state, const ColorState& target_state, const heif_color_conversion_options& options) const { int width = input->get_width(); int height = input->get_height(); int bpp = input->get_bits_per_pixel(heif_channel_interleaved); bool has_alpha = (input->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_BE || input->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_LE); auto outimg = std::make_shared<HeifPixelImage>(); outimg->create(width, height, heif_colorspace_YCbCr, heif_chroma_420); int bytesPerPixel = has_alpha ? 8 : 6; int cwidth = (width + 1) / 2; int cheight = (height + 1) / 2; if (!outimg->add_plane(heif_channel_Y, width, height, bpp) || !outimg->add_plane(heif_channel_Cb, cwidth, cheight, bpp) || !outimg->add_plane(heif_channel_Cr, cwidth, cheight, bpp)) { return nullptr; } if (has_alpha) { if (!outimg->add_plane(heif_channel_Alpha, width, height, bpp)) { return nullptr; } } const uint8_t* in_p; int in_p_stride = 0; uint16_t* out_y, * out_cb, * out_cr, * out_a = nullptr; int out_y_stride = 0, out_cb_stride = 0, out_cr_stride = 0, out_a_stride = 0; in_p = input->get_plane(heif_channel_interleaved, &in_p_stride); out_y = (uint16_t*) outimg->get_plane(heif_channel_Y, &out_y_stride); out_cb = (uint16_t*) outimg->get_plane(heif_channel_Cb, &out_cb_stride); out_cr = (uint16_t*) outimg->get_plane(heif_channel_Cr, &out_cr_stride); if (has_alpha) { out_a = (uint16_t*) outimg->get_plane(heif_channel_Alpha, &out_a_stride); } // adapt stride as we are pointing to 16bit integers out_y_stride /= 2; out_cb_stride /= 2; out_cr_stride /= 2; out_a_stride /= 2; uint16_t halfRange = (uint16_t) (1 << (bpp - 1)); int32_t fullRange = (1 << bpp) - 1; float limited_range_offset = static_cast<float>(16 << (bpp - 8)); // le=1 for little endian, le=0 for big endian int le = (input->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_LE || input->get_chroma_format() == heif_chroma_interleaved_RRGGBB_LE) ? 1 : 0; bool full_range_flag = target_state.nclx_profile.get_full_range_flag(); RGB_to_YCbCr_coefficients coeffs = get_RGB_to_YCbCr_coefficients( target_state.nclx_profile.get_matrix_coefficients(), target_state.nclx_profile.get_colour_primaries()); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { const uint8_t* in = &in_p[y * in_p_stride + bytesPerPixel * x]; float r = static_cast<float>((in[0 + le] << 8) | in[1 - le]); float g = static_cast<float>((in[2 + le] << 8) | in[3 - le]); float b = static_cast<float>((in[4 + le] << 8) | in[5 - le]); float v = r * coeffs.c[0][0] + g * coeffs.c[0][1] + b * coeffs.c[0][2]; if (!full_range_flag) { v = v * 0.85547f + limited_range_offset; // 0.85547 = 219/256 } out_y[y * out_y_stride + x] = clip_f_u16(v, fullRange); if (has_alpha) { uint16_t a = (uint16_t) ((in[6 + le] << 8) | in[7 - le]); out_a[y * out_a_stride + x] = a; } } } for (int y = 0; y < height; y += 2) { for (int x = 0; x < width; x += 2) { const uint8_t* in = &in_p[y * in_p_stride + bytesPerPixel * x]; float r = static_cast<float>((in[0 + le] << 8) | in[1 - le]); float g = static_cast<float>((in[2 + le] << 8) | in[3 - le]); float b = static_cast<float>((in[4 + le] << 8) | in[5 - le]); int dx = (x + 1 < width) ? bytesPerPixel : 0; int dy = (y + 1 < height) ? in_p_stride : 0; r += static_cast<float>((in[0 + le + dx] << 8) | in[1 - le + dx]); g += static_cast<float>((in[2 + le + dx] << 8) | in[3 - le + dx]); b += static_cast<float>((in[4 + le + dx] << 8) | in[5 - le + dx]); r += static_cast<float>((in[0 + le + dy] << 8) | in[1 - le + dy]); g += static_cast<float>((in[2 + le + dy] << 8) | in[3 - le + dy]); b += static_cast<float>((in[4 + le + dy] << 8) | in[5 - le + dy]); r += static_cast<float>((in[0 + le + dx + dy] << 8) | in[1 - le + dx + dy]); g += static_cast<float>((in[2 + le + dx + dy] << 8) | in[3 - le + dx + dy]); b += static_cast<float>((in[4 + le + dx + dy] << 8) | in[5 - le + dx + dy]); r *= 0.25f; g *= 0.25f; b *= 0.25f; 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) { cb = cb * 0.8750f; // 0.8750 = 224/256 cr = cr * 0.8750f; // 0.8750 = 224/256 } out_cb[(y / 2) * out_cb_stride + (x / 2)] = clip_f_u16(halfRange + cb, fullRange); out_cr[(y / 2) * out_cr_stride + (x / 2)] = clip_f_u16(halfRange + cr, fullRange); } } return outimg; } std::vector<ColorStateWithCost> Op_RGB24_32_to_YCbCr::state_after_conversion(const ColorState& input_state, const ColorState& target_state, const heif_color_conversion_options& options) const { // this Op only implements the nearest-neighbor algorithm if (target_state.chroma != heif_chroma_444) { if (options.preferred_chroma_downsampling_algorithm != heif_chroma_downsampling_nearest_neighbor && options.only_use_preferred_chroma_algorithm) { return {}; } } // Note: no input alpha channel required. It will be filled up with 0xFF. if (input_state.colorspace != heif_colorspace_RGB || (input_state.chroma != heif_chroma_interleaved_RGB && input_state.chroma != heif_chroma_interleaved_RGBA)) { return {}; } if (target_state.chroma != heif_chroma_420 && target_state.chroma != heif_chroma_422 && target_state.chroma != heif_chroma_444) { return {}; } int matrix = target_state.nclx_profile.get_matrix_coefficients(); if (matrix == 0 || matrix == 8 || matrix == 11 || matrix == 14) { return {}; } std::vector<ColorStateWithCost> states; ColorState output_state; output_state.colorspace = heif_colorspace_YCbCr; output_state.chroma = target_state.chroma; output_state.has_alpha = target_state.has_alpha; output_state.bits_per_pixel = 8; output_state.nclx_profile = target_state.nclx_profile; states.push_back({output_state, SpeedCosts_Unoptimized}); return states; } 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, const ColorState& input_state, const ColorState& target_state, const heif_color_conversion_options& options) const { int width = input->get_width(); int height = input->get_height(); auto outimg = std::make_shared<HeifPixelImage>(); auto chroma = target_state.chroma; uint8_t chromaSubH = chroma_h_subsampling(chroma); uint8_t chromaSubV = chroma_v_subsampling(chroma); outimg->create(width, height, heif_colorspace_YCbCr, chroma); int chroma_width = (width + chromaSubH - 1) / chromaSubH; int chroma_height = (height + chromaSubV - 1) / chromaSubV; const bool has_alpha = (input->get_chroma_format() == heif_chroma_interleaved_32bit); const bool want_alpha = target_state.has_alpha; if (!outimg->add_plane(heif_channel_Y, width, height, 8) || !outimg->add_plane(heif_channel_Cb, chroma_width, chroma_height, 8) || !outimg->add_plane(heif_channel_Cr, chroma_width, chroma_height, 8)) { return nullptr; } if (want_alpha) { if (!outimg->add_plane(heif_channel_Alpha, width, height, 8)) { return nullptr; } } uint8_t* out_cb, * out_cr, * out_y, * out_a; int out_cb_stride = 0, out_cr_stride = 0, out_y_stride = 0, out_a_stride = 0; const uint8_t* in_p; int in_stride = 0; in_p = input->get_plane(heif_channel_interleaved, &in_stride); out_y = outimg->get_plane(heif_channel_Y, &out_y_stride); out_cb = outimg->get_plane(heif_channel_Cb, &out_cb_stride); out_cr = outimg->get_plane(heif_channel_Cr, &out_cr_stride); if (want_alpha) { out_a = outimg->get_plane(heif_channel_Alpha, &out_a_stride); } else { out_a = nullptr; } RGB_to_YCbCr_coefficients coeffs = RGB_to_YCbCr_coefficients::defaults(); bool full_range_flag = true; full_range_flag = target_state.nclx_profile.get_full_range_flag(); coeffs = get_RGB_to_YCbCr_coefficients(target_state.nclx_profile.get_matrix_coefficients(), target_state.nclx_profile.get_colour_primaries()); int bytes_per_pixel = (has_alpha ? 4 : 3); for (int y = 0; y < height; y++) { const uint8_t* p = &in_p[y * in_stride]; 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; float yv = r * coeffs.c[0][0] + g * coeffs.c[0][1] + b * coeffs.c[0][2]; if (full_range_flag) { out_y[y * out_y_stride + x] = clip_f_u8(yv); } else { out_y[y * out_y_stride + x] = (uint8_t) (clip_f_u16(yv * 0.85547f, 219) + 16); } } } if (chromaSubH == 1 && chromaSubV == 1) { // chroma 4:4:4 for (int y = 0; y < height; y++) { const uint8_t* p = &in_p[y * in_stride]; 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 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); } } // 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 (want_alpha) { 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 = has_alpha ? in_p[y * in_stride + x * 4 + 3] : 0xff; // alpha out_a[y * out_a_stride + x] = a; } } } return outimg; } std::vector<ColorStateWithCost> Op_RGB24_32_to_YCbCr444_GBR::state_after_conversion(const ColorState& input_state, const ColorState& target_state, const heif_color_conversion_options& options) const { // Note: no input alpha channel required. It will be filled up with 0xFF. if (input_state.colorspace != heif_colorspace_RGB || (input_state.chroma != heif_chroma_interleaved_RGB && input_state.chroma != heif_chroma_interleaved_RGBA)) { return {}; } if (target_state.nclx_profile.get_matrix_coefficients() != 0) { return {}; } if (!target_state.nclx_profile.get_full_range_flag()) { return {}; } std::vector<ColorStateWithCost> states; ColorState output_state; output_state.colorspace = heif_colorspace_YCbCr; output_state.chroma = heif_chroma_444; output_state.has_alpha = target_state.has_alpha; output_state.bits_per_pixel = 8; output_state.nclx_profile = target_state.nclx_profile; states.push_back({output_state, SpeedCosts_Unoptimized}); return states; } std::shared_ptr<HeifPixelImage> Op_RGB24_32_to_YCbCr444_GBR::convert_colorspace(const std::shared_ptr<const HeifPixelImage>& input, const ColorState& input_state, const ColorState& target_state, const heif_color_conversion_options& options) const { int width = input->get_width(); int height = input->get_height(); auto outimg = std::make_shared<HeifPixelImage>(); outimg->create(width, height, heif_colorspace_YCbCr, heif_chroma_444); const bool has_alpha = (input->get_chroma_format() == heif_chroma_interleaved_32bit); const bool want_alpha = target_state.has_alpha; if (!outimg->add_plane(heif_channel_Y, width, height, 8) || !outimg->add_plane(heif_channel_Cb, width, height, 8) || !outimg->add_plane(heif_channel_Cr, width, height, 8)) { return nullptr; } if (want_alpha) { if (!outimg->add_plane(heif_channel_Alpha, width, height, 8)) { return nullptr; } } uint8_t* out_cb, * out_cr, * out_y, * out_a; int out_cb_stride = 0, out_cr_stride = 0, out_y_stride = 0, out_a_stride = 0; const uint8_t* in_p; int in_stride = 0; in_p = input->get_plane(heif_channel_interleaved, &in_stride); out_y = outimg->get_plane(heif_channel_Y, &out_y_stride); out_cb = outimg->get_plane(heif_channel_Cb, &out_cb_stride); out_cr = outimg->get_plane(heif_channel_Cr, &out_cr_stride); if (want_alpha) { out_a = outimg->get_plane(heif_channel_Alpha, &out_a_stride); } assert(target_state.nclx_profile.get_matrix_coefficients() == 0); int bytes_per_pixel = (has_alpha ? 4 : 3); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { uint8_t r = in_p[y * in_stride + x * bytes_per_pixel + 0]; uint8_t g = in_p[y * in_stride + x * bytes_per_pixel + 1]; uint8_t b = in_p[y * in_stride + x * bytes_per_pixel + 2]; out_y[y * out_y_stride + x] = g; out_cb[y * out_cb_stride + x] = b; out_cr[y * out_cr_stride + x] = r; if (want_alpha) { uint8_t a = has_alpha ? in_p[y * in_stride + x * bytes_per_pixel + 3] : 0xff; out_a[y * out_a_stride + x] = a; } } } return outimg; }