mirror of
https://invent.kde.org/frameworks/kimageformats.git
synced 2025-06-03 17:08:08 -04:00
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:
parent
7af4eea253
commit
a30f043e5d
@ -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/)
|
||||
|
@ -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,45 +18,38 @@
|
||||
#include <limits>
|
||||
#include <string.h>
|
||||
|
||||
namespace // Private.
|
||||
{
|
||||
struct HeifQIODeviceWriter : public heif::Context::Writer {
|
||||
HeifQIODeviceWriter(QIODevice *device)
|
||||
: m_ioDevice(device)
|
||||
{
|
||||
}
|
||||
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;
|
||||
|
||||
heif_error write(const void *data, size_t size) override
|
||||
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 = errorOkMessage;
|
||||
error.message = "Success";
|
||||
|
||||
qint64 bytesWritten = m_ioDevice->write(static_cast<const char *>(data), size);
|
||||
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 = QIODeviceWriteErrorMessage;
|
||||
error.message = "Bytes written to QIODevice are smaller than input data size";
|
||||
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;
|
||||
}
|
||||
|
||||
HEIFHandler::HEIFHandler()
|
||||
: m_parseState(ParseHeicNotParsed)
|
||||
@ -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;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user