Compare commits

..

4 Commits

Author SHA1 Message Date
4c6d2b92b6 GIT_SILENT Upgrade ECM and KF version requirements for 5.102.0 release. 2023-01-07 00:28:55 +00:00
05bd9397b3 raw: tweak seek implementation
libraw uses fseek when doing files, which allows seeking past the end
without problems, so do the same, otherwise when we report oss-fuzz
issues they say "give me an example to reproduce" and since our seek
and their seek don't behave the same it's hard to convince them
to fix their code
2022-12-14 23:56:20 +01:00
f4ca3f6783 heif: fix error handling 2022-12-13 11:11:38 +01:00
a30f043e5d 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
2022-12-05 22:43:41 +01:00
4 changed files with 533 additions and 466 deletions

View File

@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.16)
project(KImageFormats)
include(FeatureSummary)
find_package(ECM 5.101.0 NO_MODULE)
find_package(ECM 5.102.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)

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,42 +233,70 @@ 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);
} catch (const heif::Error &err) {
qWarning() << "libheif error:" << err.get_message().c_str();
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;
}
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);
if (err.code) {
qWarning() << "Writing HEIF image failed:" << err.message;
heif_context_free(context);
return false;
}
heif_context_free(context);
return true;
}
@ -404,14 +430,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) {
qWarning() << "heif_context_read_from_memory error:" << err.message;
heif_context_free(ctx);
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) {
qWarning() << "heif_context_get_primary_image_handle error:" << err.message;
heif_context_free(ctx);
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 +473,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 +483,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) {
qWarning() << "heif_decode_image error:" << err.message;
heif_image_handle_release(handle);
heif_context_free(ctx);
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 +532,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 +698,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 +727,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 +788,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;
}

View File

@ -141,7 +141,7 @@ public:
if (whence == SEEK_END) {
pos = size + o;
}
if (pos < 0 || pos > size || m_device->isSequential()) {
if (pos < 0 || m_device->isSequential()) {
return -1;
}
return m_device->seek(pos) ? 0 : -1;