diff --git a/src/imageformats/CMakeLists.txt b/src/imageformats/CMakeLists.txt index 2f18125..a300e60 100644 --- a/src/imageformats/CMakeLists.txt +++ b/src/imageformats/CMakeLists.txt @@ -93,7 +93,6 @@ endif() if (LibHeif_FOUND) kimageformats_add_plugin(kimg_heif SOURCES heif.cpp) target_link_libraries(kimg_heif PkgConfig::LibHeif) - kde_target_enable_exceptions(kimg_heif PRIVATE) if (QT_MAJOR_VERSION STREQUAL "5") install(FILES heif.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/) diff --git a/src/imageformats/heif.cpp b/src/imageformats/heif.cpp index b876e59..b10b142 100644 --- a/src/imageformats/heif.cpp +++ b/src/imageformats/heif.cpp @@ -8,8 +8,8 @@ */ #include "heif_p.h" -#include "libheif/heif_cxx.h" #include "util_p.h" +#include #include #include @@ -18,46 +18,39 @@ #include #include -namespace // Private. -{ -struct HeifQIODeviceWriter : public heif::Context::Writer { - HeifQIODeviceWriter(QIODevice *device) - : m_ioDevice(device) - { - } - - heif_error write(const void *data, size_t size) override - { - heif_error error; - error.code = heif_error_Ok; - error.subcode = heif_suberror_Unspecified; - error.message = errorOkMessage; - - qint64 bytesWritten = m_ioDevice->write(static_cast(data), size); - - if (bytesWritten < static_cast(size)) { - error.code = heif_error_Encoding_error; - error.message = QIODeviceWriteErrorMessage; - error.subcode = heif_suberror_Cannot_write_output_data; - } - - return error; - } - - static constexpr const char *errorOkMessage = "Success"; - static constexpr const char *QIODeviceWriteErrorMessage = "Bytes written to QIODevice are smaller than input data size"; - -private: - QIODevice *m_ioDevice; -}; - -} // namespace - size_t HEIFHandler::m_initialized_count = 0; bool HEIFHandler::m_plugins_queried = false; bool HEIFHandler::m_heif_decoder_available = false; bool HEIFHandler::m_heif_encoder_available = false; +extern "C" { +static struct heif_error heifhandler_write_callback(struct heif_context * /* ctx */, const void *data, size_t size, void *userdata) +{ + heif_error error; + error.code = heif_error_Ok; + error.subcode = heif_suberror_Unspecified; + error.message = "Success"; + + if (!userdata || !data || size == 0) { + error.code = heif_error_Usage_error; + error.subcode = heif_suberror_Null_pointer_argument; + error.message = "Wrong parameters!"; + return error; + } + + QIODevice *ioDevice = static_cast(userdata); + qint64 bytesWritten = ioDevice->write(static_cast(data), size); + + if (bytesWritten < static_cast(size)) { + error.code = heif_error_Encoding_error; + error.message = "Bytes written to QIODevice are smaller than input data size"; + error.subcode = heif_suberror_Cannot_write_output_data; + } + + return error; +} +} + HEIFHandler::HEIFHandler() : m_parseState(ParseHeicNotParsed) , m_quality(100) @@ -154,120 +147,152 @@ bool HEIFHandler::write_helper(const QImage &image) const QImage tmpimage = image.convertToFormat(tmpformat); - try { - heif::Context ctx; - heif::Image heifImage; - heifImage.create(tmpimage.width(), tmpimage.height(), heif_colorspace_RGB, chroma); + struct heif_context *context = heif_context_alloc(); + struct heif_error err; + struct heif_image *h_image = nullptr; - QByteArray iccprofile = tmpimage.colorSpace().iccProfile(); - if (iccprofile.size() > 0) { - std::vector rawProfile(iccprofile.begin(), iccprofile.end()); - heifImage.set_raw_color_profile(heif_color_profile_type_prof, rawProfile); - } + err = heif_image_create(tmpimage.width(), tmpimage.height(), heif_colorspace_RGB, chroma, &h_image); + if (err.code) { + qWarning() << "heif_image_create error:" << err.message; + heif_context_free(context); + return false; + } - heifImage.add_plane(heif_channel_interleaved, image.width(), image.height(), save_depth); - int stride = 0; - uint8_t *const dst = heifImage.get_plane(heif_channel_interleaved, &stride); - size_t rowbytes; + QByteArray iccprofile = tmpimage.colorSpace().iccProfile(); + if (iccprofile.size() > 0) { + heif_image_set_raw_color_profile(h_image, "prof", iccprofile.constData(), iccprofile.size()); + } - switch (save_depth) { - case 10: - if (save_alpha) { - for (int y = 0; y < tmpimage.height(); y++) { - const uint16_t *src_word = reinterpret_cast(tmpimage.constScanLine(y)); - uint16_t *dest_word = reinterpret_cast(dst + (y * stride)); - for (int x = 0; x < tmpimage.width(); x++) { - int tmp_pixelval; - // R - tmp_pixelval = (int)(((float)(*src_word) / 65535.0f) * 1023.0f + 0.5f); - *dest_word = qBound(0, tmp_pixelval, 1023); - src_word++; - dest_word++; - // G - tmp_pixelval = (int)(((float)(*src_word) / 65535.0f) * 1023.0f + 0.5f); - *dest_word = qBound(0, tmp_pixelval, 1023); - src_word++; - dest_word++; - // B - tmp_pixelval = (int)(((float)(*src_word) / 65535.0f) * 1023.0f + 0.5f); - *dest_word = qBound(0, tmp_pixelval, 1023); - src_word++; - dest_word++; - // A - tmp_pixelval = (int)(((float)(*src_word) / 65535.0f) * 1023.0f + 0.5f); - *dest_word = qBound(0, tmp_pixelval, 1023); - src_word++; - dest_word++; - } - } - } else { // no alpha channel - for (int y = 0; y < tmpimage.height(); y++) { - const uint16_t *src_word = reinterpret_cast(tmpimage.constScanLine(y)); - uint16_t *dest_word = reinterpret_cast(dst + (y * stride)); - for (int x = 0; x < tmpimage.width(); x++) { - int tmp_pixelval; - // R - tmp_pixelval = (int)(((float)(*src_word) / 65535.0f) * 1023.0f + 0.5f); - *dest_word = qBound(0, tmp_pixelval, 1023); - src_word++; - dest_word++; - // G - tmp_pixelval = (int)(((float)(*src_word) / 65535.0f) * 1023.0f + 0.5f); - *dest_word = qBound(0, tmp_pixelval, 1023); - src_word++; - dest_word++; - // B - tmp_pixelval = (int)(((float)(*src_word) / 65535.0f) * 1023.0f + 0.5f); - *dest_word = qBound(0, tmp_pixelval, 1023); - src_word++; - dest_word++; - // X - src_word++; - } - } - } - break; - case 8: - rowbytes = save_alpha ? (tmpimage.width() * 4) : (tmpimage.width() * 3); + heif_image_add_plane(h_image, heif_channel_interleaved, image.width(), image.height(), save_depth); + int stride = 0; + uint8_t *const dst = heif_image_get_plane(h_image, heif_channel_interleaved, &stride); + size_t rowbytes; + + switch (save_depth) { + case 10: + if (save_alpha) { for (int y = 0; y < tmpimage.height(); y++) { - memcpy(dst + (y * stride), tmpimage.constScanLine(y), rowbytes); + const uint16_t *src_word = reinterpret_cast(tmpimage.constScanLine(y)); + uint16_t *dest_word = reinterpret_cast(dst + (y * stride)); + for (int x = 0; x < tmpimage.width(); x++) { + int tmp_pixelval; + // R + tmp_pixelval = (int)(((float)(*src_word) / 65535.0f) * 1023.0f + 0.5f); + *dest_word = qBound(0, tmp_pixelval, 1023); + src_word++; + dest_word++; + // G + tmp_pixelval = (int)(((float)(*src_word) / 65535.0f) * 1023.0f + 0.5f); + *dest_word = qBound(0, tmp_pixelval, 1023); + src_word++; + dest_word++; + // B + tmp_pixelval = (int)(((float)(*src_word) / 65535.0f) * 1023.0f + 0.5f); + *dest_word = qBound(0, tmp_pixelval, 1023); + src_word++; + dest_word++; + // A + tmp_pixelval = (int)(((float)(*src_word) / 65535.0f) * 1023.0f + 0.5f); + *dest_word = qBound(0, tmp_pixelval, 1023); + src_word++; + dest_word++; + } } - break; - default: - qWarning() << "Unsupported depth:" << save_depth; - return false; - break; - } - - heif::Encoder encoder(heif_compression_HEVC); - - encoder.set_lossy_quality(m_quality); - if (m_quality > 90) { - if (m_quality == 100) { - encoder.set_lossless(true); - } - encoder.set_string_parameter("chroma", "444"); - } - - heif::Context::EncodingOptions encodingOptions; - encodingOptions.save_alpha_channel = save_alpha; - - if ((tmpimage.width() % 2 == 1) || (tmpimage.height() % 2 == 1)) { - qWarning() << "Image has odd dimension!\nUse even-numbered dimension(s) for better compatibility with other HEIF implementations."; - if (save_alpha) { - // This helps to save alpha channel when image has odd dimension - encodingOptions.macOS_compatibility_workaround = 0; + } else { // no alpha channel + for (int y = 0; y < tmpimage.height(); y++) { + const uint16_t *src_word = reinterpret_cast(tmpimage.constScanLine(y)); + uint16_t *dest_word = reinterpret_cast(dst + (y * stride)); + for (int x = 0; x < tmpimage.width(); x++) { + int tmp_pixelval; + // R + tmp_pixelval = (int)(((float)(*src_word) / 65535.0f) * 1023.0f + 0.5f); + *dest_word = qBound(0, tmp_pixelval, 1023); + src_word++; + dest_word++; + // G + tmp_pixelval = (int)(((float)(*src_word) / 65535.0f) * 1023.0f + 0.5f); + *dest_word = qBound(0, tmp_pixelval, 1023); + src_word++; + dest_word++; + // B + tmp_pixelval = (int)(((float)(*src_word) / 65535.0f) * 1023.0f + 0.5f); + *dest_word = qBound(0, tmp_pixelval, 1023); + src_word++; + dest_word++; + // X + src_word++; + } } } + break; + case 8: + rowbytes = save_alpha ? (tmpimage.width() * 4) : (tmpimage.width() * 3); + for (int y = 0; y < tmpimage.height(); y++) { + memcpy(dst + (y * stride), tmpimage.constScanLine(y), rowbytes); + } + break; + default: + qWarning() << "Unsupported depth:" << save_depth; + heif_image_release(h_image); + heif_context_free(context); + return false; + break; + } - ctx.encode_image(heifImage, encoder, encodingOptions); + struct heif_encoder *encoder = nullptr; + err = heif_context_get_encoder_for_format(context, heif_compression_HEVC, &encoder); + if (err.code) { + qWarning() << "Unable to get an encoder instance:" << err.message; + heif_image_release(h_image); + heif_context_free(context); + return false; + } - HeifQIODeviceWriter writer(device()); + heif_encoder_set_lossy_quality(encoder, m_quality); + if (m_quality > 90) { + if (m_quality == 100) { + heif_encoder_set_lossless(encoder, true); + } + heif_encoder_set_parameter_string(encoder, "chroma", "444"); + } - ctx.write(writer); + struct heif_encoding_options *encoder_options = heif_encoding_options_alloc(); + encoder_options->save_alpha_channel = save_alpha; - } catch (const heif::Error &err) { - qWarning() << "libheif error:" << err.get_message().c_str(); + if ((tmpimage.width() % 2 == 1) || (tmpimage.height() % 2 == 1)) { + qWarning() << "Image has odd dimension!\nUse even-numbered dimension(s) for better compatibility with other HEIF implementations."; + if (save_alpha) { + // This helps to save alpha channel when image has odd dimension + encoder_options->macOS_compatibility_workaround = 0; + } + } + + err = heif_context_encode_image(context, h_image, encoder, encoder_options, nullptr); + + if (encoder_options) { + heif_encoding_options_free(encoder_options); + } + + if (err.code) { + qWarning() << "heif_context_encode_image failed:" << err.message; + heif_encoder_release(encoder); + heif_image_release(h_image); + heif_context_free(context); + return false; + } + + struct heif_writer writer; + writer.writer_api_version = 1; + writer.write = heifhandler_write_callback; + + err = heif_context_write(context, &writer, device()); + + heif_encoder_release(encoder); + heif_image_release(h_image); + heif_context_free(context); + + if (err.code) { + qWarning() << "Writing HEIF image failed:" << err.message; return false; } @@ -404,325 +429,367 @@ bool HEIFHandler::ensureDecoder() return false; } - try { - heif::Context ctx; - ctx.read_from_memory_without_copy(static_cast(buffer.constData()), buffer.size()); + struct heif_context *ctx = heif_context_alloc(); + struct heif_error err = heif_context_read_from_memory(ctx, static_cast(buffer.constData()), buffer.size(), nullptr); - heif::ImageHandle handle = ctx.get_primary_image_handle(); - - const bool hasAlphaChannel = handle.has_alpha_channel(); - const int bit_depth = handle.get_luma_bits_per_pixel(); - heif_chroma chroma; - - QImage::Format target_image_format; - - if (bit_depth == 10 || bit_depth == 12) { - if (hasAlphaChannel) { - chroma = (QSysInfo::ByteOrder == QSysInfo::LittleEndian) ? heif_chroma_interleaved_RRGGBBAA_LE : heif_chroma_interleaved_RRGGBBAA_BE; - target_image_format = QImage::Format_RGBA64; - } else { - chroma = (QSysInfo::ByteOrder == QSysInfo::LittleEndian) ? heif_chroma_interleaved_RRGGBB_LE : heif_chroma_interleaved_RRGGBB_BE; - target_image_format = QImage::Format_RGBX64; - } - } else if (bit_depth == 8) { - if (hasAlphaChannel) { - chroma = heif_chroma_interleaved_RGBA; - target_image_format = QImage::Format_ARGB32; - } else { - chroma = heif_chroma_interleaved_RGB; - target_image_format = QImage::Format_RGB32; - } - } else { - m_parseState = ParseHeicError; - if (bit_depth > 0) { - qWarning() << "Unsupported bit depth:" << bit_depth; - } else { - qWarning() << "Undefined bit depth."; - } - return false; - } - - heif::Image img = handle.decode_image(heif_colorspace_RGB, chroma); - - const int imageWidth = img.get_width(heif_channel_interleaved); - const int imageHeight = img.get_height(heif_channel_interleaved); - - QSize imageSize(imageWidth, imageHeight); - - if (!imageSize.isValid()) { - m_parseState = ParseHeicError; - qWarning() << "HEIC image size invalid:" << imageSize; - return false; - } - - int stride = 0; - const uint8_t *const src = img.get_plane(heif_channel_interleaved, &stride); - - if (!src || stride <= 0) { - m_parseState = ParseHeicError; - qWarning() << "HEIC data pixels information not valid!"; - return false; - } - - m_current_image = imageAlloc(imageSize, target_image_format); - if (m_current_image.isNull()) { - m_parseState = ParseHeicError; - qWarning() << "Unable to allocate memory!"; - return false; - } - - switch (bit_depth) { - case 12: - if (hasAlphaChannel) { - for (int y = 0; y < imageHeight; y++) { - const uint16_t *src_word = reinterpret_cast(src + (y * stride)); - uint16_t *dest_data = reinterpret_cast(m_current_image.scanLine(y)); - for (int x = 0; x < imageWidth; x++) { - int tmpvalue; - // R - tmpvalue = (int)(((float)(0x0fff & (*src_word)) / 4095.0f) * 65535.0f + 0.5f); - tmpvalue = qBound(0, tmpvalue, 65535); - *dest_data = (uint16_t)tmpvalue; - src_word++; - dest_data++; - // G - tmpvalue = (int)(((float)(0x0fff & (*src_word)) / 4095.0f) * 65535.0f + 0.5f); - tmpvalue = qBound(0, tmpvalue, 65535); - *dest_data = (uint16_t)tmpvalue; - src_word++; - dest_data++; - // B - tmpvalue = (int)(((float)(0x0fff & (*src_word)) / 4095.0f) * 65535.0f + 0.5f); - tmpvalue = qBound(0, tmpvalue, 65535); - *dest_data = (uint16_t)tmpvalue; - src_word++; - dest_data++; - // A - tmpvalue = (int)(((float)(0x0fff & (*src_word)) / 4095.0f) * 65535.0f + 0.5f); - tmpvalue = qBound(0, tmpvalue, 65535); - *dest_data = (uint16_t)tmpvalue; - src_word++; - dest_data++; - } - } - } else { // no alpha channel - for (int y = 0; y < imageHeight; y++) { - const uint16_t *src_word = reinterpret_cast(src + (y * stride)); - uint16_t *dest_data = reinterpret_cast(m_current_image.scanLine(y)); - for (int x = 0; x < imageWidth; x++) { - int tmpvalue; - // R - tmpvalue = (int)(((float)(0x0fff & (*src_word)) / 4095.0f) * 65535.0f + 0.5f); - tmpvalue = qBound(0, tmpvalue, 65535); - *dest_data = (uint16_t)tmpvalue; - src_word++; - dest_data++; - // G - tmpvalue = (int)(((float)(0x0fff & (*src_word)) / 4095.0f) * 65535.0f + 0.5f); - tmpvalue = qBound(0, tmpvalue, 65535); - *dest_data = (uint16_t)tmpvalue; - src_word++; - dest_data++; - // B - tmpvalue = (int)(((float)(0x0fff & (*src_word)) / 4095.0f) * 65535.0f + 0.5f); - tmpvalue = qBound(0, tmpvalue, 65535); - *dest_data = (uint16_t)tmpvalue; - src_word++; - dest_data++; - // X = 0xffff - *dest_data = 0xffff; - dest_data++; - } - } - } - break; - case 10: - if (hasAlphaChannel) { - for (int y = 0; y < imageHeight; y++) { - const uint16_t *src_word = reinterpret_cast(src + (y * stride)); - uint16_t *dest_data = reinterpret_cast(m_current_image.scanLine(y)); - for (int x = 0; x < imageWidth; x++) { - int tmpvalue; - // R - tmpvalue = (int)(((float)(0x03ff & (*src_word)) / 1023.0f) * 65535.0f + 0.5f); - tmpvalue = qBound(0, tmpvalue, 65535); - *dest_data = (uint16_t)tmpvalue; - src_word++; - dest_data++; - // G - tmpvalue = (int)(((float)(0x03ff & (*src_word)) / 1023.0f) * 65535.0f + 0.5f); - tmpvalue = qBound(0, tmpvalue, 65535); - *dest_data = (uint16_t)tmpvalue; - src_word++; - dest_data++; - // B - tmpvalue = (int)(((float)(0x03ff & (*src_word)) / 1023.0f) * 65535.0f + 0.5f); - tmpvalue = qBound(0, tmpvalue, 65535); - *dest_data = (uint16_t)tmpvalue; - src_word++; - dest_data++; - // A - tmpvalue = (int)(((float)(0x03ff & (*src_word)) / 1023.0f) * 65535.0f + 0.5f); - tmpvalue = qBound(0, tmpvalue, 65535); - *dest_data = (uint16_t)tmpvalue; - src_word++; - dest_data++; - } - } - } else { // no alpha channel - for (int y = 0; y < imageHeight; y++) { - const uint16_t *src_word = reinterpret_cast(src + (y * stride)); - uint16_t *dest_data = reinterpret_cast(m_current_image.scanLine(y)); - for (int x = 0; x < imageWidth; x++) { - int tmpvalue; - // R - tmpvalue = (int)(((float)(0x03ff & (*src_word)) / 1023.0f) * 65535.0f + 0.5f); - tmpvalue = qBound(0, tmpvalue, 65535); - *dest_data = (uint16_t)tmpvalue; - src_word++; - dest_data++; - // G - tmpvalue = (int)(((float)(0x03ff & (*src_word)) / 1023.0f) * 65535.0f + 0.5f); - tmpvalue = qBound(0, tmpvalue, 65535); - *dest_data = (uint16_t)tmpvalue; - src_word++; - dest_data++; - // B - tmpvalue = (int)(((float)(0x03ff & (*src_word)) / 1023.0f) * 65535.0f + 0.5f); - tmpvalue = qBound(0, tmpvalue, 65535); - *dest_data = (uint16_t)tmpvalue; - src_word++; - dest_data++; - // X = 0xffff - *dest_data = 0xffff; - dest_data++; - } - } - } - break; - case 8: - if (hasAlphaChannel) { - for (int y = 0; y < imageHeight; y++) { - const uint8_t *src_byte = src + (y * stride); - uint32_t *dest_pixel = reinterpret_cast(m_current_image.scanLine(y)); - for (int x = 0; x < imageWidth; x++) { - int red = *src_byte++; - int green = *src_byte++; - int blue = *src_byte++; - int alpha = *src_byte++; - *dest_pixel = qRgba(red, green, blue, alpha); - dest_pixel++; - } - } - } else { // no alpha channel - for (int y = 0; y < imageHeight; y++) { - const uint8_t *src_byte = src + (y * stride); - uint32_t *dest_pixel = reinterpret_cast(m_current_image.scanLine(y)); - for (int x = 0; x < imageWidth; x++) { - int red = *src_byte++; - int green = *src_byte++; - int blue = *src_byte++; - *dest_pixel = qRgb(red, green, blue); - dest_pixel++; - } - } - } - break; - default: - m_parseState = ParseHeicError; - qWarning() << "Unsupported bit depth:" << bit_depth; - return false; - break; - } - - heif_color_profile_type profileType = heif_image_handle_get_color_profile_type(handle.get_raw_image_handle()); - struct heif_error err; - if (profileType == heif_color_profile_type_prof || profileType == heif_color_profile_type_rICC) { - size_t rawProfileSize = heif_image_handle_get_raw_color_profile_size(handle.get_raw_image_handle()); - if (rawProfileSize > 0 && rawProfileSize < std::numeric_limits::max()) { - QByteArray ba(rawProfileSize, 0); - err = heif_image_handle_get_raw_color_profile(handle.get_raw_image_handle(), ba.data()); - if (err.code) { - qWarning() << "icc profile loading failed"; - } else { - m_current_image.setColorSpace(QColorSpace::fromIccProfile(ba)); - if (!m_current_image.colorSpace().isValid()) { - qWarning() << "HEIC image has Qt-unsupported or invalid ICC profile!"; - } - } - } else { - qWarning() << "icc profile is empty or above limits"; - } - - } else if (profileType == heif_color_profile_type_nclx) { - struct heif_color_profile_nclx *nclx = nullptr; - err = heif_image_handle_get_nclx_color_profile(handle.get_raw_image_handle(), &nclx); - if (err.code || !nclx) { - qWarning() << "nclx profile loading failed"; - } else { - const QPointF redPoint(nclx->color_primary_red_x, nclx->color_primary_red_y); - const QPointF greenPoint(nclx->color_primary_green_x, nclx->color_primary_green_y); - const QPointF bluePoint(nclx->color_primary_blue_x, nclx->color_primary_blue_y); - const QPointF whitePoint(nclx->color_primary_white_x, nclx->color_primary_white_y); - - QColorSpace::TransferFunction q_trc = QColorSpace::TransferFunction::Custom; - float q_trc_gamma = 0.0f; - - switch (nclx->transfer_characteristics) { - case 4: - q_trc = QColorSpace::TransferFunction::Gamma; - q_trc_gamma = 2.2f; - break; - case 5: - q_trc = QColorSpace::TransferFunction::Gamma; - q_trc_gamma = 2.8f; - break; - case 8: - q_trc = QColorSpace::TransferFunction::Linear; - break; - case 2: - case 13: - q_trc = QColorSpace::TransferFunction::SRgb; - break; - default: - qWarning("CICP color_primaries: %d, transfer_characteristics: %d\nThe colorspace is unsupported by this plug-in yet.", - nclx->color_primaries, - nclx->transfer_characteristics); - q_trc = QColorSpace::TransferFunction::SRgb; - break; - } - - if (q_trc != QColorSpace::TransferFunction::Custom) { // we create new colorspace using Qt - switch (nclx->color_primaries) { - case 1: - case 2: - m_current_image.setColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, q_trc, q_trc_gamma)); - break; - case 12: - m_current_image.setColorSpace(QColorSpace(QColorSpace::Primaries::DciP3D65, q_trc, q_trc_gamma)); - break; - default: - m_current_image.setColorSpace(QColorSpace(whitePoint, redPoint, greenPoint, bluePoint, q_trc, q_trc_gamma)); - break; - } - } - heif_nclx_color_profile_free(nclx); - - if (!m_current_image.colorSpace().isValid()) { - qWarning() << "HEIC plugin created invalid QColorSpace from NCLX!"; - } - } - - } else { - m_current_image.setColorSpace(QColorSpace(QColorSpace::SRgb)); - } - - } catch (const heif::Error &err) { + if (err.code) { + heif_context_free(ctx); + qWarning() << "heif_context_read_from_memory error:" << err.message; m_parseState = ParseHeicError; - qWarning() << "libheif error:" << err.get_message().c_str(); return false; } + struct heif_image_handle *handle = nullptr; + err = heif_context_get_primary_image_handle(ctx, &handle); + if (err.code) { + heif_context_free(ctx); + qWarning() << "heif_context_get_primary_image_handle error:" << err.message; + m_parseState = ParseHeicError; + return false; + } + + const bool hasAlphaChannel = heif_image_handle_has_alpha_channel(handle); + const int bit_depth = heif_image_handle_get_luma_bits_per_pixel(handle); + heif_chroma chroma; + + QImage::Format target_image_format; + + if (bit_depth == 10 || bit_depth == 12) { + if (hasAlphaChannel) { + chroma = (QSysInfo::ByteOrder == QSysInfo::LittleEndian) ? heif_chroma_interleaved_RRGGBBAA_LE : heif_chroma_interleaved_RRGGBBAA_BE; + target_image_format = QImage::Format_RGBA64; + } else { + chroma = (QSysInfo::ByteOrder == QSysInfo::LittleEndian) ? heif_chroma_interleaved_RRGGBB_LE : heif_chroma_interleaved_RRGGBB_BE; + target_image_format = QImage::Format_RGBX64; + } + } else if (bit_depth == 8) { + if (hasAlphaChannel) { + chroma = heif_chroma_interleaved_RGBA; + target_image_format = QImage::Format_ARGB32; + } else { + chroma = heif_chroma_interleaved_RGB; + target_image_format = QImage::Format_RGB32; + } + } else { + m_parseState = ParseHeicError; + heif_image_handle_release(handle); + heif_context_free(ctx); + if (bit_depth > 0) { + qWarning() << "Unsupported bit depth:" << bit_depth; + } else { + qWarning() << "Undefined bit depth."; + } + return false; + } + + struct heif_decoding_options *decoder_option = heif_decoding_options_alloc(); + +#if LIBHEIF_HAVE_VERSION(1, 13, 0) + decoder_option->strict_decoding = 1; +#endif + + struct heif_image *img = nullptr; + err = heif_decode_image(handle, &img, heif_colorspace_RGB, chroma, decoder_option); + + if (decoder_option) { + heif_decoding_options_free(decoder_option); + } + + if (err.code) { + heif_image_handle_release(handle); + heif_context_free(ctx); + qWarning() << "heif_decode_image error:" << err.message; + m_parseState = ParseHeicError; + return false; + } + + const int imageWidth = heif_image_get_width(img, heif_channel_interleaved); + const int imageHeight = heif_image_get_height(img, heif_channel_interleaved); + + QSize imageSize(imageWidth, imageHeight); + + if (!imageSize.isValid()) { + heif_image_release(img); + heif_image_handle_release(handle); + heif_context_free(ctx); + m_parseState = ParseHeicError; + qWarning() << "HEIC image size invalid:" << imageSize; + return false; + } + + int stride = 0; + const uint8_t *const src = heif_image_get_plane_readonly(img, heif_channel_interleaved, &stride); + + if (!src || stride <= 0) { + heif_image_release(img); + heif_image_handle_release(handle); + heif_context_free(ctx); + m_parseState = ParseHeicError; + qWarning() << "HEIC data pixels information not valid!"; + return false; + } + + m_current_image = imageAlloc(imageSize, target_image_format); + if (m_current_image.isNull()) { + heif_image_release(img); + heif_image_handle_release(handle); + heif_context_free(ctx); + m_parseState = ParseHeicError; + qWarning() << "Unable to allocate memory!"; + return false; + } + + switch (bit_depth) { + case 12: + if (hasAlphaChannel) { + for (int y = 0; y < imageHeight; y++) { + const uint16_t *src_word = reinterpret_cast(src + (y * stride)); + uint16_t *dest_data = reinterpret_cast(m_current_image.scanLine(y)); + for (int x = 0; x < imageWidth; x++) { + int tmpvalue; + // R + tmpvalue = (int)(((float)(0x0fff & (*src_word)) / 4095.0f) * 65535.0f + 0.5f); + tmpvalue = qBound(0, tmpvalue, 65535); + *dest_data = (uint16_t)tmpvalue; + src_word++; + dest_data++; + // G + tmpvalue = (int)(((float)(0x0fff & (*src_word)) / 4095.0f) * 65535.0f + 0.5f); + tmpvalue = qBound(0, tmpvalue, 65535); + *dest_data = (uint16_t)tmpvalue; + src_word++; + dest_data++; + // B + tmpvalue = (int)(((float)(0x0fff & (*src_word)) / 4095.0f) * 65535.0f + 0.5f); + tmpvalue = qBound(0, tmpvalue, 65535); + *dest_data = (uint16_t)tmpvalue; + src_word++; + dest_data++; + // A + tmpvalue = (int)(((float)(0x0fff & (*src_word)) / 4095.0f) * 65535.0f + 0.5f); + tmpvalue = qBound(0, tmpvalue, 65535); + *dest_data = (uint16_t)tmpvalue; + src_word++; + dest_data++; + } + } + } else { // no alpha channel + for (int y = 0; y < imageHeight; y++) { + const uint16_t *src_word = reinterpret_cast(src + (y * stride)); + uint16_t *dest_data = reinterpret_cast(m_current_image.scanLine(y)); + for (int x = 0; x < imageWidth; x++) { + int tmpvalue; + // R + tmpvalue = (int)(((float)(0x0fff & (*src_word)) / 4095.0f) * 65535.0f + 0.5f); + tmpvalue = qBound(0, tmpvalue, 65535); + *dest_data = (uint16_t)tmpvalue; + src_word++; + dest_data++; + // G + tmpvalue = (int)(((float)(0x0fff & (*src_word)) / 4095.0f) * 65535.0f + 0.5f); + tmpvalue = qBound(0, tmpvalue, 65535); + *dest_data = (uint16_t)tmpvalue; + src_word++; + dest_data++; + // B + tmpvalue = (int)(((float)(0x0fff & (*src_word)) / 4095.0f) * 65535.0f + 0.5f); + tmpvalue = qBound(0, tmpvalue, 65535); + *dest_data = (uint16_t)tmpvalue; + src_word++; + dest_data++; + // X = 0xffff + *dest_data = 0xffff; + dest_data++; + } + } + } + break; + case 10: + if (hasAlphaChannel) { + for (int y = 0; y < imageHeight; y++) { + const uint16_t *src_word = reinterpret_cast(src + (y * stride)); + uint16_t *dest_data = reinterpret_cast(m_current_image.scanLine(y)); + for (int x = 0; x < imageWidth; x++) { + int tmpvalue; + // R + tmpvalue = (int)(((float)(0x03ff & (*src_word)) / 1023.0f) * 65535.0f + 0.5f); + tmpvalue = qBound(0, tmpvalue, 65535); + *dest_data = (uint16_t)tmpvalue; + src_word++; + dest_data++; + // G + tmpvalue = (int)(((float)(0x03ff & (*src_word)) / 1023.0f) * 65535.0f + 0.5f); + tmpvalue = qBound(0, tmpvalue, 65535); + *dest_data = (uint16_t)tmpvalue; + src_word++; + dest_data++; + // B + tmpvalue = (int)(((float)(0x03ff & (*src_word)) / 1023.0f) * 65535.0f + 0.5f); + tmpvalue = qBound(0, tmpvalue, 65535); + *dest_data = (uint16_t)tmpvalue; + src_word++; + dest_data++; + // A + tmpvalue = (int)(((float)(0x03ff & (*src_word)) / 1023.0f) * 65535.0f + 0.5f); + tmpvalue = qBound(0, tmpvalue, 65535); + *dest_data = (uint16_t)tmpvalue; + src_word++; + dest_data++; + } + } + } else { // no alpha channel + for (int y = 0; y < imageHeight; y++) { + const uint16_t *src_word = reinterpret_cast(src + (y * stride)); + uint16_t *dest_data = reinterpret_cast(m_current_image.scanLine(y)); + for (int x = 0; x < imageWidth; x++) { + int tmpvalue; + // R + tmpvalue = (int)(((float)(0x03ff & (*src_word)) / 1023.0f) * 65535.0f + 0.5f); + tmpvalue = qBound(0, tmpvalue, 65535); + *dest_data = (uint16_t)tmpvalue; + src_word++; + dest_data++; + // G + tmpvalue = (int)(((float)(0x03ff & (*src_word)) / 1023.0f) * 65535.0f + 0.5f); + tmpvalue = qBound(0, tmpvalue, 65535); + *dest_data = (uint16_t)tmpvalue; + src_word++; + dest_data++; + // B + tmpvalue = (int)(((float)(0x03ff & (*src_word)) / 1023.0f) * 65535.0f + 0.5f); + tmpvalue = qBound(0, tmpvalue, 65535); + *dest_data = (uint16_t)tmpvalue; + src_word++; + dest_data++; + // X = 0xffff + *dest_data = 0xffff; + dest_data++; + } + } + } + break; + case 8: + if (hasAlphaChannel) { + for (int y = 0; y < imageHeight; y++) { + const uint8_t *src_byte = src + (y * stride); + uint32_t *dest_pixel = reinterpret_cast(m_current_image.scanLine(y)); + for (int x = 0; x < imageWidth; x++) { + int red = *src_byte++; + int green = *src_byte++; + int blue = *src_byte++; + int alpha = *src_byte++; + *dest_pixel = qRgba(red, green, blue, alpha); + dest_pixel++; + } + } + } else { // no alpha channel + for (int y = 0; y < imageHeight; y++) { + const uint8_t *src_byte = src + (y * stride); + uint32_t *dest_pixel = reinterpret_cast(m_current_image.scanLine(y)); + for (int x = 0; x < imageWidth; x++) { + int red = *src_byte++; + int green = *src_byte++; + int blue = *src_byte++; + *dest_pixel = qRgb(red, green, blue); + dest_pixel++; + } + } + } + break; + default: + heif_image_release(img); + heif_image_handle_release(handle); + heif_context_free(ctx); + m_parseState = ParseHeicError; + qWarning() << "Unsupported bit depth:" << bit_depth; + return false; + break; + } + + heif_color_profile_type profileType = heif_image_handle_get_color_profile_type(handle); + if (profileType == heif_color_profile_type_prof || profileType == heif_color_profile_type_rICC) { + size_t rawProfileSize = heif_image_handle_get_raw_color_profile_size(handle); + if (rawProfileSize > 0 && rawProfileSize < std::numeric_limits::max()) { + QByteArray ba(rawProfileSize, 0); + err = heif_image_handle_get_raw_color_profile(handle, ba.data()); + if (err.code) { + qWarning() << "icc profile loading failed"; + } else { + m_current_image.setColorSpace(QColorSpace::fromIccProfile(ba)); + if (!m_current_image.colorSpace().isValid()) { + qWarning() << "HEIC image has Qt-unsupported or invalid ICC profile!"; + } + } + } else { + qWarning() << "icc profile is empty or above limits"; + } + + } else if (profileType == heif_color_profile_type_nclx) { + struct heif_color_profile_nclx *nclx = nullptr; + err = heif_image_handle_get_nclx_color_profile(handle, &nclx); + if (err.code || !nclx) { + qWarning() << "nclx profile loading failed"; + } else { + const QPointF redPoint(nclx->color_primary_red_x, nclx->color_primary_red_y); + const QPointF greenPoint(nclx->color_primary_green_x, nclx->color_primary_green_y); + const QPointF bluePoint(nclx->color_primary_blue_x, nclx->color_primary_blue_y); + const QPointF whitePoint(nclx->color_primary_white_x, nclx->color_primary_white_y); + + QColorSpace::TransferFunction q_trc = QColorSpace::TransferFunction::Custom; + float q_trc_gamma = 0.0f; + + switch (nclx->transfer_characteristics) { + case 4: + q_trc = QColorSpace::TransferFunction::Gamma; + q_trc_gamma = 2.2f; + break; + case 5: + q_trc = QColorSpace::TransferFunction::Gamma; + q_trc_gamma = 2.8f; + break; + case 8: + q_trc = QColorSpace::TransferFunction::Linear; + break; + case 2: + case 13: + q_trc = QColorSpace::TransferFunction::SRgb; + break; + default: + qWarning("CICP color_primaries: %d, transfer_characteristics: %d\nThe colorspace is unsupported by this plug-in yet.", + nclx->color_primaries, + nclx->transfer_characteristics); + q_trc = QColorSpace::TransferFunction::SRgb; + break; + } + + if (q_trc != QColorSpace::TransferFunction::Custom) { // we create new colorspace using Qt + switch (nclx->color_primaries) { + case 1: + case 2: + m_current_image.setColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, q_trc, q_trc_gamma)); + break; + case 12: + m_current_image.setColorSpace(QColorSpace(QColorSpace::Primaries::DciP3D65, q_trc, q_trc_gamma)); + break; + default: + m_current_image.setColorSpace(QColorSpace(whitePoint, redPoint, greenPoint, bluePoint, q_trc, q_trc_gamma)); + break; + } + } + heif_nclx_color_profile_free(nclx); + + if (!m_current_image.colorSpace().isValid()) { + qWarning() << "HEIC plugin created invalid QColorSpace from NCLX!"; + } + } + + } else { + m_current_image.setColorSpace(QColorSpace(QColorSpace::SRgb)); + } + + heif_image_release(img); + heif_image_handle_release(handle); + heif_context_free(ctx); m_parseState = ParseHeicSuccess; return true; }