diff --git a/core/dplugins/dimg/heif/dimgheifloader.h b/core/dplugins/dimg/heif/dimgheifloader.h index 7e2f2b1ba7..b64db2c262 100644 --- a/core/dplugins/dimg/heif/dimgheifloader.h +++ b/core/dplugins/dimg/heif/dimgheifloader.h @@ -1,89 +1,89 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2019-09-26 * Description : A HEIF IO file for DImg framework * * Copyright (C) 2019 by Gilles Caulier * * Other HEIF loader implementions: * https://github.com/KDE/krita/tree/master/plugins/impex/heif * https://github.com/jakar/qt-heif-image-plugin * https://github.com/ImageMagick/ImageMagick/blob/master/coders/heic.c * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #ifndef DIGIKAM_DIMG_HEIF_LOADER_H #define DIGIKAM_DIMG_HEIF_LOADER_H // Local includes #include "dimg.h" #include "dimgloader.h" #include "digikam_export.h" #include "heif.h" using namespace Digikam; namespace Digikam { class DIGIKAM_EXPORT DImgHEIFLoader : public DImgLoader { public: explicit DImgHEIFLoader(DImg* const image); bool load(const QString& filePath, DImgLoaderObserver* const observer) override; bool save(const QString& filePath, DImgLoaderObserver* const observer) override; virtual bool hasAlpha() const override; virtual bool sixteenBit() const override; virtual bool isReadOnly() const override; + /** + * Determine libx265 encoder bits depth capability: 8=standard, 10, 12, or more. + * Return -1 if encoder instance is not found. + */ + static int x265MaxBitsDepth(); + private: bool isHeifSuccess(struct heif_error* const error); // Read operations bool readHEICColorProfile(struct heif_image_handle* const image_handle); bool readHEICMetadata(struct heif_image_handle* const image_handle); bool readHEICImageByID(struct heif_context* const heif_context, heif_item_id image_id); // Save operations bool saveHEICColorProfile(struct heif_image* const image); bool saveHEICMetadata(struct heif_context* const heif_context, struct heif_image_handle* const image_handle); - /** - * Determine libx265 encoder bits depth capability: 8=standard, 10, 12, or more. - * Return -1 if encoder instance is not found. - */ - int x265MaxBitsDepth() const; - private: bool m_sixteenBit; bool m_hasAlpha; DImgLoaderObserver* m_observer; }; } // namespace Digikam #endif // DIGIKAM_DIMG_HEIF_LOADER_H diff --git a/core/dplugins/dimg/heif/dimgheifloader_save.cpp b/core/dplugins/dimg/heif/dimgheifloader_save.cpp index f4b5bc62fb..7b70d1095e 100644 --- a/core/dplugins/dimg/heif/dimgheifloader_save.cpp +++ b/core/dplugins/dimg/heif/dimgheifloader_save.cpp @@ -1,474 +1,474 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2019-09-26 * Description : A HEIF IO file for DImg framework - save operations * * Copyright (C) 2019 by Gilles Caulier * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "dimgheifloader.h" // Qt includes #include #include #include #include #include #include // Local includes #include "digikam_debug.h" #include "dimg.h" #include "dimgloaderobserver.h" #include "metaengine.h" // libx265 includes #ifdef HAVE_X265 # include #endif namespace Digikam { -int DImgHEIFLoader::x265MaxBitsDepth() const +int DImgHEIFLoader::x265MaxBitsDepth() { int maxOutputBitsDepth = -1; #ifdef HAVE_X265 for (int i = 16 ; i >= 8 ; i-=2) { qDebug() << "Check HEVC encoder for" << i << "bits encoding..."; const x265_api* const api = x265_api_get(i); if (api) { maxOutputBitsDepth = i; break; } } qDebug() << "HEVC encoder max bits depth:" << maxOutputBitsDepth; #endif if (maxOutputBitsDepth == -1) { qWarning() << "Cannot get max supported HEVC encoder bits depth!"; } return maxOutputBitsDepth; } bool DImgHEIFLoader::save(const QString& filePath, DImgLoaderObserver* const observer) { m_observer = observer; // ------------------------------------------------------------------- // Open the file FILE* const f = fopen(QFile::encodeName(filePath).constData(), "wb"); if (!f) { qWarning() << "Cannot open target image file."; return false; } fclose(f); QVariant qualityAttr = imageGetAttribute(QLatin1String("quality")); int quality = qualityAttr.isValid() ? qualityAttr.toInt() : 50; QVariant losslessAttr = imageGetAttribute(QLatin1String("lossless")); bool lossless = losslessAttr.isValid() ? qualityAttr.toBool() : false; // --- Determine libx265 encoder bits depth capability: 8=standard, 10, 12, or later 16. int maxOutputBitsDepth = x265MaxBitsDepth(); if (maxOutputBitsDepth == -1) { return false; } heif_chroma chroma; if (maxOutputBitsDepth > 8) // 16 bits image. { chroma = imageHasAlpha() ? heif_chroma_interleaved_RRGGBBAA_BE : heif_chroma_interleaved_RRGGBB_BE; } else { chroma = imageHasAlpha() ? heif_chroma_interleaved_RGBA : heif_chroma_interleaved_RGB; } // --- use standard HEVC encoder qDebug() << "HEVC encoder setup..."; struct heif_context* const ctx = heif_context_alloc(); if (!ctx) { qWarning() << "Cannot create HEIC context!"; return false; } struct heif_encoder* encoder = nullptr; struct heif_error error = heif_context_get_encoder_for_format(ctx, heif_compression_HEVC, &encoder); if (!isHeifSuccess(&error)) { heif_context_free(ctx); return false; } heif_encoder_set_lossy_quality(encoder, quality); heif_encoder_set_lossless(encoder, lossless); struct heif_image* image = nullptr; error = heif_image_create(imageWidth(), imageHeight(), heif_colorspace_RGB, chroma, &image); if (!isHeifSuccess(&error)) { heif_encoder_release(encoder); heif_context_free(ctx); return false; } // --- Save color profile before to create image data, as converting to color space can be processed at this stage. qDebug() << "HEIC set color profile..."; saveHEICColorProfile(image); // --- Add image data qDebug() << "HEIC setup data plane..."; error = heif_image_add_plane(image, heif_channel_interleaved, imageWidth(), imageHeight(), maxOutputBitsDepth); if (!isHeifSuccess(&error)) { heif_encoder_release(encoder); heif_context_free(ctx); return false; } int stride = 0; uint8_t* const data = heif_image_get_plane(image, heif_channel_interleaved, &stride); if (!data || stride <= 0) { qWarning() << "HEIC data pixels information not valid!"; heif_encoder_release(encoder); heif_context_free(ctx); return false; } qDebug() << "HEIC data container:" << data; qDebug() << "HEIC bytes per line:" << stride; uint checkpoint = 0; unsigned char r = 0; unsigned char g = 0; unsigned char b = 0; unsigned char a = 0; unsigned char* src = nullptr; unsigned char* dst = nullptr; unsigned short r16 = 0; unsigned short g16 = 0; unsigned short b16 = 0; unsigned short a16 = 0; unsigned short* src16 = nullptr; unsigned short* dst16 = nullptr; int div16 = 16 - maxOutputBitsDepth; int mul8 = maxOutputBitsDepth - 8; int nbOutputBytesPerColor = (maxOutputBitsDepth > 8) ? (imageHasAlpha() ? 4 * 2 : 3 * 2) // output data stored on 16 bits : (imageHasAlpha() ? 4 : 3 ); // output data stored on 8 bits qDebug() << "HEIC output bytes per color:" << nbOutputBytesPerColor; qDebug() << "HEIC 16 to 8 bits coeff. :" << div16; qDebug() << "HEIC 8 to 16 bits coeff. :" << mul8; for (unsigned int y = 0 ; y < imageHeight() ; ++y) { src = &imageData()[(y * imageWidth()) * imageBytesDepth()]; src16 = reinterpret_cast(src); dst = reinterpret_cast(data + (y * stride)); dst16 = reinterpret_cast(dst); for (unsigned int x = 0 ; x < imageWidth() ; ++x) { if (imageSixteenBit()) // 16 bits source image. { b16 = src16[0]; g16 = src16[1]; r16 = src16[2]; if (imageHasAlpha()) { a16 = src16[3]; } if (maxOutputBitsDepth > 8) // From 16 bits to 10 bits or more. { dst16[0] = (unsigned short)(r16 >> div16); dst16[1] = (unsigned short)(g16 >> div16); dst16[2] = (unsigned short)(b16 >> div16); if (imageHasAlpha()) { dst16[3] = (unsigned short)(a16 >> div16); dst16 += 4; } else { dst16 += 3; } } else // From 16 bits to 8 bits. { dst[0] = (unsigned char)(r16 >> div16); dst[1] = (unsigned char)(g16 >> div16); dst[2] = (unsigned char)(b16 >> div16); if (imageHasAlpha()) { dst[3] = (unsigned char)(a16 >> div16); dst += 4; } else { dst += 3; } } src16 += 4; } else // 8 bits source image. { b = src[0]; g = src[1]; r = src[2]; if (imageHasAlpha()) { a = src[3]; } if (maxOutputBitsDepth > 8) // From 8 bits to 10 bits or more. { dst16[0] = (unsigned short)(r << mul8); dst16[1] = (unsigned short)(g << mul8); dst16[2] = (unsigned short)(b << mul8); if (imageHasAlpha()) { dst16[3] = (unsigned short)(a << mul8); dst16 += 4; } else { dst16 += 3; } } else // From 8 bits to 8 bits. { dst[0] = r; dst[1] = g; dst[2] = b; if (imageHasAlpha()) { dst[3] = a; dst += 4; } else { dst += 3; } } src += 4; } } if (m_observer && y == (long)checkpoint) { checkpoint += granularity(m_observer, imageHeight(), 0.8F); if (!m_observer->continueQuery(m_image)) { heif_encoder_release(encoder); heif_context_free(ctx); return false; } m_observer->progressInfo(m_image, 0.1 + (0.8 * (((float)y) / ((float)imageHeight())))); } } qDebug() << "HEIC image encoding..."; // --- encode and write image struct heif_encoding_options* options = heif_encoding_options_alloc(); options->save_alpha_channel = imageHasAlpha() ? 1 : 0; struct heif_image_handle* hdl = nullptr; error = heif_context_encode_image(ctx, image, encoder, options, &hdl); if (!isHeifSuccess(&error)) { heif_encoding_options_free(options); heif_image_handle_release(hdl); heif_encoder_release(encoder); heif_context_free(ctx); return false; } heif_encoding_options_free(options); heif_encoder_release(encoder); // --- Add Exif and XMP metadata qDebug() << "HEIC metadata storage..."; saveHEICMetadata(ctx, hdl); heif_image_handle_release(hdl); // --- TODO: Add thumnail image. // --- write HEIF file qDebug() << "HEIC flush to file..."; error = heif_context_write_to_file(ctx, QFile::encodeName(filePath).constData()); if (!isHeifSuccess(&error)) { heif_context_free(ctx); return false; } heif_context_free(ctx); imageSetAttribute(QLatin1String("savedFormat"), QLatin1String("HEIC")); saveMetadata(filePath); return true; } bool DImgHEIFLoader::saveHEICColorProfile(struct heif_image* const image) { #if LIBHEIF_NUMERIC_VERSION >= 0x01040000 QByteArray profile = m_image->getIccProfile().data(); if (!profile.isEmpty()) { // Save color profile. struct heif_error error = heif_image_set_raw_color_profile(image, "prof", // FIXME: detect string in profile data profile.data(), profile.size()); if (error.code != 0) { qWarning() << "Cannot set HEIC color profile!"; return false; } qDebug() << "Stored HEIC color profile size:" << profile.size(); } #else Q_UNUSED(image_handle); #endif return true; } bool DImgHEIFLoader::saveHEICMetadata(struct heif_context* const heif_context, struct heif_image_handle* const image_handle) { MetaEngine meta(m_image->getMetadata()); if (!meta.hasExif() && !meta.hasXmp()) { return false; } QByteArray exif = meta.getExifEncoded(); QByteArray xmp = meta.getXmp(); struct heif_error error; if (!exif.isEmpty()) { error = heif_context_add_exif_metadata(heif_context, image_handle, exif.data(), exif.size()); if (error.code != 0) { qWarning() << "Cannot store HEIC Exif metadata!"; return false; } qDebug() << "Stored HEIC Exif data size:" << exif.size(); } if (!xmp.isEmpty()) { error = heif_context_add_XMP_metadata(heif_context, image_handle, xmp.data(), xmp.size()); if (error.code != 0) { qWarning() << "Cannot store HEIC Xmp metadata!"; return false; } qDebug() << "Stored HEIC Xmp data size:" << xmp.size(); } return true; } } // namespace Digikam diff --git a/core/dplugins/dimg/heif/dimgheifplugin.cpp b/core/dplugins/dimg/heif/dimgheifplugin.cpp index cf87e26c66..4726cf0b80 100644 --- a/core/dplugins/dimg/heif/dimgheifplugin.cpp +++ b/core/dplugins/dimg/heif/dimgheifplugin.cpp @@ -1,183 +1,200 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2019-09-22 * Description : HEIF DImg plugin. * * Copyright (C) 2019 by Gilles Caulier * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "dimgheifplugin.h" // C++ includes #include // Qt includes #include #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "digikam_globals.h" #include "dimgheifloader.h" namespace DigikamHEIFDImgPlugin { DImgHEIFPlugin::DImgHEIFPlugin(QObject* const parent) : DPluginDImg(parent) { } DImgHEIFPlugin::~DImgHEIFPlugin() { } QString DImgHEIFPlugin::name() const { return i18n("HEIF loader"); } QString DImgHEIFPlugin::iid() const { return QLatin1String(DPLUGIN_IID); } QIcon DImgHEIFPlugin::icon() const { return QIcon::fromTheme(QLatin1String("image-x-generic")); } QString DImgHEIFPlugin::description() const { return i18n("An image loader based on Libheif codec"); } QString DImgHEIFPlugin::details() const { + QString x265Notice = i18n("This library is not present on your system."); + +#ifdef HAVE_X265 + int depth = DImgHEIFLoader::x265MaxBitsDepth(); + + if (depth != -1) + { + x265Notice = i18n("This library is available on your system with a maximum color depth " + "support of %1 bits.", depth); + } + else + { + x265Notice = i18n("This library is available on your system but is not able to encode " + "image with a suitable color depth."); + } +#endif + return i18n("

This plugin permit to load and save image using Libheif codec.

" "

High Efficiency Image File Format (HEIF), also known as High Efficiency Image Coding (HEIC), " "is a file format for individual images and image sequences. It was developed by the " "Moving Picture Experts Group (MPEG) and it claims that twice as much information can be " "stored in a HEIF image as in a JPEG image of the same size, resulting in a better quality image. " "HEIF also supports animation, and is capable of storing more information than an animated GIF " "at a small fraction of the size.

" + "

Encoding HEIC is relevant of optional libx265 codec. %1

" "

See " - "High Efficiency Image File Format for details.

" - ); + "High Efficiency Image File Format for details.

", x265Notice); } QList DImgHEIFPlugin::authors() const { return QList() << DPluginAuthor(QString::fromUtf8("Gilles Caulier"), QString::fromUtf8("caulier dot gilles at gmail dot com"), QString::fromUtf8("(C) 2019")) ; } void DImgHEIFPlugin::setup(QObject* const /*parent*/) { // Nothing to do } QString DImgHEIFPlugin::loaderName() const { return QLatin1String("HEIF"); } QString DImgHEIFPlugin::typeMimes() const { return QLatin1String("HEIC"); } bool DImgHEIFPlugin::canRead(const QString& filePath) const { QFileInfo fileInfo(filePath); if (!fileInfo.exists()) { qCDebug(DIGIKAM_DIMG_LOG) << "File " << filePath << " does not exist"; return false; } // First simply check file extension QString ext = fileInfo.suffix().toUpper(); if (!ext.isEmpty() && (ext == QLatin1String("HEIC"))) { return true; } // In second, we trying to parse file header. FILE* const f = fopen(QFile::encodeName(filePath).constData(), "rb"); if (!f) { qCDebug(DIGIKAM_DIMG_LOG) << "Failed to open file " << filePath; return false; } const int headerLen = 12; unsigned char header[headerLen]; if (fread(&header, headerLen, 1, f) != 1) { qCDebug(DIGIKAM_DIMG_LOG) << "Failed to read header of file " << filePath; fclose(f); return false; } fclose(f); if ((memcmp(&header[4], "ftyp", 4) == 0) || (memcmp(&header[8], "heic", 4) == 0) || (memcmp(&header[8], "heix", 4) == 0) || (memcmp(&header[8], "mif1", 4) == 0)) { return true; } return false; } bool DImgHEIFPlugin::canWrite(const QString& format) const { #ifdef HAVE_X265 if (format.toUpper() == QLatin1String("HEIC")) { return true; } #endif return false; } DImgLoader* DImgHEIFPlugin::loader(DImg* const image, const DRawDecoding&) const { return new DImgHEIFLoader(image); } } // namespace DigikamHEIFDImgPlugin