From ca52d4ddf540cc731d0f31dbccc1897f7e6d9db4 Mon Sep 17 00:00:00 2001 From: Daniel Novomesky Date: Fri, 12 Feb 2021 13:27:53 +0100 Subject: [PATCH] Add plugin for High Efficiency Image File Format (HEIF) Code partially by Sirius Bakke --- CMakeLists.txt | 9 +- autotests/CMakeLists.txt | 6 + autotests/read/heif/rgb.heif | Bin 0 -> 4354 bytes autotests/read/heif/rgb.png | Bin 0 -> 3668 bytes autotests/read/heif/rgba.heif | Bin 0 -> 6655 bytes autotests/read/heif/rgba.png | Bin 0 -> 3978 bytes src/imageformats/CMakeLists.txt | 9 + src/imageformats/heif.cpp | 734 ++++++++++++++++++++++++++++++++ src/imageformats/heif.desktop | 7 + src/imageformats/heif.json | 4 + src/imageformats/heif_p.h | 55 +++ 11 files changed, 823 insertions(+), 1 deletion(-) create mode 100644 autotests/read/heif/rgb.heif create mode 100644 autotests/read/heif/rgb.png create mode 100644 autotests/read/heif/rgba.heif create mode 100644 autotests/read/heif/rgba.png create mode 100644 src/imageformats/heif.cpp create mode 100644 src/imageformats/heif.desktop create mode 100644 src/imageformats/heif.json create mode 100644 src/imageformats/heif_p.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 3f9f61c..b7bbd69 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.5) +cmake_minimum_required(VERSION 3.6) project(KImageFormats) @@ -54,6 +54,13 @@ set_package_properties(libavif PROPERTIES PURPOSE "Required for the QImage plugin for AVIF images" ) +option(KIMAGEFORMATS_HEIF "Enable plugin for HEIF format" OFF) +if(KIMAGEFORMATS_HEIF) + include(FindPkgConfig) + pkg_check_modules(LibHeif IMPORTED_TARGET libheif>=1.10.0) +endif() +add_feature_info(LibHeif LibHeif_FOUND "required for the QImage plugin for HEIF/HEIC images") + add_definitions(-DQT_NO_FOREACH) # 050d00 (5.13) triggers a BIC in qimageiohandler.h, in Qt 5.13, so do not enable that until we can require 5.14 # https://codereview.qt-project.org/c/qt/qtbase/+/279215 diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt index 8af6256..2de3b9c 100644 --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -76,6 +76,12 @@ if (TARGET avif) ) endif() +if (LibHeif_FOUND) + kimageformats_read_tests( + heif + ) +endif() + # Allow some fuzziness when reading this formats, to allow for # rounding errors (eg: in alpha blending). kimageformats_read_tests(FUZZ 1 diff --git a/autotests/read/heif/rgb.heif b/autotests/read/heif/rgb.heif new file mode 100644 index 0000000000000000000000000000000000000000..811b2d375abaed56134a2a749c2521d7364e7131 GIT binary patch literal 4354 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 literal 0 HcmV?d00001 diff --git a/autotests/read/heif/rgba.heif b/autotests/read/heif/rgba.heif new file mode 100644 index 0000000000000000000000000000000000000000..30aff5da529c15866553e36ba65e2e5ba2613c1e GIT binary patch literal 6655 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 PECT}_6K#SP_R;?V?8Qw9 literal 0 HcmV?d00001 diff --git a/src/imageformats/CMakeLists.txt b/src/imageformats/CMakeLists.txt index 1888386..6d41198 100644 --- a/src/imageformats/CMakeLists.txt +++ b/src/imageformats/CMakeLists.txt @@ -71,6 +71,15 @@ install(FILES hdr.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}/qimageioplugi ################################## +if (LibHeif_FOUND) + kimageformats_add_plugin(kimg_heif JSON "heif.json" SOURCES heif.cpp) + target_link_libraries(kimg_heif PkgConfig::LibHeif) + kde_target_enable_exceptions(kimg_heif PRIVATE) + install(FILES heif.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}/qimageioplugins/) +endif() + +################################## + kimageformats_add_plugin(kimg_pcx JSON "pcx.json" SOURCES pcx.cpp) install(FILES pcx.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}/qimageioplugins/) diff --git a/src/imageformats/heif.cpp b/src/imageformats/heif.cpp new file mode 100644 index 0000000..d399273 --- /dev/null +++ b/src/imageformats/heif.cpp @@ -0,0 +1,734 @@ +/* + High Efficiency Image File Format (HEIF) support for QImage. + + SPDX-FileCopyrightText: 2020 Sirius Bakke + SPDX-FileCopyrightText: 2021 Daniel Novomesky + + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#include "heif_p.h" +#include "libheif/heif_cxx.h" + +#include +#include +#include +#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 + +HEIFHandler::HEIFHandler() : + m_parseState(ParseHeicNotParsed), + m_quality(100) +{ +} + +bool HEIFHandler::canRead() const +{ + if (m_parseState == ParseHeicNotParsed && !canRead(device())) { + return false; + } + + if (m_parseState != ParseHeicError) { + setFormat("heif"); + return true; + } + return false; +} + +bool HEIFHandler::read(QImage *outImage) +{ + if (!ensureParsed()) { + return false; + } + + *outImage = m_current_image; + return true; +} + +bool HEIFHandler::write(const QImage &image) +{ + if (image.format() == QImage::Format_Invalid || image.isNull()) { + qWarning("No image data to save"); + return false; + } + + int save_depth; //8 or 10bit per channel + QImage::Format tmpformat; //format for temporary image + const bool save_alpha = image.hasAlphaChannel(); + + switch (image.format()) { + case QImage::Format_BGR30: + case QImage::Format_A2BGR30_Premultiplied: + case QImage::Format_RGB30: + case QImage::Format_A2RGB30_Premultiplied: + case QImage::Format_Grayscale16: + case QImage::Format_RGBX64: + case QImage::Format_RGBA64: + case QImage::Format_RGBA64_Premultiplied: + save_depth = 10; + break; + default: + if (image.depth() > 32) { + save_depth = 10; + } else { + save_depth = 8; + } + break; + } + + heif_chroma chroma; + if (save_depth > 8) { + if (save_alpha) { + tmpformat = QImage::Format_RGBA64; + chroma = (QSysInfo::ByteOrder == QSysInfo::LittleEndian) ? heif_chroma_interleaved_RRGGBBAA_LE : heif_chroma_interleaved_RRGGBBAA_BE; + } else { + tmpformat = QImage::Format_RGBX64; + chroma = (QSysInfo::ByteOrder == QSysInfo::LittleEndian) ? heif_chroma_interleaved_RRGGBB_LE : heif_chroma_interleaved_RRGGBB_BE; + } + } else { + if (save_alpha) { + tmpformat = QImage::Format_RGBA8888; + chroma = heif_chroma_interleaved_RGBA; + } else { + tmpformat = QImage::Format_RGB888; + chroma = heif_chroma_interleaved_RGB; + } + + } + + const QImage tmpimage = image.convertToFormat(tmpformat); + + try { + heif::Context ctx; + heif::Image heifImage; + heifImage.create(tmpimage.width(), tmpimage.height(), heif_colorspace_RGB, chroma); + + if (tmpimage.colorSpace().isValid()) { + 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); + } + } + + 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; + + 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); + for (int y = 0; y < tmpimage.height(); y++) { + memcpy(dst + (y * stride), tmpimage.constScanLine(y), rowbytes); + } + 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; + } + } + + ctx.encode_image(heifImage, encoder, encodingOptions); + + HeifQIODeviceWriter writer(device()); + + ctx.write(writer); + + } catch (const heif::Error &err) { + qWarning() << "libheif error:" << err.get_message().c_str(); + return false; + } + + return true; +} + +bool HEIFHandler::canRead(QIODevice *device) +{ + if (!device) { + qWarning("HEIFHandler::canRead() called with no device"); + return false; + } + + const QByteArray header = device->peek(28); + if (header.size() < 28) { + return false; + } + + const char *buffer = header.constData(); + if (qstrncmp(buffer + 4, "ftyp", 4) == 0) { + if (qstrncmp(buffer + 8, "heic", 4) == 0) { + return true; + } + if (qstrncmp(buffer + 8, "heis", 4) == 0) { + return true; + } + if (qstrncmp(buffer + 8, "heix", 4) == 0) { + return true; + } + + /* we want to avoid loading AVIF files via this plugin */ + if (qstrncmp(buffer + 8, "mif1", 4) == 0) { + for (int offset = 16; offset <= 24; offset += 4) { + if (qstrncmp(buffer + offset, "avif", 4) == 0) { + return false; + } + } + return true; + } + + if (qstrncmp(buffer + 8, "mif2", 4) == 0) { + return true; + } + if (qstrncmp(buffer + 8, "msf1", 4) == 0) { + return true; + } + } + + return false; +} + +QVariant HEIFHandler::option(ImageOption option) const +{ + if (option == Quality) { + return m_quality; + } + + if (!supportsOption(option) || !ensureParsed()) { + return QVariant(); + } + + switch (option) { + case Size: + return m_current_image.size(); + break; + default: + return QVariant(); + break; + } +} + +void HEIFHandler::setOption(ImageOption option, const QVariant &value) +{ + switch (option) { + case Quality: + m_quality = value.toInt(); + if (m_quality > 100) { + m_quality = 100; + } else if (m_quality < 0) { + m_quality = 100; + } + break; + default: + QImageIOHandler::setOption(option, value); + break; + } +} + +bool HEIFHandler::supportsOption(ImageOption option) const +{ + return option == Quality + || option == Size; +} + +bool HEIFHandler::ensureParsed() const +{ + if (m_parseState == ParseHeicSuccess) { + return true; + } + if (m_parseState == ParseHeicError) { + return false; + } + + HEIFHandler *that = const_cast(this); + + return that->ensureDecoder(); +} +bool HEIFHandler::ensureDecoder() +{ + if (m_parseState != ParseHeicNotParsed) { + if (m_parseState == ParseHeicSuccess) { + return true; + } + return false; + } + + const QByteArray buffer = device()->readAll(); + if (buffer.isEmpty()) { + m_parseState = ParseHeicError; + return false; + } + + try { + heif::Context ctx; + ctx.read_from_memory_without_copy((const void *)(buffer.constData()), + buffer.size()); + + 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 = QImage(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) { + int rawProfileSize = (int) heif_image_handle_get_raw_color_profile_size(handle.get_raw_image_handle()); + if (rawProfileSize > 0) { + 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() << "icc profile is invalid"; + } + } + } else { + qWarning() << "icc profile is empty"; + } + + } 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() << "invalid color profile created from NCLX"; + } + + } + + } else { + m_current_image.setColorSpace(QColorSpace(QColorSpace::SRgb)); + } + + } catch (const heif::Error &err) { + m_parseState = ParseHeicError; + qWarning() << "libheif error:" << err.get_message().c_str(); + return false; + } + + m_parseState = ParseHeicSuccess; + return true; +} + +QImageIOPlugin::Capabilities HEIFPlugin::capabilities(QIODevice *device, const QByteArray &format) const +{ + if (format == "heif" || format == "heic") { + Capabilities format_cap; + if (heif_have_decoder_for_format(heif_compression_HEVC)) { + format_cap |= CanRead; + } + if (heif_have_encoder_for_format(heif_compression_HEVC)) { + format_cap |= CanWrite; + } + return format_cap; + } + if (!format.isEmpty()) { + return {}; + } + if (!device->isOpen()) { + return {}; + } + + Capabilities cap; + if (device->isReadable() && HEIFHandler::canRead(device) && heif_have_decoder_for_format(heif_compression_HEVC)) { + cap |= CanRead; + } + + if (device->isWritable() && heif_have_encoder_for_format(heif_compression_HEVC)) { + cap |= CanWrite; + } + return cap; +} + +QImageIOHandler *HEIFPlugin::create(QIODevice *device, const QByteArray &format) const +{ + QImageIOHandler *handler = new HEIFHandler; + handler->setDevice(device); + handler->setFormat(format); + return handler; +} diff --git a/src/imageformats/heif.desktop b/src/imageformats/heif.desktop new file mode 100644 index 0000000..049b5fd --- /dev/null +++ b/src/imageformats/heif.desktop @@ -0,0 +1,7 @@ +[Desktop Entry] +Type=Service +X-KDE-ServiceTypes=QImageIOPlugins +X-KDE-ImageFormat=heif +X-KDE-MimeType=image/heif +X-KDE-Read=true +X-KDE-Write=true diff --git a/src/imageformats/heif.json b/src/imageformats/heif.json new file mode 100644 index 0000000..02307c2 --- /dev/null +++ b/src/imageformats/heif.json @@ -0,0 +1,4 @@ +{ + "Keys": [ "heif", "heic" ], + "MimeTypes": [ "image/heif", "image/heif" ] +} diff --git a/src/imageformats/heif_p.h b/src/imageformats/heif_p.h new file mode 100644 index 0000000..dd3e342 --- /dev/null +++ b/src/imageformats/heif_p.h @@ -0,0 +1,55 @@ +/* + High Efficiency Image File Format (HEIF) support for QImage. + + SPDX-FileCopyrightText: 2020 Sirius Bakke + SPDX-FileCopyrightText: 2021 Daniel Novomesky + + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#ifndef KIMG_HEIF_P_H +#define KIMG_HEIF_P_H + +#include +#include + +class HEIFHandler : public QImageIOHandler +{ +public: + HEIFHandler(); + + bool canRead() const override; + bool read(QImage *image) override; + bool write(const QImage &image) override; + + static bool canRead(QIODevice *device); + + QVariant option(ImageOption option) const override; + void setOption(ImageOption option, const QVariant &value) override; + bool supportsOption(ImageOption option) const override; +private: + bool ensureParsed() const; + bool ensureDecoder(); + + enum ParseHeicState { + ParseHeicError = -1, + ParseHeicNotParsed = 0, + ParseHeicSuccess = 1 + }; + + ParseHeicState m_parseState; + int m_quality; + QImage m_current_image; +}; + +class HEIFPlugin : public QImageIOPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QImageIOHandlerFactoryInterface" FILE "heif.json") + +public: + Capabilities capabilities(QIODevice *device, const QByteArray &format) const override; + QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const override; +}; + +#endif // KIMG_HEIF_P_H