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