heif: rewrite plugin to use only libheif C API

Using C-API instead of C++ libheif API has following advantages:
- More libheif features available (for ex.: strict decoding)
- Linking with static build of libheif is possible
- No need to enable exceptions
This commit is contained in:
Daniel Novomeský 2022-12-05 21:53:38 +01:00
parent 7af4eea253
commit a30f043e5d
2 changed files with 520 additions and 454 deletions

View File

@ -93,7 +93,6 @@ endif()
if (LibHeif_FOUND)
kimageformats_add_plugin(kimg_heif SOURCES heif.cpp)
target_link_libraries(kimg_heif PkgConfig::LibHeif)
kde_target_enable_exceptions(kimg_heif PRIVATE)
if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES heif.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)

View File

@ -8,8 +8,8 @@
*/
#include "heif_p.h"
#include "libheif/heif_cxx.h"
#include "util_p.h"
#include <libheif/heif.h>
#include <QColorSpace>
#include <QDebug>
@ -18,46 +18,39 @@
#include <limits>
#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
size_t HEIFHandler::m_initialized_count = 0;
bool HEIFHandler::m_plugins_queried = false;
bool HEIFHandler::m_heif_decoder_available = false;
bool HEIFHandler::m_heif_encoder_available = false;
extern "C" {
static struct heif_error heifhandler_write_callback(struct heif_context * /* ctx */, const void *data, size_t size, void *userdata)
{
heif_error error;
error.code = heif_error_Ok;
error.subcode = heif_suberror_Unspecified;
error.message = "Success";
if (!userdata || !data || size == 0) {
error.code = heif_error_Usage_error;
error.subcode = heif_suberror_Null_pointer_argument;
error.message = "Wrong parameters!";
return error;
}
QIODevice *ioDevice = static_cast<QIODevice *>(userdata);
qint64 bytesWritten = ioDevice->write(static_cast<const char *>(data), size);
if (bytesWritten < static_cast<qint64>(size)) {
error.code = heif_error_Encoding_error;
error.message = "Bytes written to QIODevice are smaller than input data size";
error.subcode = heif_suberror_Cannot_write_output_data;
}
return error;
}
}
HEIFHandler::HEIFHandler()
: m_parseState(ParseHeicNotParsed)
, m_quality(100)
@ -154,20 +147,25 @@ bool HEIFHandler::write_helper(const QImage &image)
const QImage tmpimage = image.convertToFormat(tmpformat);
try {
heif::Context ctx;
heif::Image heifImage;
heifImage.create(tmpimage.width(), tmpimage.height(), heif_colorspace_RGB, chroma);
struct heif_context *context = heif_context_alloc();
struct heif_error err;
struct heif_image *h_image = nullptr;
err = heif_image_create(tmpimage.width(), tmpimage.height(), heif_colorspace_RGB, chroma, &h_image);
if (err.code) {
qWarning() << "heif_image_create error:" << err.message;
heif_context_free(context);
return false;
}
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);
heif_image_set_raw_color_profile(h_image, "prof", iccprofile.constData(), iccprofile.size());
}
heifImage.add_plane(heif_channel_interleaved, image.width(), image.height(), save_depth);
heif_image_add_plane(h_image, heif_channel_interleaved, image.width(), image.height(), save_depth);
int stride = 0;
uint8_t *const dst = heifImage.get_plane(heif_channel_interleaved, &stride);
uint8_t *const dst = heif_image_get_plane(h_image, heif_channel_interleaved, &stride);
size_t rowbytes;
switch (save_depth) {
@ -235,39 +233,66 @@ bool HEIFHandler::write_helper(const QImage &image)
break;
default:
qWarning() << "Unsupported depth:" << save_depth;
heif_image_release(h_image);
heif_context_free(context);
return false;
break;
}
heif::Encoder encoder(heif_compression_HEVC);
struct heif_encoder *encoder = nullptr;
err = heif_context_get_encoder_for_format(context, heif_compression_HEVC, &encoder);
if (err.code) {
qWarning() << "Unable to get an encoder instance:" << err.message;
heif_image_release(h_image);
heif_context_free(context);
return false;
}
encoder.set_lossy_quality(m_quality);
heif_encoder_set_lossy_quality(encoder, m_quality);
if (m_quality > 90) {
if (m_quality == 100) {
encoder.set_lossless(true);
heif_encoder_set_lossless(encoder, true);
}
encoder.set_string_parameter("chroma", "444");
heif_encoder_set_parameter_string(encoder, "chroma", "444");
}
heif::Context::EncodingOptions encodingOptions;
encodingOptions.save_alpha_channel = save_alpha;
struct heif_encoding_options *encoder_options = heif_encoding_options_alloc();
encoder_options->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;
encoder_options->macOS_compatibility_workaround = 0;
}
}
ctx.encode_image(heifImage, encoder, encodingOptions);
err = heif_context_encode_image(context, h_image, encoder, encoder_options, nullptr);
HeifQIODeviceWriter writer(device());
if (encoder_options) {
heif_encoding_options_free(encoder_options);
}
ctx.write(writer);
if (err.code) {
qWarning() << "heif_context_encode_image failed:" << err.message;
heif_encoder_release(encoder);
heif_image_release(h_image);
heif_context_free(context);
return false;
}
} catch (const heif::Error &err) {
qWarning() << "libheif error:" << err.get_message().c_str();
struct heif_writer writer;
writer.writer_api_version = 1;
writer.write = heifhandler_write_callback;
err = heif_context_write(context, &writer, device());
heif_encoder_release(encoder);
heif_image_release(h_image);
heif_context_free(context);
if (err.code) {
qWarning() << "Writing HEIF image failed:" << err.message;
return false;
}
@ -404,14 +429,27 @@ bool HEIFHandler::ensureDecoder()
return false;
}
try {
heif::Context ctx;
ctx.read_from_memory_without_copy(static_cast<const void *>(buffer.constData()), buffer.size());
struct heif_context *ctx = heif_context_alloc();
struct heif_error err = heif_context_read_from_memory(ctx, static_cast<const void *>(buffer.constData()), buffer.size(), nullptr);
heif::ImageHandle handle = ctx.get_primary_image_handle();
if (err.code) {
heif_context_free(ctx);
qWarning() << "heif_context_read_from_memory error:" << err.message;
m_parseState = ParseHeicError;
return false;
}
const bool hasAlphaChannel = handle.has_alpha_channel();
const int bit_depth = handle.get_luma_bits_per_pixel();
struct heif_image_handle *handle = nullptr;
err = heif_context_get_primary_image_handle(ctx, &handle);
if (err.code) {
heif_context_free(ctx);
qWarning() << "heif_context_get_primary_image_handle error:" << err.message;
m_parseState = ParseHeicError;
return false;
}
const bool hasAlphaChannel = heif_image_handle_has_alpha_channel(handle);
const int bit_depth = heif_image_handle_get_luma_bits_per_pixel(handle);
heif_chroma chroma;
QImage::Format target_image_format;
@ -434,6 +472,8 @@ bool HEIFHandler::ensureDecoder()
}
} else {
m_parseState = ParseHeicError;
heif_image_handle_release(handle);
heif_context_free(ctx);
if (bit_depth > 0) {
qWarning() << "Unsupported bit depth:" << bit_depth;
} else {
@ -442,23 +482,48 @@ bool HEIFHandler::ensureDecoder()
return false;
}
heif::Image img = handle.decode_image(heif_colorspace_RGB, chroma);
struct heif_decoding_options *decoder_option = heif_decoding_options_alloc();
const int imageWidth = img.get_width(heif_channel_interleaved);
const int imageHeight = img.get_height(heif_channel_interleaved);
#if LIBHEIF_HAVE_VERSION(1, 13, 0)
decoder_option->strict_decoding = 1;
#endif
struct heif_image *img = nullptr;
err = heif_decode_image(handle, &img, heif_colorspace_RGB, chroma, decoder_option);
if (decoder_option) {
heif_decoding_options_free(decoder_option);
}
if (err.code) {
heif_image_handle_release(handle);
heif_context_free(ctx);
qWarning() << "heif_decode_image error:" << err.message;
m_parseState = ParseHeicError;
return false;
}
const int imageWidth = heif_image_get_width(img, heif_channel_interleaved);
const int imageHeight = heif_image_get_height(img, heif_channel_interleaved);
QSize imageSize(imageWidth, imageHeight);
if (!imageSize.isValid()) {
heif_image_release(img);
heif_image_handle_release(handle);
heif_context_free(ctx);
m_parseState = ParseHeicError;
qWarning() << "HEIC image size invalid:" << imageSize;
return false;
}
int stride = 0;
const uint8_t *const src = img.get_plane(heif_channel_interleaved, &stride);
const uint8_t *const src = heif_image_get_plane_readonly(img, heif_channel_interleaved, &stride);
if (!src || stride <= 0) {
heif_image_release(img);
heif_image_handle_release(handle);
heif_context_free(ctx);
m_parseState = ParseHeicError;
qWarning() << "HEIC data pixels information not valid!";
return false;
@ -466,6 +531,9 @@ bool HEIFHandler::ensureDecoder()
m_current_image = imageAlloc(imageSize, target_image_format);
if (m_current_image.isNull()) {
heif_image_release(img);
heif_image_handle_release(handle);
heif_context_free(ctx);
m_parseState = ParseHeicError;
qWarning() << "Unable to allocate memory!";
return false;
@ -629,19 +697,21 @@ bool HEIFHandler::ensureDecoder()
}
break;
default:
heif_image_release(img);
heif_image_handle_release(handle);
heif_context_free(ctx);
m_parseState = ParseHeicError;
qWarning() << "Unsupported bit depth:" << bit_depth;
return false;
break;
}
heif_color_profile_type profileType = heif_image_handle_get_color_profile_type(handle.get_raw_image_handle());
struct heif_error err;
heif_color_profile_type profileType = heif_image_handle_get_color_profile_type(handle);
if (profileType == heif_color_profile_type_prof || profileType == heif_color_profile_type_rICC) {
size_t rawProfileSize = heif_image_handle_get_raw_color_profile_size(handle.get_raw_image_handle());
size_t rawProfileSize = heif_image_handle_get_raw_color_profile_size(handle);
if (rawProfileSize > 0 && rawProfileSize < std::numeric_limits<int>::max()) {
QByteArray ba(rawProfileSize, 0);
err = heif_image_handle_get_raw_color_profile(handle.get_raw_image_handle(), ba.data());
err = heif_image_handle_get_raw_color_profile(handle, ba.data());
if (err.code) {
qWarning() << "icc profile loading failed";
} else {
@ -656,7 +726,7 @@ bool HEIFHandler::ensureDecoder()
} 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);
err = heif_image_handle_get_nclx_color_profile(handle, &nclx);
if (err.code || !nclx) {
qWarning() << "nclx profile loading failed";
} else {
@ -717,12 +787,9 @@ bool HEIFHandler::ensureDecoder()
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;
}
heif_image_release(img);
heif_image_handle_release(handle);
heif_context_free(ctx);
m_parseState = ParseHeicSuccess;
return true;
}