Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package kimageformats for openSUSE:Factory checked in at 2021-03-16 15:40:45 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/kimageformats (Old) and /work/SRC/openSUSE:Factory/.kimageformats.new.2401 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "kimageformats" Tue Mar 16 15:40:45 2021 rev:90 rq:878814 version:5.80.0 Changes: -------- --- /work/SRC/openSUSE:Factory/kimageformats/kimageformats.changes 2021-02-17 18:12:10.677971919 +0100 +++ /work/SRC/openSUSE:Factory/.kimageformats.new.2401/kimageformats.changes 2021-03-16 15:42:02.656810557 +0100 @@ -1,0 +2,14 @@ +Sun Mar 7 09:26:56 UTC 2021 - Christophe Giboudeaux <[email protected]> + +- Update to 5.80.0 + * New feature release + * For more details please see: + * https://kde.org/announcements/frameworks/5/5.80.0 +- Changes since 5.79.0: + * Fix Non-square Radiance/RGBE/.hdr images failing to load (kde#433877) + * Check the input buffer before passing it to libheif + * Check primaries returned from libavif + * Add plugin for High Efficiency Image File Format (HEIF) + * Quality option can be returned without parsing input file. + +------------------------------------------------------------------- Old: ---- kimageformats-5.79.0.tar.xz kimageformats-5.79.0.tar.xz.sig New: ---- kimageformats-5.80.0.tar.xz kimageformats-5.80.0.tar.xz.sig ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ kimageformats.spec ++++++ --- /var/tmp/diff_new_pack.WXttdy/_old 2021-03-16 15:42:03.204811433 +0100 +++ /var/tmp/diff_new_pack.WXttdy/_new 2021-03-16 15:42:03.204811433 +0100 @@ -19,7 +19,7 @@ %if 0%{?suse_version} > 1500 %define with_avif 1 %endif -%define _tar_path 5.79 +%define _tar_path 5.80 # Full KF5 version (e.g. 5.33.0) %{!?_kf5_version: %global _kf5_version %{version}} # Last major and minor KF5 version (e.g. 5.33) @@ -27,7 +27,7 @@ # Only needed for the package signature condition %bcond_without lang Name: kimageformats -Version: 5.79.0 +Version: 5.80.0 Release: 0 Summary: Image format plugins for Qt License: LGPL-2.1-or-later ++++++ kimageformats-5.79.0.tar.xz -> kimageformats-5.80.0.tar.xz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kimageformats-5.79.0/.gitignore new/kimageformats-5.80.0/.gitignore --- old/kimageformats-5.79.0/.gitignore 2021-02-02 09:28:32.000000000 +0100 +++ new/kimageformats-5.80.0/.gitignore 2021-03-04 22:57:23.000000000 +0100 @@ -21,3 +21,4 @@ *.unc-backup* .cmake/ /.clang-format +/compile_commands.json diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kimageformats-5.79.0/CMakeLists.txt new/kimageformats-5.80.0/CMakeLists.txt --- old/kimageformats-5.79.0/CMakeLists.txt 2021-02-02 09:28:32.000000000 +0100 +++ new/kimageformats-5.80.0/CMakeLists.txt 2021-03-04 22:57:23.000000000 +0100 @@ -1,11 +1,11 @@ -cmake_minimum_required(VERSION 3.5) +cmake_minimum_required(VERSION 3.6) project(KImageFormats) set (CMAKE_CXX_STANDARD 14) include(FeatureSummary) -find_package(ECM 5.79.0 NO_MODULE) +find_package(ECM 5.80.0 NO_MODULE) set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://commits.kde.org/extra-cmake-modules") feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES) @@ -54,11 +54,18 @@ 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 add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x050e00) -add_definitions(-DKF_DISABLE_DEPRECATED_BEFORE_AND_AT=0x054B00) +add_definitions(-DKF_DISABLE_DEPRECATED_BEFORE_AND_AT=0x054F00) add_subdirectory(src) if (BUILD_TESTING) add_subdirectory(autotests) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kimageformats-5.79.0/autotests/CMakeLists.txt new/kimageformats-5.80.0/autotests/CMakeLists.txt --- old/kimageformats-5.79.0/autotests/CMakeLists.txt 2021-02-02 09:28:32.000000000 +0100 +++ new/kimageformats-5.80.0/autotests/CMakeLists.txt 2021-03-04 22:57:23.000000000 +0100 @@ -76,6 +76,12 @@ ) 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 Binary files old/kimageformats-5.79.0/autotests/read/hdr/rgb-landscape.hdr and new/kimageformats-5.80.0/autotests/read/hdr/rgb-landscape.hdr differ Binary files old/kimageformats-5.79.0/autotests/read/hdr/rgb-landscape.png and new/kimageformats-5.80.0/autotests/read/hdr/rgb-landscape.png differ Binary files old/kimageformats-5.79.0/autotests/read/hdr/rgb-portrait.hdr and new/kimageformats-5.80.0/autotests/read/hdr/rgb-portrait.hdr differ Binary files old/kimageformats-5.79.0/autotests/read/hdr/rgb-portrait.png and new/kimageformats-5.80.0/autotests/read/hdr/rgb-portrait.png differ Binary files old/kimageformats-5.79.0/autotests/read/heif/rgb.heif and new/kimageformats-5.80.0/autotests/read/heif/rgb.heif differ Binary files old/kimageformats-5.79.0/autotests/read/heif/rgb.png and new/kimageformats-5.80.0/autotests/read/heif/rgb.png differ Binary files old/kimageformats-5.79.0/autotests/read/heif/rgba.heif and new/kimageformats-5.80.0/autotests/read/heif/rgba.heif differ Binary files old/kimageformats-5.79.0/autotests/read/heif/rgba.png and new/kimageformats-5.80.0/autotests/read/heif/rgba.png differ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kimageformats-5.79.0/src/imageformats/CMakeLists.txt new/kimageformats-5.80.0/src/imageformats/CMakeLists.txt --- old/kimageformats-5.79.0/src/imageformats/CMakeLists.txt 2021-02-02 09:28:32.000000000 +0100 +++ new/kimageformats-5.80.0/src/imageformats/CMakeLists.txt 2021-03-04 22:57:23.000000000 +0100 @@ -71,6 +71,15 @@ ################################## +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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kimageformats-5.79.0/src/imageformats/avif.cpp new/kimageformats-5.80.0/src/imageformats/avif.cpp --- old/kimageformats-5.79.0/src/imageformats/avif.cpp 2021-02-02 09:28:32.000000000 +0100 +++ new/kimageformats-5.80.0/src/imageformats/avif.cpp 2021-03-04 22:57:23.000000000 +0100 @@ -12,6 +12,7 @@ #include <QColorSpace> #include "avif_p.h" +#include <cfloat> QAVIFHandler::QAVIFHandler() : @@ -102,7 +103,7 @@ decodeResult = avifDecoderSetIOMemory(m_decoder, m_rawAvifData.data, m_rawAvifData.size); if (decodeResult != AVIF_RESULT_OK) { - qWarning("ERROR: avifDecoderSetIOMemory failed: %s\n", avifResultToString(decodeResult)); + qWarning("ERROR: avifDecoderSetIOMemory failed: %s", avifResultToString(decodeResult)); avifDecoderDestroy(m_decoder); m_decoder = nullptr; @@ -112,7 +113,7 @@ decodeResult = avifDecoderParse(m_decoder); if (decodeResult != AVIF_RESULT_OK) { - qWarning("ERROR: Failed to parse input: %s\n", avifResultToString(decodeResult)); + qWarning("ERROR: Failed to parse input: %s", avifResultToString(decodeResult)); avifDecoderDestroy(m_decoder); m_decoder = nullptr; @@ -147,7 +148,7 @@ return false; } } else { - qWarning("ERROR: Failed to decode image: %s\n", avifResultToString(decodeResult)); + qWarning("ERROR: Failed to decode image: %s", avifResultToString(decodeResult)); } avifDecoderDestroy(m_decoder); @@ -195,16 +196,17 @@ if (m_decoder->image->icc.data && (m_decoder->image->icc.size > 0)) { result.setColorSpace(QColorSpace::fromIccProfile(QByteArray::fromRawData((const char *) m_decoder->image->icc.data, (int) m_decoder->image->icc.size))); if (! result.colorSpace().isValid()) { - qWarning("Invalid QColorSpace created from ICC!\n"); + qWarning("Invalid QColorSpace created from ICC!"); } } else { - float prim[8]; // outPrimaries: rX, rY, gX, gY, bX, bY, wX, wY + float prim[8] = { 0.64f, 0.33f, 0.3f, 0.6f, 0.15f, 0.06f, 0.3127f, 0.329f }; + // outPrimaries: rX, rY, gX, gY, bX, bY, wX, wY avifColorPrimariesGetValues(m_decoder->image->colorPrimaries, prim); - QPointF redPoint(prim[0], prim[1]); - QPointF greenPoint(prim[2], prim[3]); - QPointF bluePoint(prim[4], prim[5]); - QPointF whitePoint(prim[6], prim[7]); + const QPointF redPoint(QAVIFHandler::CompatibleChromacity(prim[0], prim[1])); + const QPointF greenPoint(QAVIFHandler::CompatibleChromacity(prim[2], prim[3])); + const QPointF bluePoint(QAVIFHandler::CompatibleChromacity(prim[4], prim[5])); + const QPointF whitePoint(QAVIFHandler::CompatibleChromacity(prim[6], prim[7])); QColorSpace::TransferFunction q_trc = QColorSpace::TransferFunction::Custom; @@ -257,7 +259,7 @@ } if (! result.colorSpace().isValid()) { - qWarning("Invalid QColorSpace created from NCLX/CICP!\n"); + qWarning("Invalid QColorSpace created from NCLX/CICP!"); } } @@ -296,7 +298,7 @@ avifResult res = avifImageYUVToRGB(m_decoder->image, &rgb); if (res != AVIF_RESULT_OK) { - qWarning("ERROR in avifImageYUVToRGB: %s\n", avifResultToString(res)); + qWarning("ERROR in avifImageYUVToRGB: %s", avifResultToString(res)); return false; } @@ -338,7 +340,7 @@ } else { //Zero values, we need to avoid 0 divide. - qWarning("ERROR: Wrong values in avifCleanApertureBox\n"); + qWarning("ERROR: Wrong values in avifCleanApertureBox"); } } @@ -680,7 +682,7 @@ res = avifImageRGBToYUV(avif, &rgb); if (res != AVIF_RESULT_OK) { - qWarning("ERROR in avifImageRGBToYUV: %s\n", avifResultToString(res)); + qWarning("ERROR in avifImageRGBToYUV: %s", avifResultToString(res)); return false; } } @@ -709,11 +711,11 @@ if (status > 0) { return true; } else if (status == -1) { - qWarning("Write error: %s\n", qUtf8Printable(device()->errorString())); + qWarning("Write error: %s", qUtf8Printable(device()->errorString())); return false; } } else { - qWarning("ERROR: Failed to encode: %s\n", avifResultToString(res)); + qWarning("ERROR: Failed to encode: %s", avifResultToString(res)); } return false; @@ -722,13 +724,15 @@ QVariant QAVIFHandler::option(ImageOption option) const { + if (option == Quality) { + return m_quality; + } + if (!supportsOption(option) || !ensureParsed()) { return QVariant(); } switch (option) { - case Quality: - return m_quality; case Size: return m_current_image.size(); case Animation: @@ -808,14 +812,14 @@ avifResult decodeResult = avifDecoderNextImage(m_decoder); if (decodeResult != AVIF_RESULT_OK) { - qWarning("ERROR: Failed to decode Next image in sequence: %s\n", avifResultToString(decodeResult)); + qWarning("ERROR: Failed to decode Next image in sequence: %s", avifResultToString(decodeResult)); m_parseState = ParseAvifError; return false; } if ((m_container_width != m_decoder->image->width) || (m_container_height != m_decoder->image->height)) { - qWarning("Decoded image sequence size (%dx%d) do not match first image size (%dx%d)!\n", + qWarning("Decoded image sequence size (%dx%d) do not match first image size (%dx%d)!", m_decoder->image->width, m_decoder->image->height, m_container_width, m_container_height); @@ -857,14 +861,14 @@ avifResult decodeResult = avifDecoderNthImage(m_decoder, imageNumber); if (decodeResult != AVIF_RESULT_OK) { - qWarning("ERROR: Failed to decode %d th Image in sequence: %s\n", imageNumber, avifResultToString(decodeResult)); + qWarning("ERROR: Failed to decode %d th Image in sequence: %s", imageNumber, avifResultToString(decodeResult)); m_parseState = ParseAvifError; return false; } if ((m_container_width != m_decoder->image->width) || (m_container_height != m_decoder->image->height)) { - qWarning("Decoded image sequence size (%dx%d) do not match declared container size (%dx%d)!\n", + qWarning("Decoded image sequence size (%dx%d) do not match declared container size (%dx%d)!", m_decoder->image->width, m_decoder->image->height, m_container_width, m_container_height); @@ -910,6 +914,18 @@ return 1; } +QPointF QAVIFHandler::CompatibleChromacity(qreal chrX, qreal chrY) +{ + chrX = qBound(qreal(0.0), chrX, qreal(1.0)); + chrY = qBound(qreal(DBL_MIN), chrY, qreal(1.0)); + + if ((chrX + chrY) > qreal(1.0)) { + chrX = qreal(1.0) - chrY; + } + + return QPointF(chrX, chrY); +} + QImageIOPlugin::Capabilities QAVIFPlugin::capabilities(QIODevice *device, const QByteArray &format) const { if (format == "avif") { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kimageformats-5.79.0/src/imageformats/avif_p.h new/kimageformats-5.80.0/src/imageformats/avif_p.h --- old/kimageformats-5.79.0/src/imageformats/avif_p.h 2021-02-02 09:28:32.000000000 +0100 +++ new/kimageformats-5.80.0/src/imageformats/avif_p.h 2021-03-04 22:57:23.000000000 +0100 @@ -14,6 +14,7 @@ #include <qimageiohandler.h> #include <QImageIOPlugin> #include <QByteArray> +#include <QPointF> #include <avif/avif.h> class QAVIFHandler : public QImageIOHandler @@ -41,6 +42,7 @@ int loopCount() const override; private: + static QPointF CompatibleChromacity(qreal chrX, qreal chrY); bool ensureParsed() const; bool ensureDecoder(); bool decode_one_frame(); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kimageformats-5.79.0/src/imageformats/hdr.cpp new/kimageformats-5.80.0/src/imageformats/hdr.cpp --- old/kimageformats-5.79.0/src/imageformats/hdr.cpp 2021-02-02 09:28:32.000000000 +0100 +++ new/kimageformats-5.80.0/src/imageformats/hdr.cpp 2021-03-04 22:57:23.000000000 +0100 @@ -227,8 +227,15 @@ qCDebug(HDRPLUGIN) << "Invalid HDR file, the first line after the header didn't have the expected format:" << line; return false; } - const int width = match.captured(2).toInt(); - const int height = match.captured(4).toInt(); + + if ( (match.captured(1).at(1) != u'Y') || + (match.captured(3).at(1) != u'X') ) { + qCDebug(HDRPLUGIN) << "Unsupported image orientation in HDR file."; + return false; + } + + const int width = match.captured(4).toInt(); + const int height = match.captured(2).toInt(); QDataStream s(device()); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kimageformats-5.79.0/src/imageformats/heif.cpp new/kimageformats-5.80.0/src/imageformats/heif.cpp --- old/kimageformats-5.79.0/src/imageformats/heif.cpp 1970-01-01 01:00:00.000000000 +0100 +++ new/kimageformats-5.80.0/src/imageformats/heif.cpp 2021-03-04 22:57:23.000000000 +0100 @@ -0,0 +1,739 @@ +/* + High Efficiency Image File Format (HEIF) support for QImage. + + SPDX-FileCopyrightText: 2020 Sirius Bakke <[email protected]> + SPDX-FileCopyrightText: 2021 Daniel Novomesky <[email protected]> + + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#include "heif_p.h" +#include "libheif/heif_cxx.h" + +#include <QPointF> +#include <QColorSpace> +#include <QDebug> +#include <QSysInfo> +#include <string.h> + +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<const char *>(data), size); + + if (bytesWritten < static_cast<qint64>(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<uint8_t> 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<const uint16_t *>(tmpimage.constScanLine(y)); + uint16_t *dest_word = reinterpret_cast<uint16_t *>(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<const uint16_t *>(tmpimage.constScanLine(y)); + uint16_t *dest_word = reinterpret_cast<uint16_t *>(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); + return HEIFHandler::isSupportedBMFFType(header); +} + +bool HEIFHandler::isSupportedBMFFType(const QByteArray &header) +{ + 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<HEIFHandler *>(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 (!HEIFHandler::isSupportedBMFFType(buffer)) { + 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<const uint16_t *>(src + (y * stride)); + uint16_t *dest_data = reinterpret_cast<uint16_t *>(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<const uint16_t *>(src + (y * stride)); + uint16_t *dest_data = reinterpret_cast<uint16_t *>(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<const uint16_t *>(src + (y * stride)); + uint16_t *dest_data = reinterpret_cast<uint16_t *>(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<const uint16_t *>(src + (y * stride)); + uint16_t *dest_data = reinterpret_cast<uint16_t *>(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<uint32_t *>(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<uint32_t *>(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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kimageformats-5.79.0/src/imageformats/heif.desktop new/kimageformats-5.80.0/src/imageformats/heif.desktop --- old/kimageformats-5.79.0/src/imageformats/heif.desktop 1970-01-01 01:00:00.000000000 +0100 +++ new/kimageformats-5.80.0/src/imageformats/heif.desktop 2021-03-04 22:57:23.000000000 +0100 @@ -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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kimageformats-5.79.0/src/imageformats/heif.json new/kimageformats-5.80.0/src/imageformats/heif.json --- old/kimageformats-5.79.0/src/imageformats/heif.json 1970-01-01 01:00:00.000000000 +0100 +++ new/kimageformats-5.80.0/src/imageformats/heif.json 2021-03-04 22:57:23.000000000 +0100 @@ -0,0 +1,4 @@ +{ + "Keys": [ "heif", "heic" ], + "MimeTypes": [ "image/heif", "image/heif" ] +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kimageformats-5.79.0/src/imageformats/heif_p.h new/kimageformats-5.80.0/src/imageformats/heif_p.h --- old/kimageformats-5.79.0/src/imageformats/heif_p.h 1970-01-01 01:00:00.000000000 +0100 +++ new/kimageformats-5.80.0/src/imageformats/heif_p.h 2021-03-04 22:57:23.000000000 +0100 @@ -0,0 +1,57 @@ +/* + High Efficiency Image File Format (HEIF) support for QImage. + + SPDX-FileCopyrightText: 2020 Sirius Bakke <[email protected]> + SPDX-FileCopyrightText: 2021 Daniel Novomesky <[email protected]> + + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#ifndef KIMG_HEIF_P_H +#define KIMG_HEIF_P_H + +#include <QByteArray> +#include <QImage> +#include <QImageIOPlugin> + +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: + static bool isSupportedBMFFType(const QByteArray &header); + 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
