in libheif/context.cc [1729:2116]
Error HeifContext::decode_image_planar(heif_item_id ID,
std::shared_ptr<HeifPixelImage>& img,
heif_colorspace out_colorspace,
heif_chroma out_chroma,
const struct heif_decoding_options& options, bool alphaImage) const
{
std::string image_type = m_heif_file->get_item_type(ID);
std::shared_ptr<Image> imginfo;
if (m_all_images.find(ID) != m_all_images.end()) {
imginfo = m_all_images.find(ID)->second;
}
// Note: this may happen, for example when an 'iden' image references a non-existing image item.
if (imginfo == nullptr) {
return Error(heif_error_Invalid_input, heif_suberror_Nonexisting_item_referenced);
}
Error error;
// --- check whether image size exceeds maximum (according to 'ispe')
auto ispe = m_heif_file->get_property<Box_ispe>(ID);
if (ispe) {
error = check_resolution(ispe->get_width(), ispe->get_height());
if (error) {
return error;
}
}
// --- decode image, depending on its type
if (image_type == "hvc1" ||
image_type == "vvc1" ||
image_type == "av01" ||
image_type == "j2k1" ||
image_type == "jpeg" ||
(image_type == "mime" && m_heif_file->get_content_type(ID) == "image/jpeg")) {
heif_compression_format compression = heif_compression_undefined;
if (image_type == "hvc1") {
compression = heif_compression_HEVC;
}
else if (image_type == "vvc1") {
compression = heif_compression_VVC;
}
else if (image_type == "av01") {
compression = heif_compression_AV1;
}
else if (image_type == "jpeg" ||
(image_type == "mime" && m_heif_file->get_content_type(ID) == "image/jpeg")) {
compression = heif_compression_JPEG;
}
else if (image_type == "j2k1") {
compression = heif_compression_JPEG2000;
}
const struct heif_decoder_plugin* decoder_plugin = get_decoder(compression, options.decoder_id);
if (!decoder_plugin) {
return Error(heif_error_Plugin_loading_error, heif_suberror_No_matching_decoder_installed);
}
std::vector<uint8_t> data;
error = m_heif_file->get_compressed_image_data(ID, &data);
if (error) {
return error;
}
void* decoder;
struct heif_error err = decoder_plugin->new_decoder(&decoder, m_max_decoder_threads);
if (err.code != heif_error_Ok) {
return Error(err.code, err.subcode, err.message);
}
if (decoder_plugin->plugin_api_version >= 2) {
if (decoder_plugin->set_strict_decoding) {
decoder_plugin->set_strict_decoding(decoder, options.strict_decoding);
}
}
err = decoder_plugin->push_data(decoder, data.data(), data.size());
if (err.code != heif_error_Ok) {
decoder_plugin->free_decoder(decoder);
return Error(err.code, err.subcode, err.message);
}
//std::shared_ptr<HeifPixelImage>* decoded_img;
heif_image* decoded_img = nullptr;
err = decoder_plugin->decode_image(decoder, &decoded_img);
if (err.code != heif_error_Ok) {
decoder_plugin->free_decoder(decoder);
return Error(err.code, err.subcode, err.message);
}
if (!decoded_img) {
// TODO(farindk): The plugin should return an error in this case.
decoder_plugin->free_decoder(decoder);
return Error(heif_error_Decoder_plugin_error, heif_suberror_Unspecified);
}
img = std::move(decoded_img->image);
heif_image_release(decoded_img);
decoder_plugin->free_decoder(decoder);
// --- convert to output chroma format
// If there is an NCLX profile in the HEIF/AVIF metadata, use this for the color conversion.
// Otherwise, use the profile that is stored in the image stream itself and then set the
// (non-NCLX) profile later.
auto nclx = imginfo->get_color_profile_nclx();
if (nclx) {
img->set_color_profile_nclx(nclx);
}
auto icc = imginfo->get_color_profile_icc();
if (icc) {
img->set_color_profile_icc(icc);
}
if (alphaImage) {
// no color conversion required
}
else {
heif_colorspace target_colorspace = (out_colorspace == heif_colorspace_undefined ?
img->get_colorspace() :
out_colorspace);
// if (!alphaImage && target_colorspace == heif_colorspace_YCbCr) {
// target_colorspace = heif_colorspace_RGB;
// }
// heif_chroma target_chroma = (target_colorspace == heif_colorspace_monochrome ? heif_chroma_monochrome :
// (out_chroma == heif_chroma_interleaved_RGBA? heif_chroma_interleaved_RGBA : heif_chroma_444));
heif_chroma target_chroma = out_chroma == heif_chroma_undefined ? img->get_chroma_format() :
(target_colorspace == heif_colorspace_monochrome ? heif_chroma_monochrome : heif_chroma_444);
bool different_chroma = (target_chroma != img->get_chroma_format());
bool different_colorspace = (target_colorspace != img->get_colorspace());
if (different_chroma || different_colorspace) {
if(options.ext_dst && options.ext_dst_enable) {
img->set_image_external_info(true, options.ext_dst, options.ext_dst_len, options.ext_dst_stride);
}
img = convert_colorspace(img, target_colorspace, target_chroma, nullptr, 0, options.color_conversion_options);
if (!img) {
return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_color_conversion);
}
}
}
}
else if (image_type == "grid") {
std::vector<uint8_t> data;
error = m_heif_file->get_compressed_image_data(ID, &data);
if (error) {
return error;
}
error = decode_full_grid_image(ID, img, data, options);
if (error) {
return error;
}
}
else if (image_type == "iden") {
error = decode_derived_image(ID, img, options);
if (error) {
return error;
}
}
else if (image_type == "iovl") {
std::vector<uint8_t> data;
error = m_heif_file->get_compressed_image_data(ID, &data);
if (error) {
return error;
}
error = decode_overlay_image(ID, img, data, options);
if (error) {
return error;
}
#if WITH_UNCOMPRESSED_CODEC
}
else if (image_type == "unci") {
std::vector<uint8_t> data;
error = m_heif_file->get_compressed_image_data(ID, &data);
if (error) {
return error;
}
error = UncompressedImageCodec::decode_uncompressed_image(this,
ID,
img,
data);
if (error) {
return error;
}
#endif
}
else if (image_type == "mski") {
std::vector<uint8_t> data;
error = m_heif_file->get_compressed_image_data(ID, &data);
if (error) {
std::cout << "mski error 1" << std::endl;
return error;
}
error = MaskImageCodec::decode_mask_image(this,
ID,
img,
data);
if (error) {
return error;
}
}
else {
// Should not reach this, was already rejected by "get_image_data".
return Error(heif_error_Unsupported_feature,
heif_suberror_Unsupported_image_type);
}
// --- apply image transformations
if (options.ignore_transformations == false) {
std::vector<std::shared_ptr<Box>> properties;
auto ipco_box = m_heif_file->get_ipco_box();
auto ipma_box = m_heif_file->get_ipma_box();
error = ipco_box->get_properties_for_item_ID(ID, ipma_box, properties);
for (const auto& property : properties) {
if (property->get_short_type() == fourcc("irot")) {
auto rot = std::dynamic_pointer_cast<Box_irot>(property);
std::shared_ptr<HeifPixelImage> rotated_img;
error = img->rotate_ccw(rot->get_rotation(), rotated_img);
if (error) {
return error;
}
img = rotated_img;
}
if (property->get_short_type() == fourcc("imir")) {
auto mirror = std::dynamic_pointer_cast<Box_imir>(property);
error = img->mirror_inplace(mirror->get_mirror_direction());
if (error) {
return error;
}
}
if (property->get_short_type() == fourcc("clap")) {
auto clap = std::dynamic_pointer_cast<Box_clap>(property);
std::shared_ptr<HeifPixelImage> clap_img;
int img_width = img->get_width();
int img_height = img->get_height();
assert(img_width >= 0);
assert(img_height >= 0);
int left = clap->left_rounded(img_width);
int right = clap->right_rounded(img_width);
int top = clap->top_rounded(img_height);
int bottom = clap->bottom_rounded(img_height);
if (left < 0) { left = 0; }
if (top < 0) { top = 0; }
if (right >= img_width) { right = img_width - 1; }
if (bottom >= img_height) { bottom = img_height - 1; }
if (left > right ||
top > bottom) {
return Error(heif_error_Invalid_input,
heif_suberror_Invalid_clean_aperture);
}
std::shared_ptr<HeifPixelImage> cropped_img;
error = img->crop(left, right, top, bottom, cropped_img);
if (error) {
return error;
}
img = cropped_img;
}
}
}
// --- add alpha channel, if available
// TODO: this if statement is probably wrong. When we have a tiled image with alpha
// channel, then the alpha images should be associated with their respective tiles.
// However, the tile images are not part of the m_all_images list.
// Fix this, when we have a test image available.
if (m_all_images.find(ID) != m_all_images.end()) {
const auto imginfo = m_all_images.find(ID)->second;
std::shared_ptr<Image> alpha_image = imginfo->get_alpha_channel();
if (alpha_image) {
std::shared_ptr<HeifPixelImage> alpha;
Error err = decode_image_planar(alpha_image->get_id(), alpha,
heif_colorspace_undefined, heif_chroma_undefined, options, true);
if (err) {
return err;
}
// TODO: check that sizes are the same and that we have an Y channel
// BUT: is there any indication in the standard that the alpha channel should have the same size?
heif_channel channel;
switch (alpha->get_colorspace()) {
case heif_colorspace_YCbCr:
case heif_colorspace_monochrome:
channel = heif_channel_Y;
break;
case heif_colorspace_RGB:
channel = heif_channel_R;
break;
case heif_colorspace_undefined:
default:
return Error(heif_error_Invalid_input,
heif_suberror_Unsupported_color_conversion);
}
// TODO: we should include a decoding option to control whether libheif should automatically scale the alpha channel, and if so, which scaling filter (enum: Off, NN, Bilinear, ...).
// It might also be that a specific output format implies that alpha is scaled (RGBA32). That would favor an enum for the scaling filter option + a bool to switch auto-filtering on.
// But we can only do this when libheif itself doesn't assume anymore that the alpha channel has the same resolution.
if ((alpha_image->get_width() != img->get_width()) || (alpha_image->get_height() != img->get_height())) {
std::shared_ptr<HeifPixelImage> scaled_alpha;
err = alpha->scale_nearest_neighbor(scaled_alpha, img->get_width(), img->get_height());
if (err) {
return err;
}
alpha = std::move(scaled_alpha);
}
img->transfer_plane_from_image_as(alpha, channel, heif_channel_Alpha);
if (imginfo->is_premultiplied_alpha()) {
img->set_premultiplied_alpha(true);
}
}
}
// --- attach metadata to image
{
auto ipco_box = m_heif_file->get_ipco_box();
auto ipma_box = m_heif_file->get_ipma_box();
// CLLI
auto clli_box = ipco_box->get_property_for_item_ID(ID, ipma_box, fourcc("clli"));
auto clli = std::dynamic_pointer_cast<Box_clli>(clli_box);
if (clli) {
img->set_clli(clli->clli);
}
// MDCV
auto mdcv_box = ipco_box->get_property_for_item_ID(ID, ipma_box, fourcc("mdcv"));
auto mdcv = std::dynamic_pointer_cast<Box_mdcv>(mdcv_box);
if (mdcv) {
img->set_mdcv(mdcv->mdcv);
}
// PASP
auto pasp_box = ipco_box->get_property_for_item_ID(ID, ipma_box, fourcc("pasp"));
auto pasp = std::dynamic_pointer_cast<Box_pasp>(pasp_box);
if (pasp) {
img->set_pixel_ratio(pasp->hSpacing, pasp->vSpacing);
}
}
return Error::Ok;
}