diff --git a/libheif/color-conversion/chroma_sampling.cc b/libheif/color-conversion/chroma_sampling.cc index 78fbaeadf0..72ca791b91 100644 --- a/libheif/color-conversion/chroma_sampling.cc +++ b/libheif/color-conversion/chroma_sampling.cc @@ -240,6 +240,204 @@ template class Op_YCbCr444_to_YCbCr420_average; +template +std::vector +Op_YCbCr444_to_YCbCr422_average::state_after_conversion(const ColorState& input_state, + const ColorState& target_state, + const heif_color_conversion_options& options) const +{ + if (input_state.colorspace != heif_colorspace_YCbCr) { + return {}; + } + + if (input_state.chroma != heif_chroma_444) { + return {}; + } + + // this Op only implements the averaging algorithm + + if (options.preferred_chroma_downsampling_algorithm != heif_chroma_downsampling_average) { + return {}; + } + + bool hdr = !std::is_same::value; + + if ((input_state.bits_per_pixel != 8) != hdr) { + return {}; + } + + if (input_state.nclx_profile.get_matrix_coefficients() == 0) { + return {}; + } + + if (target_state.chroma != heif_chroma_422) { + return {}; + } + + std::vector states; + + ColorState output_state; + + // --- convert to 4:2:0 + + output_state.colorspace = heif_colorspace_YCbCr; + output_state.chroma = heif_chroma_422; + 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 = input_state.nclx_profile; + + states.push_back({output_state, SpeedCosts_Unoptimized}); + + return states; +} + + +template +std::shared_ptr +Op_YCbCr444_to_YCbCr422_average::convert_colorspace(const std::shared_ptr& input, + const ColorState& input_state, + const ColorState& target_state, + const heif_color_conversion_options& options) const +{ + bool hdr = !std::is_same::value; + + 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(); + + outimg->create(width, height, heif_colorspace_YCbCr, heif_chroma_422); + + int cwidth = (width + 1) / 2; + int cheight = height; + + if (!outimg->add_plane(heif_channel_Y, width, height, bpp_y) || + !outimg->add_plane(heif_channel_Cb, cwidth, cheight, bpp_cb) || + !outimg->add_plane(heif_channel_Cr, cwidth, cheight, bpp_cr)) { + 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_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_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_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_y_stride /= 2; + in_cb_stride /= 2; + in_cr_stride /= 2; + in_a_stride /= 2; + out_y_stride /= 2; + out_cb_stride /= 2; + out_cr_stride /= 2; + out_a_stride /= 2; + } + + // --- fill right border if the image size is odd + + if (width & 1) { + for (int y = 0; y < height - 1; y++) { + out_cb[y * out_cb_stride + cwidth - 1] = (Pixel) in_cb[y * in_cb_stride + width - 1]; + out_cr[y * out_cr_stride + cwidth - 1] = (Pixel) in_cr[y * in_cr_stride + width - 1]; + } + } + + + // --- averaging filter + + int x, y; + for (y = 0; y < height; y++) { + for (x = 0; x < width - 1; x += 2) { + Pixel cb00 = in_cb[y * in_cb_stride + x]; + Pixel cr00 = in_cr[y * in_cr_stride + x]; + Pixel cb01 = in_cb[y * in_cb_stride + x + 1]; + Pixel cr01 = in_cr[y * in_cr_stride + x + 1]; + + out_cb[y * out_cb_stride + x / 2] = (Pixel) ((cb00 + cb01 + 1) / 2); + out_cr[y * out_cr_stride + x / 2] = (Pixel) ((cr00 + cr01 + 1) / 2); + } + } + + // TODO: check whether we can use HeifPixelImage::transfer_plane_from_image_as() instead of copying Y and Alpha + + for (y = 0; y < height; y++) { + int copyWidth = (hdr ? width * 2 : width); + + memcpy(&out_y[y * out_y_stride], &in_y[y * in_y_stride], copyWidth); + + if (has_alpha) { + memcpy(&out_a[y * out_a_stride], &in_a[y * in_a_stride], copyWidth); + } + } + + return outimg; +} + +template class Op_YCbCr444_to_YCbCr422_average; +template class Op_YCbCr444_to_YCbCr422_average; + + + template std::vector Op_YCbCr420_bilinear_to_YCbCr444::state_after_conversion(const ColorState& input_state, diff --git a/libheif/color-conversion/chroma_sampling.h b/libheif/color-conversion/chroma_sampling.h index 825edeb3d0..1e1b2f7105 100644 --- a/libheif/color-conversion/chroma_sampling.h +++ b/libheif/color-conversion/chroma_sampling.h @@ -45,6 +45,23 @@ class Op_YCbCr444_to_YCbCr420_average : public ColorConversionOperation }; +template +class Op_YCbCr444_to_YCbCr422_average : public ColorConversionOperation +{ +public: + std::vector + state_after_conversion(const ColorState& input_state, + const ColorState& target_state, + const heif_color_conversion_options& options) const override; + + std::shared_ptr + convert_colorspace(const std::shared_ptr& input, + const ColorState& input_state, + const ColorState& target_state, + const heif_color_conversion_options& options) const override; +}; + + // --- upsampling --- template diff --git a/libheif/color-conversion/colorconversion.cc b/libheif/color-conversion/colorconversion.cc index 24e1e1129c..b84ce3df26 100644 --- a/libheif/color-conversion/colorconversion.cc +++ b/libheif/color-conversion/colorconversion.cc @@ -244,6 +244,8 @@ void ColorConversionPipeline::init_ops() ops.push_back(std::make_shared>()); ops.push_back(std::make_shared>()); ops.push_back(std::make_shared>()); + ops.push_back(std::make_shared>()); + ops.push_back(std::make_shared>()); ops.push_back(std::make_shared()); }