libheif/color-conversion/rgb2yuv_sharp.cc (216 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 <cassert>
#include <memory>
#include <vector>
#include "rgb2yuv_sharp.h"
#ifdef HAVE_LIBSHARPYUV
#include <sharpyuv/sharpyuv.h>
#include <sharpyuv/sharpyuv_csp.h>
#include "nclx.h"
#include "common_utils.h"
static inline bool PlatformIsBigEndian()
{
int i = 1;
return !*((char*) &i);
}
static uint16_t Shift(uint16_t v, int input_bits, int output_bits)
{
if (input_bits == output_bits) return v;
if (output_bits > input_bits) {
int shift1 = output_bits - input_bits;
int shift2 = 8 - shift1;
return (uint16_t) ((v << shift1) | (v >> shift2));
}
else {
int shift = input_bits - output_bits;
return (uint16_t) (v >> shift);
}
}
#endif
std::vector<ColorStateWithCost>
Op_Any_RGB_to_YCbCr_420_Sharp::state_after_conversion(
const ColorState& input_state, const ColorState& target_state,
const heif_color_conversion_options& options) const
{
#ifdef HAVE_LIBSHARPYUV
// this Op only implements the sharp_yuv algorithm
// Note: no input alpha channel required. It will be filled up with 0xFF.
if (options.preferred_chroma_downsampling_algorithm != heif_chroma_downsampling_sharp_yuv &&
options.only_use_preferred_chroma_algorithm) {
return {};
}
// Only endianness matching the platform's is supported.
const bool big_endian = PlatformIsBigEndian();
const heif_chroma hdr_chroma = big_endian ? heif_chroma_interleaved_RRGGBB_BE
: heif_chroma_interleaved_RRGGBB_LE;
const heif_chroma hdr_with_alpha_chroma =
big_endian ? heif_chroma_interleaved_RRGGBBAA_BE
: heif_chroma_interleaved_RRGGBBAA_LE;
if (input_state.colorspace != heif_colorspace_RGB ||
(
input_state.chroma != heif_chroma_444 && // Planar input.
input_state.chroma != heif_chroma_interleaved_RGB &&
input_state.chroma != heif_chroma_interleaved_RGBA &&
input_state.chroma != hdr_chroma &&
input_state.chroma != hdr_with_alpha_chroma)) {
return {};
}
if (input_state.bits_per_pixel != 8 && input_state.bits_per_pixel != 10 &&
input_state.bits_per_pixel != 12 && input_state.bits_per_pixel != 16) {
return {};
}
if (target_state.bits_per_pixel != 8 && target_state.bits_per_pixel != 10 &&
target_state.bits_per_pixel != 12) {
return {};
}
if (target_state.chroma != heif_chroma_420) {
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 = heif_chroma_420;
output_state.has_alpha = target_state.has_alpha;
output_state.bits_per_pixel = target_state.bits_per_pixel;
output_state.nclx_profile = target_state.nclx_profile;
states.push_back({output_state, SpeedCosts_Slow});
return states;
#else
return {};
#endif
}
std::shared_ptr<HeifPixelImage>
Op_Any_RGB_to_YCbCr_420_Sharp::convert_colorspace(
const std::shared_ptr<const HeifPixelImage>& input,
const ColorState& input_state,
const ColorState& target_state,
const heif_color_conversion_options& options) const
{
#ifdef HAVE_LIBSHARPYUV
int width = input->get_width();
int height = input->get_height();
auto outimg = std::make_shared<HeifPixelImage>();
heif_chroma input_chroma = input->get_chroma_format();
heif_chroma output_chroma = target_state.chroma;
assert(output_chroma == heif_chroma_420); // Only 420 is supported by libsharpyuv.
uint8_t chromaSubH = chroma_h_subsampling(output_chroma);
uint8_t chromaSubV = chroma_v_subsampling(output_chroma);
outimg->create(width, height, heif_colorspace_YCbCr, output_chroma);
int chroma_width = (width + chromaSubH - 1) / chromaSubH;
int chroma_height = (height + chromaSubV - 1) / chromaSubV;
bool has_alpha =
input->get_chroma_format() == heif_chroma_interleaved_RGBA ||
input->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_LE ||
input->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_BE ||
(input->get_chroma_format() == heif_chroma_444 &&
input->has_channel(heif_channel_Alpha));
bool want_alpha = target_state.has_alpha;
int output_bits = target_state.bits_per_pixel;
if (!outimg->add_plane(heif_channel_Y, width, height, output_bits) ||
!outimg->add_plane(heif_channel_Cb, chroma_width, chroma_height, output_bits) ||
!outimg->add_plane(heif_channel_Cr, chroma_width, chroma_height, output_bits)) {
return nullptr;
}
if (want_alpha) {
if (!outimg->add_plane(heif_channel_Alpha, width, height, output_bits)) {
return nullptr;
}
}
int input_bytes_per_sample =
(input_chroma == heif_chroma_interleaved_RGB ||
input_chroma == heif_chroma_interleaved_RGBA ||
(input_chroma == heif_chroma_444 &&
input->get_bits_per_pixel(heif_channel_R) <= 8))
? 1
: 2;
const uint8_t* in_r, * in_g, * in_b, * in_a = nullptr;
int in_stride = 0;
int in_a_stride = 0;
bool planar_input = input_chroma == heif_chroma_444;
int input_bits = 0;
if (planar_input) {
int in_r_stride = 0, in_g_stride = 0, in_b_stride = 0;
in_r = input->get_plane(heif_channel_R, &in_r_stride);
in_g = input->get_plane(heif_channel_G, &in_g_stride);
in_b = input->get_plane(heif_channel_B, &in_b_stride);
// The stride must be the same for all channels.
if (in_r_stride != in_g_stride || in_r_stride != in_b_stride) {
return nullptr;
}
in_stride = in_r_stride;
// Bpp must also be the same.
input_bits = input->get_bits_per_pixel(heif_channel_R);
if (input_bits != input->get_bits_per_pixel(heif_channel_G) ||
input_bits != input->get_bits_per_pixel(heif_channel_B)) {
return nullptr;
}
if (has_alpha) {
in_a = input->get_plane(heif_channel_Alpha, &in_a_stride);
}
}
else {
const uint8_t* in_p = input->get_plane(heif_channel_interleaved, &in_stride);
input_bits = input->get_bits_per_pixel(heif_channel_interleaved);
in_r = &in_p[input_bytes_per_sample * 0];
in_g = &in_p[input_bytes_per_sample * 1];
in_b = &in_p[input_bytes_per_sample * 2];
if (has_alpha) {
in_a = &in_p[input_bytes_per_sample * 3];
in_a_stride = in_stride;
}
}
int out_cb_stride = 0, out_cr_stride = 0, out_y_stride = 0;
uint8_t* out_y = outimg->get_plane(heif_channel_Y, &out_y_stride);
uint8_t* out_cb = outimg->get_plane(heif_channel_Cb, &out_cb_stride);
uint8_t* out_cr = outimg->get_plane(heif_channel_Cr, &out_cr_stride);
bool full_range_flag = true;
Kr_Kb kr_kb = Kr_Kb::defaults();
full_range_flag = target_state.nclx_profile.get_full_range_flag();
kr_kb =
get_Kr_Kb(target_state.nclx_profile.get_matrix_coefficients(),
target_state.nclx_profile.get_colour_primaries());
SharpYuvColorSpace color_space = {
kr_kb.Kr, kr_kb.Kb, output_bits,
full_range_flag ? kSharpYuvRangeFull : kSharpYuvRangeLimited};
SharpYuvConversionMatrix yuv_matrix;
SharpYuvComputeConversionMatrix(&color_space, &yuv_matrix);
int input_bytes_per_pixel = (has_alpha ? 4 : 3) * input_bytes_per_sample;
int rgb_step = planar_input ? input_bytes_per_sample : input_bytes_per_pixel;
int sharpyuv_ok =
SharpYuvConvert(in_r, in_g, in_b, rgb_step, in_stride,
input_bits, out_y, out_y_stride, out_cb, out_cb_stride,
out_cr, out_cr_stride, output_bits,
input->get_width(), input->get_height(), &yuv_matrix);
if (!sharpyuv_ok) {
return nullptr;
}
if (want_alpha) {
int le = (input_chroma == heif_chroma_interleaved_RRGGBBAA_LE ||
input_chroma == heif_chroma_interleaved_RRGGBB_LE ||
(planar_input && !PlatformIsBigEndian()))
? 1
: 0;
int out_a_stride;
uint8_t* out_a = outimg->get_plane(heif_channel_Alpha, &out_a_stride);
uint16_t alpha_max = static_cast<uint16_t>((1 << input_bits) - 1);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
const uint8_t* in = has_alpha ? &in_a[y * in_a_stride + x * rgb_step] : nullptr;
uint16_t a = has_alpha
? ((input_bits == 8)
? in[0]
: (uint16_t) ((in[0 + le] << 8) | in[1 - le]))
: alpha_max;
if (output_bits == 8) {
out_a[y * out_a_stride + x] = (uint8_t) Shift(a, input_bits, output_bits);
}
else {
uint16_t* out_a16 = reinterpret_cast<uint16_t*>(out_a);
out_a16[y * out_a_stride / 2 + x] = Shift(a, input_bits, output_bits);
}
}
}
}
return outimg;
#else
return nullptr;
#endif
}