libheif/color-conversion/yuv2rgb.cc (971 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 <cmath>
#include <cstring>
#include "yuv2rgb.h"
#include "nclx.h"
#include "common_utils.h"
template<class Pixel>
std::vector<ColorStateWithCost>
Op_YCbCr_to_RGB<Pixel>::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 (input_state.chroma != heif_chroma_444) {
if (options.preferred_chroma_upsampling_algorithm != heif_chroma_upsampling_nearest_neighbor &&
options.only_use_preferred_chroma_algorithm) {
return {};
}
}
if (input_state.colorspace != heif_colorspace_YCbCr ||
(input_state.chroma != heif_chroma_444 &&
input_state.chroma != heif_chroma_422 &&
input_state.chroma != heif_chroma_420)) {
return {};
}
int matrix = input_state.nclx_profile.get_matrix_coefficients();
if ( matrix == 11 || matrix == 14) {
return {};
}
bool hdr = !std::is_same<Pixel, uint8_t>::value;
if ((input_state.bits_per_pixel != 8) != hdr) {
return {};
}
std::vector<ColorStateWithCost> states;
ColorState output_state;
// --- convert to RGB
output_state.colorspace = heif_colorspace_RGB;
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;
states.push_back({output_state, SpeedCosts_Unoptimized});
return states;
}
template<class Pixel>
std::shared_ptr<HeifPixelImage>
Op_YCbCr_to_RGB<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;
heif_chroma chroma = input->get_chroma_format();
int bpp_y = input->get_bits_per_pixel(heif_channel_Y);
int bpp_cb = input->get_bits_per_pixel(heif_channel_Cb);
int bpp_cr = input->get_bits_per_pixel(heif_channel_Cr);
int bpp_a = 0;
bool has_alpha = input->has_channel(heif_channel_Alpha);
if (has_alpha) {
bpp_a = input->get_bits_per_pixel(heif_channel_Alpha);
}
if (!hdr) {
if (bpp_y != 8 ||
bpp_cb != 8 ||
bpp_cr != 8) {
return nullptr;
}
}
else {
if (bpp_y == 8 ||
bpp_cb == 8 ||
bpp_cr == 8) {
return nullptr;
}
}
if (bpp_y != bpp_cb ||
bpp_y != bpp_cr) {
// TODO: test with varying bit depths when we have a test image
return nullptr;
}
auto colorProfile = input->get_color_profile_nclx();
int width = input->get_width();
int height = input->get_height();
auto outimg = std::make_shared<HeifPixelImage>();
outimg->create(width, height, heif_colorspace_RGB, heif_chroma_444);
if (!outimg->add_plane(heif_channel_R, width, height, bpp_y) ||
!outimg->add_plane(heif_channel_G, width, height, bpp_y) ||
!outimg->add_plane(heif_channel_B, width, height, bpp_y)) {
return nullptr;
}
if (has_alpha) {
if (!outimg->add_plane(heif_channel_Alpha, width, height, bpp_a)) {
return nullptr;
}
}
const Pixel* in_y, * in_cb, * in_cr, * in_a;
int in_y_stride = 0, in_cb_stride = 0, in_cr_stride = 0, in_a_stride = 0;
Pixel* out_r, * out_g, * out_b, * out_a;
int out_r_stride = 0, out_g_stride = 0, out_b_stride = 0, out_a_stride = 0;
in_y = (const Pixel*) input->get_plane(heif_channel_Y, &in_y_stride);
in_cb = (const Pixel*) input->get_plane(heif_channel_Cb, &in_cb_stride);
in_cr = (const Pixel*) input->get_plane(heif_channel_Cr, &in_cr_stride);
out_r = (Pixel*) outimg->get_plane(heif_channel_R, &out_r_stride);
out_g = (Pixel*) outimg->get_plane(heif_channel_G, &out_g_stride);
out_b = (Pixel*) outimg->get_plane(heif_channel_B, &out_b_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;
}
uint16_t halfRange = (uint16_t) (1 << (bpp_y - 1));
int32_t fullRange = (1 << bpp_y) - 1;
int limited_range_offset_int = 16 << (bpp_y - 8);
float limited_range_offset = static_cast<float>(limited_range_offset_int);
int shiftH = chroma_h_subsampling(chroma) - 1;
int shiftV = chroma_v_subsampling(chroma) - 1;
if (hdr) {
in_y_stride /= 2;
in_cb_stride /= 2;
in_cr_stride /= 2;
in_a_stride /= 2;
out_r_stride /= 2;
out_g_stride /= 2;
out_b_stride /= 2;
out_a_stride /= 2;
}
int matrix_coeffs = 2;
bool full_range_flag = true;
YCbCr_to_RGB_coefficients coeffs = YCbCr_to_RGB_coefficients::defaults();
if (colorProfile) {
matrix_coeffs = colorProfile->get_matrix_coefficients();
full_range_flag = colorProfile->get_full_range_flag();
coeffs = get_YCbCr_to_RGB_coefficients(colorProfile->get_matrix_coefficients(),
colorProfile->get_colour_primaries());
}
int x, y;
for (y = 0; y < height; y++) {
for (x = 0; x < width; x++) {
int cx = (x >> shiftH);
int cy = (y >> shiftV);
if (matrix_coeffs == 0) {
if (full_range_flag) {
out_r[y * out_r_stride + x] = in_cr[cy * in_cr_stride + cx];
out_g[y * out_g_stride + x] = in_y[y * in_y_stride + x];
out_b[y * out_b_stride + x] = in_cb[cy * in_cb_stride + cx];
}
else {
// Convert from limited range to full range.
out_r[y * out_r_stride + x] = (Pixel) clip_f_u16((in_cr[cy * in_cr_stride + cx] - limited_range_offset) * 1.1429f, fullRange);
out_g[y * out_g_stride + x] = (Pixel) clip_f_u16((in_y[y * in_y_stride + x] - limited_range_offset) * 1.1689f, fullRange);
out_b[y * out_b_stride + x] = (Pixel) clip_f_u16((in_cb[cy * in_cb_stride + cx] - limited_range_offset) * 1.1429f, fullRange);
}
}
else if (matrix_coeffs == 8) {
// TODO: check this. I have no input image yet which is known to be correct.
// TODO: is there a coeff=8 with full_range=false ?
int yv = in_y[y * in_y_stride + x];
int cb = in_cb[cy * in_cb_stride + cx] - halfRange;
int cr = in_cr[cy * in_cr_stride + cx] - halfRange;
out_r[y * out_r_stride + x] = (Pixel) (clip_int_u8(yv - cb + cr));
out_g[y * out_g_stride + x] = (Pixel) (clip_int_u8(yv + cb));
out_b[y * out_b_stride + x] = (Pixel) (clip_int_u8(yv - cb - cr));
}
else { // TODO: matrix_coefficients = 11,14
float yv, cb, cr;
yv = static_cast<float>(in_y[y * in_y_stride + x] );
cb = static_cast<float>(in_cb[cy * in_cb_stride + cx] - halfRange);
cr = static_cast<float>(in_cr[cy * in_cr_stride + cx] - halfRange);
if (!full_range_flag) {
yv = (yv - limited_range_offset) * 1.1689f;
cb = cb * 1.1429f;
cr = cr * 1.1429f;
}
out_r[y * out_r_stride + x] = (Pixel) (clip_f_u16(yv + coeffs.r_cr * cr, fullRange));
out_g[y * out_g_stride + x] = (Pixel) (clip_f_u16(yv + coeffs.g_cb * cb + coeffs.g_cr * cr, fullRange));
out_b[y * out_b_stride + x] = (Pixel) (clip_f_u16(yv + coeffs.b_cb * cb, fullRange));
}
}
if (has_alpha) {
int copyWidth = (hdr ? width * 2 : width);
memcpy(&out_a[y * out_a_stride], &in_a[y * in_a_stride], copyWidth);
}
}
return outimg;
}
template class Op_YCbCr_to_RGB<uint8_t>;
template class Op_YCbCr_to_RGB<uint16_t>;
std::vector<ColorStateWithCost>
Op_YCbCr420_to_RGB24::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 (input_state.chroma != heif_chroma_444) {
if (options.preferred_chroma_upsampling_algorithm != heif_chroma_upsampling_nearest_neighbor &&
options.only_use_preferred_chroma_algorithm) {
return {};
}
}
if (input_state.colorspace != heif_colorspace_YCbCr ||
input_state.chroma != heif_chroma_420 ||
input_state.bits_per_pixel != 8 ||
input_state.has_alpha == true) {
return {};
}
int matrix = input_state.nclx_profile.get_matrix_coefficients();
if (matrix == 0 || matrix == 8 || matrix == 11 || matrix == 14) {
return {};
}
if (!input_state.nclx_profile.get_full_range_flag()) {
return {};
}
std::vector<ColorStateWithCost> states;
ColorState output_state;
// --- convert to RGB
output_state.colorspace = heif_colorspace_RGB;
output_state.chroma = heif_chroma_interleaved_RGB;
output_state.has_alpha = false;
output_state.bits_per_pixel = 8;
states.push_back({output_state, SpeedCosts_Unoptimized});
return states;
}
std::shared_ptr<HeifPixelImage>
Op_YCbCr420_to_RGB24::convert_colorspace(const std::shared_ptr<const HeifPixelImage>& input,
const ColorState& input_state,
const ColorState& target_state,
const heif_color_conversion_options& options) const
{
if (input->get_bits_per_pixel(heif_channel_Y) != 8 ||
input->get_bits_per_pixel(heif_channel_Cb) != 8 ||
input->get_bits_per_pixel(heif_channel_Cr) != 8) {
return nullptr;
}
auto outimg = std::make_shared<HeifPixelImage>();
int width = input->get_width();
int height = input->get_height();
outimg->create(width, height, heif_colorspace_RGB, heif_chroma_interleaved_24bit);
if (!outimg->add_plane(heif_channel_interleaved, width, height, 8)) {
return nullptr;
}
auto colorProfile = input->get_color_profile_nclx();
YCbCr_to_RGB_coefficients coeffs = YCbCr_to_RGB_coefficients::defaults();
if (colorProfile) {
coeffs = get_YCbCr_to_RGB_coefficients(colorProfile->get_matrix_coefficients(),
colorProfile->get_colour_primaries());
}
int r_cr = static_cast<int>(std::lround(256 * coeffs.r_cr));
int g_cr = static_cast<int>(std::lround(256 * coeffs.g_cr));
int g_cb = static_cast<int>(std::lround(256 * coeffs.g_cb));
int b_cb = static_cast<int>(std::lround(256 * coeffs.b_cb));
const uint8_t* in_y, * in_cb, * in_cr;
int in_y_stride = 0, in_cb_stride = 0, in_cr_stride = 0;
uint8_t* out_p;
int out_p_stride = 0;
in_y = input->get_plane(heif_channel_Y, &in_y_stride);
in_cb = input->get_plane(heif_channel_Cb, &in_cb_stride);
in_cr = input->get_plane(heif_channel_Cr, &in_cr_stride);
out_p = outimg->get_plane(heif_channel_interleaved, &out_p_stride);
int x, y;
for (y = 0; y < height; y++) {
for (x = 0; x < width; x++) {
int yv = (in_y[y * in_y_stride + x]);
int cb = (in_cb[y / 2 * in_cb_stride + x / 2] - 128);
int cr = (in_cr[y / 2 * in_cr_stride + x / 2] - 128);
out_p[y * out_p_stride + 3 * x + 0] = clip_int_u8(yv + ((r_cr * cr + 128) >> 8));
out_p[y * out_p_stride + 3 * x + 1] = clip_int_u8(yv + ((g_cb * cb + g_cr * cr + 128) >> 8));
out_p[y * out_p_stride + 3 * x + 2] = clip_int_u8(yv + ((b_cb * cb + 128) >> 8));
}
}
return outimg;
}
std::vector<ColorStateWithCost>
Op_YCbCr420_to_RGB32::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 (input_state.chroma != heif_chroma_444) {
if (options.preferred_chroma_upsampling_algorithm != heif_chroma_upsampling_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_YCbCr ||
input_state.chroma != heif_chroma_420 ||
input_state.bits_per_pixel != 8) {
return {};
}
int matrix = input_state.nclx_profile.get_matrix_coefficients();
if (matrix == 0 || matrix == 8 || matrix == 11 || matrix == 14) {
return {};
}
if (!input_state.nclx_profile.get_full_range_flag()) {
return {};
}
std::vector<ColorStateWithCost> states;
ColorState output_state;
// --- convert to RGB
output_state.colorspace = heif_colorspace_RGB;
output_state.chroma = heif_chroma_interleaved_RGBA;
output_state.has_alpha = true;
output_state.bits_per_pixel = 8;
states.push_back({output_state, SpeedCosts_Unoptimized});
return states;
}
std::shared_ptr<HeifPixelImage>
Op_YCbCr420_to_RGB32::convert_colorspace(const std::shared_ptr<const HeifPixelImage>& input,
const ColorState& input_state,
const ColorState& target_state,
const heif_color_conversion_options& options) const
{
if (input->get_bits_per_pixel(heif_channel_Y) != 8 ||
input->get_bits_per_pixel(heif_channel_Cb) != 8 ||
input->get_bits_per_pixel(heif_channel_Cr) != 8) {
return nullptr;
}
auto outimg = std::make_shared<HeifPixelImage>();
int width = input->get_width();
int height = input->get_height();
outimg->create(width, height, heif_colorspace_RGB, heif_chroma_interleaved_32bit);
if (!outimg->add_plane(heif_channel_interleaved, width, height, 8)) {
return nullptr;
}
// --- get conversion coefficients
auto colorProfile = input->get_color_profile_nclx();
YCbCr_to_RGB_coefficients coeffs = YCbCr_to_RGB_coefficients::defaults();
if (colorProfile) {
coeffs = get_YCbCr_to_RGB_coefficients(colorProfile->get_matrix_coefficients(),
colorProfile->get_colour_primaries());
}
int r_cr = static_cast<int>(std::lround(256 * coeffs.r_cr));
int g_cr = static_cast<int>(std::lround(256 * coeffs.g_cr));
int g_cb = static_cast<int>(std::lround(256 * coeffs.g_cb));
int b_cb = static_cast<int>(std::lround(256 * coeffs.b_cb));
const bool with_alpha = input->has_channel(heif_channel_Alpha);
const uint8_t* in_y, * in_cb, * in_cr, * in_a = nullptr;
int in_y_stride = 0, in_cb_stride = 0, in_cr_stride = 0, in_a_stride = 0;
uint8_t* out_p;
int out_p_stride = 0;
in_y = input->get_plane(heif_channel_Y, &in_y_stride);
in_cb = input->get_plane(heif_channel_Cb, &in_cb_stride);
in_cr = input->get_plane(heif_channel_Cr, &in_cr_stride);
if (with_alpha) {
in_a = input->get_plane(heif_channel_Alpha, &in_a_stride);
}
out_p = outimg->get_plane(heif_channel_interleaved, &out_p_stride);
int x, y;
for (y = 0; y < height; y++) {
for (x = 0; x < width; x++) {
int yv = (in_y[y * in_y_stride + x]);
int cb = (in_cb[y / 2 * in_cb_stride + x / 2] - 128);
int cr = (in_cr[y / 2 * in_cr_stride + x / 2] - 128);
out_p[y * out_p_stride + 4 * x + 0] = clip_int_u8(yv + ((r_cr * cr + 128) >> 8));
out_p[y * out_p_stride + 4 * x + 1] = clip_int_u8(yv + ((g_cb * cb + g_cr * cr + 128) >> 8));
out_p[y * out_p_stride + 4 * x + 2] = clip_int_u8(yv + ((b_cb * cb + 128) >> 8));
if (with_alpha) {
out_p[y * out_p_stride + 4 * x + 3] = in_a[y * in_a_stride + x];
}
else {
out_p[y * out_p_stride + 4 * x + 3] = 0xFF;
}
}
}
return outimg;
}
std::vector<ColorStateWithCost>
Op_YCbCr420_to_RRGGBBaa::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 (input_state.chroma != heif_chroma_444) {
if (options.preferred_chroma_upsampling_algorithm != heif_chroma_upsampling_nearest_neighbor &&
options.only_use_preferred_chroma_algorithm) {
return {};
}
}
if (input_state.colorspace != heif_colorspace_YCbCr ||
input_state.chroma != heif_chroma_420 ||
input_state.bits_per_pixel == 8) {
return {};
}
int matrix = input_state.nclx_profile.get_matrix_coefficients();
if (matrix == 0 || matrix == 8 || matrix == 11 || matrix == 14) {
return {};
}
std::vector<ColorStateWithCost> states;
ColorState output_state;
// --- convert to YCbCr
output_state.colorspace = heif_colorspace_RGB;
output_state.chroma = (input_state.has_alpha ?
heif_chroma_interleaved_RRGGBBAA_LE : heif_chroma_interleaved_RRGGBB_LE);
output_state.has_alpha = input_state.has_alpha;
output_state.bits_per_pixel = input_state.bits_per_pixel;
states.push_back({output_state, SpeedCosts_Unoptimized});
output_state.colorspace = heif_colorspace_RGB;
output_state.chroma = (input_state.has_alpha ?
heif_chroma_interleaved_RRGGBBAA_BE : heif_chroma_interleaved_RRGGBB_BE);
output_state.has_alpha = input_state.has_alpha;
output_state.bits_per_pixel = input_state.bits_per_pixel;
states.push_back({output_state, SpeedCosts_Unoptimized});
return states;
}
std::shared_ptr<HeifPixelImage>
Op_YCbCr420_to_RRGGBBaa::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_Y);
bool has_alpha = input->has_channel(heif_channel_Alpha);
int le = (target_state.chroma == heif_chroma_interleaved_RRGGBB_LE ||
target_state.chroma == heif_chroma_interleaved_RRGGBBAA_LE) ? 1 : 0;
auto outimg = std::make_shared<HeifPixelImage>();
outimg->create(width, height, heif_colorspace_RGB, target_state.chroma);
int bytesPerPixel = has_alpha ? 8 : 6;
if (!outimg->add_plane(heif_channel_interleaved, width, height, bpp)) {
return nullptr;
}
if (has_alpha) {
if (!outimg->add_plane(heif_channel_Alpha, width, height, bpp)) {
return nullptr;
}
}
uint8_t* out_p;
int out_p_stride = 0;
const uint16_t* in_y, * in_cb, * in_cr, * in_a = nullptr;
int in_y_stride = 0, in_cb_stride = 0, in_cr_stride = 0, in_a_stride = 0;
out_p = outimg->get_plane(heif_channel_interleaved, &out_p_stride);
in_y = (uint16_t*) input->get_plane(heif_channel_Y, &in_y_stride);
in_cb = (uint16_t*) input->get_plane(heif_channel_Cb, &in_cb_stride);
in_cr = (uint16_t*) input->get_plane(heif_channel_Cr, &in_cr_stride);
if (has_alpha) {
in_a = (uint16_t*) input->get_plane(heif_channel_Alpha, &in_a_stride);
}
int maxval = (1 << bpp) - 1;
bool full_range_flag = true;
YCbCr_to_RGB_coefficients coeffs = YCbCr_to_RGB_coefficients::defaults();
auto colorProfile = input->get_color_profile_nclx();
if (colorProfile) {
full_range_flag = colorProfile->get_full_range_flag();
coeffs = get_YCbCr_to_RGB_coefficients(colorProfile->get_matrix_coefficients(),
colorProfile->get_colour_primaries());
}
float limited_range_offset = static_cast<float>(16 << (bpp - 8));
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
float y_ = in_y[y * in_y_stride / 2 + x];
float cb = static_cast<float>(in_cb[y / 2 * in_cb_stride / 2 + x / 2] - (1 << (bpp - 1)));
float cr = static_cast<float>(in_cr[y / 2 * in_cr_stride / 2 + x / 2] - (1 << (bpp - 1)));
if (!full_range_flag) {
y_ = (y_ - limited_range_offset) * 1.1689f;
cb = cb * 1.1429f;
cr = cr * 1.1429f;
}
int r = clip_f_u16(y_ + coeffs.r_cr * cr, maxval);
int g = clip_f_u16(y_ + coeffs.g_cb * cb + coeffs.g_cr * cr, maxval);
int b = clip_f_u16(y_ + coeffs.b_cb * cb, maxval);
out_p[y * out_p_stride + bytesPerPixel * x + 0 + le] = (uint8_t) (r >> 8);
out_p[y * out_p_stride + bytesPerPixel * x + 2 + le] = (uint8_t) (g >> 8);
out_p[y * out_p_stride + bytesPerPixel * x + 4 + le] = (uint8_t) (b >> 8);
out_p[y * out_p_stride + bytesPerPixel * x + 1 - le] = (uint8_t) (r & 0xff);
out_p[y * out_p_stride + bytesPerPixel * x + 3 - le] = (uint8_t) (g & 0xff);
out_p[y * out_p_stride + bytesPerPixel * x + 5 - le] = (uint8_t) (b & 0xff);
if (has_alpha) {
out_p[y * out_p_stride + 8 * x + 6 + le] = (uint8_t) (in_a[y * in_a_stride / 2 + x] >> 8);
out_p[y * out_p_stride + 8 * x + 7 - le] = (uint8_t) (in_a[y * in_a_stride / 2 + x] & 0xff);
}
}
}
return outimg;
}
#if HAVE_YUV
using namespace libyuv;
template<class PIXEL>
int convert_colorspace_core(const bool has_alpha,
const heif_chroma in_chroma,
const PIXEL* in_y,
const int in_y_stride,
const PIXEL* in_cb,
const int in_cb_stride,
const PIXEL* in_cr,
const int in_cr_stride,
const PIXEL* in_a,
const int in_a_stride,
const struct YuvConstants * matrixYUV,
const struct YuvConstants * matrixYVU,
const int width,
const int height,
uint8_t* out_p,
int& out_p_stride
)
{
return 0 ;
}
template< >
int convert_colorspace_core<uint8_t> (const bool has_alpha,
const heif_chroma in_chroma,
const uint8_t* in_y,
const int in_y_stride,
const uint8_t* in_cb,
const int in_cb_stride,
const uint8_t* in_cr,
const int in_cr_stride,
const uint8_t* in_a,
const int in_a_stride,
const struct YuvConstants * matrixYUV,
const struct YuvConstants * matrixYVU,
const int width,
const int height,
uint8_t* out_p,
int& out_p_stride
)
{
int hit = 0;
// AVIF_RGB_FORMAT_RGB *ToRGB24Matrix matrixYVU
if(has_alpha)
{
if (in_chroma == heif_chroma_444 ) {
if (libyuv::I444AlphaToABGRMatrix(in_y,
in_y_stride,
in_cb,
in_cb_stride,
in_cr,
in_cr_stride,
in_a,
in_a_stride,
out_p,
out_p_stride,
matrixY,
width,
height,
0) == 0) {
hit = 1;
}
}
else if(in_chroma == heif_chroma_422 ) {
if (libyuv::I422AlphaToABGRMatrix(in_y,
in_y_stride,
in_cb,
in_cb_stride,
in_cr,
in_cr_stride,
in_a,
in_a_stride,
out_p,
out_p_stride,
matrixY,
width,
height,
0) == 0) {
hit = 1;
}
}
else if(in_chroma == heif_chroma_420) {
if (libyuv::I420AlphaToABGRMatrix(in_y,
in_y_stride,
in_cb,
in_cb_stride,
in_cr,
in_cr_stride,
in_a,
in_a_stride,
out_p,
out_p_stride,
matrixY,
width,
height,
0) == 0) {
hit = 1;
}
}
}
else
{
if (in_chroma == heif_chroma_444 ) {
if (libyuv::I444ToARGBMatrix(in_y,
in_y_stride,
in_cr,
in_cr_stride,
in_cb,
in_cb_stride,
out_p,
out_p_stride,
matrixYVU,
width,
height) == 0) {
hit = 1;
}
}
else if(in_chroma == heif_chroma_422 ) {
if (libyuv::I422ToARGBMatrix(in_y,
in_y_stride,
in_cr,
in_cr_stride,
in_cb,
in_cb_stride,
out_p,
out_p_stride,
matrixYVU,
width,
height) == 0) {
hit = 1;
}
}
else if(in_chroma == heif_chroma_420) {
if (libyuv::I420ToARGBMatrix(in_y,
in_y_stride,
in_cr,
in_cr_stride,
in_cb,
in_cb_stride,
out_p,
out_p_stride,
matrixYVU,
width,
height) == 0) {
hit = 1;
}
}
}
return hit;
}
template< >
int convert_colorspace_core<uint16_t> (const bool has_alpha,
const heif_chroma in_chroma,
const uint16_t* in_y,
const int in_y_stride,
const uint16_t* in_cb,
const int in_cb_stride,
const uint16_t* in_cr,
const int in_cr_stride,
const uint16_t* in_a,
const int in_a_stride,
const struct YuvConstants * matrixYUV,
const struct YuvConstants * matrixYVU,
const int width,
const int height,
uint8_t* out_p,
int& out_p_stride
)
{
int hit = 0;
// AVIF_RGB_FORMAT_RGB *ToRGB24Matrix matrixYVU
if(has_alpha)
{
if (in_chroma == heif_chroma_444 ) {
if (libyuv::I410AlphaToABGRMatrix(in_y,
in_y_stride/2,
in_cb,
in_cb_stride/2,
in_cr,
in_cr_stride/2,
in_a,
in_a_stride/2,
out_p,
out_p_stride,
matrixY,
width,
height,
0) == 0) {
hit = 1;
}
}
else if(in_chroma == heif_chroma_422 ) {
if (libyuv::I210AlphaToABGRMatrix(in_y,
in_y_stride/2,
in_cb,
in_cb_stride/2,
in_cr,
in_cr_stride/2,
in_a,
in_a_stride/2,
out_p,
out_p_stride,
matrixY,
width,
height,
0) == 0) {
hit = 1;
}
}
else if(in_chroma == heif_chroma_420) {
if (libyuv::I010AlphaToABGRMatrix(in_y,
in_y_stride/2,
in_cb,
in_cb_stride/2,
in_cr,
in_cr_stride/2,
in_a,
in_a_stride/2,
out_p,
out_p_stride,
matrixY,
width,
height,
0) == 0) {
hit = 1;
}
}
}
else
{
if (in_chroma == heif_chroma_444 ) {
if (libyuv::I410ToARGBMatrix(in_y,
in_y_stride/2,
in_cr,
in_cr_stride/2,
in_cb,
in_cb_stride/2,
out_p,
out_p_stride,
matrixYVU,
width,
height) == 0) {
hit = 1;
}
}
else if(in_chroma == heif_chroma_422 ) {
if (libyuv::I210ToARGBMatrix(in_y,
in_y_stride/2,
in_cr,
in_cr_stride/2,
in_cb,
in_cb_stride/2,
out_p,
out_p_stride,
matrixYVU,
width,
height) == 0) {
hit = 1;
}
}
else if(in_chroma == heif_chroma_420) {
if (libyuv::I010ToARGBMatrix(in_y,
in_y_stride/2,
in_cr,
in_cr_stride/2,
in_cb,
in_cb_stride/2,
out_p,
out_p_stride,
matrixYVU,
width,
height) == 0) {
hit = 1;
}
}
}
return hit;
}
template<class Pixel>
std::shared_ptr<HeifPixelImage> convert_colorspace_libyuv(const std::shared_ptr<const HeifPixelImage>& input,
const ColorState& target_state,
const heif_color_conversion_options& options,
const struct YuvConstants * matrixYUV,
const struct YuvConstants * matrixYVU)
{
// (void)matrixYVU;
bool has_alpha = input->has_channel(heif_channel_Alpha);
int width = input->get_width();
int height = input->get_height();
uint8_t bpp = input->get_bits_per_pixel(heif_channel_Y);
bool hdr = !std::is_same<Pixel, uint8_t>::value;
if(hdr){
if(bpp <= 8) {
return nullptr;
}
}
heif_chroma in_chroma = input->get_chroma_format();
auto outimg = std::make_shared<HeifPixelImage>();
heif_chroma dest_chroma = heif_chroma_interleaved_RGBA ;
// notice : this type change target_state.chroma, from RGB to interleaved_RGBA
outimg->create(width, height, heif_colorspace_RGB, dest_chroma ); // RGBA
if(input->get_image_use_external_buf() && (input->get_image_external_buf_base() != NULL)) {
outimg->set_image_external_info(true, input->get_image_external_buf_base(),
input->get_image_external_buf_len(),
input->get_image_external_buf_stride());
outimg->add_shared_rgba_plane(heif_channel_interleaved, width, height, 8);
}
else {
outimg->add_plane(heif_channel_interleaved, width, height, 8);
}
const Pixel* in_y, * in_cb, * in_cr, * in_a;
int in_y_stride = 0, in_cb_stride = 0, in_cr_stride = 0, in_a_stride = 0;
uint8_t* out_p;
int out_p_stride = 0;
in_y = (const Pixel*) input->get_plane(heif_channel_Y, &in_y_stride);
in_cb = (const Pixel*) input->get_plane(heif_channel_Cb, &in_cb_stride);
in_cr = (const Pixel*) input->get_plane(heif_channel_Cr, &in_cr_stride);
out_p = (uint8_t*) outimg->get_plane(heif_channel_interleaved, &out_p_stride);
if (has_alpha) {
in_a = (const Pixel*) input->get_plane(heif_channel_Alpha, &in_a_stride);
}
else {
in_a = nullptr;
}
int hit = 0;
if (target_state.colorspace == heif_colorspace_RGB) {
hit = convert_colorspace_core<Pixel>(has_alpha,
in_chroma,
in_y,
in_y_stride,
in_cb,
in_cb_stride,
in_cr,
in_cr_stride,
in_a,
in_a_stride,
matrixYUV,
matrixYVU,
width,
height,
out_p,
out_p_stride);
}
return (hit)? outimg : nullptr ;
}
std::vector<ColorStateWithCost>
Op_YCbCr_to_RGBA_GENERAL::state_after_conversion(const ColorState& input_state,
const ColorState& target_state,
const heif_color_conversion_options& options) const
{
// Note: if YUV library is not existed, return null.
#if !HAVE_YUV
return {};
#endif
// Note: no input alpha channel required. It will be filled up with 0xFF.
if (input_state.colorspace != heif_colorspace_YCbCr ||
input_state.chroma != heif_chroma_420) { // ||
// input_state.bits_per_pixel != 8) {
return {};
}
// if (input_state.nclx_profile) {
// int matrix = input_state.nclx_profile->get_matrix_coefficients();
// if (matrix == 0 || matrix == 8 || matrix == 11 || matrix == 14) {
// return {};
// }
// if (!input_state.nclx_profile->get_full_range_flag()) {
// return {};
// }
// }
std::vector<ColorStateWithCost> states;
ColorState output_state;
// --- convert to RGB
output_state.colorspace = heif_colorspace_RGB;
output_state.chroma = heif_chroma_interleaved_RGBA;
output_state.has_alpha = true;
output_state.bits_per_pixel = 8;
states.push_back({output_state, SpeedCosts_Trivial});
return states;
}
std::shared_ptr<HeifPixelImage>
Op_YCbCr_to_RGBA_GENERAL::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 bpp_y = input->get_bits_per_pixel(heif_channel_Y);
int bpp_cb = input->get_bits_per_pixel(heif_channel_Cb);
int bpp_cr = input->get_bits_per_pixel(heif_channel_Cr);
if (bpp_y != bpp_cb ||
bpp_y != bpp_cr) {
// TODO: test with varying bit depths when we have a test image
return nullptr;
}
// get YuvConstants coeff
auto colorProfile = input->get_color_profile_nclx();
int matrix_coeffs = 2;
bool full_range_flag = true;
int colorprimaries = 2;
if (colorProfile) {
matrix_coeffs = colorProfile->get_matrix_coefficients();
full_range_flag = colorProfile->get_full_range_flag();
colorprimaries = colorProfile->get_colour_primaries();
}
// Find the correct libyuv YuvConstants, based on range and CP/MC
const struct YuvConstants * matrixYUV = &libyuv::kYuvJPEGConstants;
const struct YuvConstants * matrixYVU = &libyuv::kYvuJPEGConstants;
// if (image->yuvRange == AVIF_RANGE_FULL) {
if ( full_range_flag ) {
switch (matrix_coeffs ) {
// BT.709 full range YuvConstants were added in libyuv version 1772.
// See https://chromium-review.googlesource.com/c/libyuv/libyuv/+/2646472.
case HEIF_MATRIX_COEFFICIENTS_BT709:
matrixYUV = &libyuv::kYuvF709Constants;
matrixYVU = &libyuv::kYvuF709Constants;
break;
case HEIF_MATRIX_COEFFICIENTS_BT470BG:
case HEIF_MATRIX_COEFFICIENTS_BT601:
case HEIF_MATRIX_COEFFICIENTS_UNSPECIFIED:
matrixYUV = &libyuv::kYuvJPEGConstants;
matrixYVU = &libyuv::kYvuJPEGConstants;
break;
// BT.2020 full range YuvConstants were added in libyuv version 1775.
// See https://chromium-review.googlesource.com/c/libyuv/libyuv/+/2678859.
case HEIF_MATRIX_COEFFICIENTS_BT2020_NCL:
matrixYUV = &libyuv::kYuvV2020Constants;
matrixYVU = &libyuv::kYvuV2020Constants;
break;
case HEIF_MATRIX_COEFFICIENTS_CHROMA_DERIVED_NCL:
switch (colorprimaries ) {
case HEIF_COLOR_PRIMARIES_BT709:
case HEIF_COLOR_PRIMARIES_UNSPECIFIED:
matrixYUV = &libyuv::kYuvF709Constants;
matrixYVU = &libyuv::kYvuF709Constants;
break;
case HEIF_COLOR_PRIMARIES_BT470BG:
case HEIF_COLOR_PRIMARIES_BT601:
matrixYUV = &libyuv::kYuvJPEGConstants;
matrixYVU = &libyuv::kYvuJPEGConstants;
break;
case HEIF_COLOR_PRIMARIES_BT2020:
matrixYUV = &libyuv::kYuvV2020Constants;
matrixYVU = &libyuv::kYvuV2020Constants;
break;
case HEIF_COLOR_PRIMARIES_UNKNOWN:
case HEIF_COLOR_PRIMARIES_BT470M:
case HEIF_COLOR_PRIMARIES_SMPTE240:
case HEIF_COLOR_PRIMARIES_GENERIC_FILM:
case HEIF_COLOR_PRIMARIES_XYZ:
case HEIF_COLOR_PRIMARIES_SMPTE431:
case HEIF_COLOR_PRIMARIES_SMPTE432:
case HEIF_COLOR_PRIMARIES_EBU3213:
break;
}
break;
case HEIF_MATRIX_COEFFICIENTS_IDENTITY:
case HEIF_MATRIX_COEFFICIENTS_FCC:
case HEIF_MATRIX_COEFFICIENTS_SMPTE240:
case HEIF_MATRIX_COEFFICIENTS_YCGCO:
case HEIF_MATRIX_COEFFICIENTS_BT2020_CL:
case HEIF_MATRIX_COEFFICIENTS_SMPTE2085:
case HEIF_MATRIX_COEFFICIENTS_CHROMA_DERIVED_CL:
case HEIF_MATRIX_COEFFICIENTS_ICTCP:
break;
}
} else {
switch (matrix_coeffs ) {
case HEIF_MATRIX_COEFFICIENTS_BT709:
matrixYUV = &libyuv::kYuvH709Constants;
matrixYVU = &libyuv::kYvuH709Constants;
break;
case HEIF_MATRIX_COEFFICIENTS_BT470BG:
case HEIF_MATRIX_COEFFICIENTS_BT601:
case HEIF_MATRIX_COEFFICIENTS_UNSPECIFIED:
matrixYUV = &libyuv::kYuvI601Constants;
matrixYVU = &libyuv::kYvuI601Constants;
break;
case HEIF_MATRIX_COEFFICIENTS_BT2020_NCL:
matrixYUV = &libyuv::kYuv2020Constants;
matrixYVU = &libyuv::kYvu2020Constants;
break;
case HEIF_MATRIX_COEFFICIENTS_CHROMA_DERIVED_NCL:
switch (colorprimaries ) {
case HEIF_COLOR_PRIMARIES_BT709:
case HEIF_COLOR_PRIMARIES_UNSPECIFIED:
matrixYUV = &libyuv::kYuvH709Constants;
matrixYVU = &libyuv::kYvuH709Constants;
break;
case HEIF_COLOR_PRIMARIES_BT470BG:
case HEIF_COLOR_PRIMARIES_BT601:
matrixYUV = &libyuv::kYuvI601Constants;
matrixYVU = &libyuv::kYvuI601Constants;
break;
case HEIF_COLOR_PRIMARIES_BT2020:
matrixYUV = &libyuv::kYuv2020Constants;
matrixYVU = &libyuv::kYvu2020Constants;
break;
case HEIF_COLOR_PRIMARIES_UNKNOWN:
case HEIF_COLOR_PRIMARIES_BT470M:
case HEIF_COLOR_PRIMARIES_SMPTE240:
case HEIF_COLOR_PRIMARIES_GENERIC_FILM:
case HEIF_COLOR_PRIMARIES_XYZ:
case HEIF_COLOR_PRIMARIES_SMPTE431:
case HEIF_COLOR_PRIMARIES_SMPTE432:
case HEIF_COLOR_PRIMARIES_EBU3213:
break;
}
break;
case HEIF_MATRIX_COEFFICIENTS_IDENTITY:
case HEIF_MATRIX_COEFFICIENTS_FCC:
case HEIF_MATRIX_COEFFICIENTS_SMPTE240:
case HEIF_MATRIX_COEFFICIENTS_YCGCO:
case HEIF_MATRIX_COEFFICIENTS_BT2020_CL:
case HEIF_MATRIX_COEFFICIENTS_SMPTE2085:
case HEIF_MATRIX_COEFFICIENTS_CHROMA_DERIVED_CL:
case HEIF_MATRIX_COEFFICIENTS_ICTCP:
break;
}
}
if(!matrixYUV) {
return nullptr;
}
std::shared_ptr<HeifPixelImage> out_img ;
if(bpp_y <= 8 ) // RGBA
{
out_img = convert_colorspace_libyuv<uint8_t>(input, target_state, options, matrixYUV, matrixYVU);
}
else // RRGGBBAA
{
out_img = convert_colorspace_libyuv<uint16_t>(input, target_state, options, matrixYUV, matrixYVU);
}
return out_img;
}
#endif