Compare commits
9 Commits
Author | SHA1 | Date | |
---|---|---|---|
63056c52f9 | |||
2997f7ae8d | |||
0b4741f4b7 | |||
bc52c03981 | |||
c1c57d9a11 | |||
4c6d2b92b6 | |||
05bd9397b3 | |||
f4ca3f6783 | |||
a30f043e5d |
@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.16)
|
|||||||
project(KImageFormats)
|
project(KImageFormats)
|
||||||
|
|
||||||
include(FeatureSummary)
|
include(FeatureSummary)
|
||||||
find_package(ECM 5.101.0 NO_MODULE)
|
find_package(ECM 5.103.0 NO_MODULE)
|
||||||
set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://commits.kde.org/extra-cmake-modules")
|
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)
|
feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES)
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 189 KiB After Width: | Height: | Size: 191 KiB |
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 115 KiB |
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.0 KiB |
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.0 KiB |
@ -93,7 +93,6 @@ endif()
|
|||||||
if (LibHeif_FOUND)
|
if (LibHeif_FOUND)
|
||||||
kimageformats_add_plugin(kimg_heif SOURCES heif.cpp)
|
kimageformats_add_plugin(kimg_heif SOURCES heif.cpp)
|
||||||
target_link_libraries(kimg_heif PkgConfig::LibHeif)
|
target_link_libraries(kimg_heif PkgConfig::LibHeif)
|
||||||
kde_target_enable_exceptions(kimg_heif PRIVATE)
|
|
||||||
|
|
||||||
if (QT_MAJOR_VERSION STREQUAL "5")
|
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||||
install(FILES heif.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
install(FILES heif.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||||
|
35
src/imageformats/fastmath_p.h
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
Approximated math functions used into conversions.
|
||||||
|
|
||||||
|
SPDX-FileCopyrightText: Edward Kmett
|
||||||
|
SPDX-FileCopyrightText: 2023 Mirco Miranda <mircomir@outlook.com>
|
||||||
|
|
||||||
|
SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
*/
|
||||||
|
#ifndef FASTMATH_P_H
|
||||||
|
#define FASTMATH_P_H
|
||||||
|
|
||||||
|
#include <QtGlobal>
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief fastPow
|
||||||
|
* Based on Edward Kmett code released into the public domain.
|
||||||
|
* See also: https://github.com/ekmett/approximate
|
||||||
|
*/
|
||||||
|
inline double fastPow(double x, double y)
|
||||||
|
{
|
||||||
|
union {
|
||||||
|
double d;
|
||||||
|
qint32 i[2];
|
||||||
|
} u = {x};
|
||||||
|
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
|
||||||
|
u.i[1] = qint32(y * (u.i[1] - 1072632447) + 1072632447);
|
||||||
|
u.i[0] = 0;
|
||||||
|
#else // never tested
|
||||||
|
u.i[0] = qint32(y * (u.i[0] - 1072632447) + 1072632447);
|
||||||
|
u.i[1] = 0;
|
||||||
|
#endif
|
||||||
|
return u.d;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // FASTMATH_P_H
|
@ -8,8 +8,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "heif_p.h"
|
#include "heif_p.h"
|
||||||
#include "libheif/heif_cxx.h"
|
|
||||||
#include "util_p.h"
|
#include "util_p.h"
|
||||||
|
#include <libheif/heif.h>
|
||||||
|
|
||||||
#include <QColorSpace>
|
#include <QColorSpace>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
@ -18,46 +18,39 @@
|
|||||||
#include <limits>
|
#include <limits>
|
||||||
#include <string.h>
|
#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;
|
size_t HEIFHandler::m_initialized_count = 0;
|
||||||
bool HEIFHandler::m_plugins_queried = false;
|
bool HEIFHandler::m_plugins_queried = false;
|
||||||
bool HEIFHandler::m_heif_decoder_available = false;
|
bool HEIFHandler::m_heif_decoder_available = false;
|
||||||
bool HEIFHandler::m_heif_encoder_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()
|
HEIFHandler::HEIFHandler()
|
||||||
: m_parseState(ParseHeicNotParsed)
|
: m_parseState(ParseHeicNotParsed)
|
||||||
, m_quality(100)
|
, m_quality(100)
|
||||||
@ -154,20 +147,25 @@ bool HEIFHandler::write_helper(const QImage &image)
|
|||||||
|
|
||||||
const QImage tmpimage = image.convertToFormat(tmpformat);
|
const QImage tmpimage = image.convertToFormat(tmpformat);
|
||||||
|
|
||||||
try {
|
struct heif_context *context = heif_context_alloc();
|
||||||
heif::Context ctx;
|
struct heif_error err;
|
||||||
heif::Image heifImage;
|
struct heif_image *h_image = nullptr;
|
||||||
heifImage.create(tmpimage.width(), tmpimage.height(), heif_colorspace_RGB, chroma);
|
|
||||||
|
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();
|
QByteArray iccprofile = tmpimage.colorSpace().iccProfile();
|
||||||
if (iccprofile.size() > 0) {
|
if (iccprofile.size() > 0) {
|
||||||
std::vector<uint8_t> rawProfile(iccprofile.begin(), iccprofile.end());
|
heif_image_set_raw_color_profile(h_image, "prof", iccprofile.constData(), iccprofile.size());
|
||||||
heifImage.set_raw_color_profile(heif_color_profile_type_prof, rawProfile);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
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;
|
size_t rowbytes;
|
||||||
|
|
||||||
switch (save_depth) {
|
switch (save_depth) {
|
||||||
@ -235,42 +233,70 @@ bool HEIFHandler::write_helper(const QImage &image)
|
|||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
qWarning() << "Unsupported depth:" << save_depth;
|
qWarning() << "Unsupported depth:" << save_depth;
|
||||||
|
heif_image_release(h_image);
|
||||||
|
heif_context_free(context);
|
||||||
return false;
|
return false;
|
||||||
break;
|
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 > 90) {
|
||||||
if (m_quality == 100) {
|
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;
|
struct heif_encoding_options *encoder_options = heif_encoding_options_alloc();
|
||||||
encodingOptions.save_alpha_channel = save_alpha;
|
encoder_options->save_alpha_channel = save_alpha;
|
||||||
|
|
||||||
if ((tmpimage.width() % 2 == 1) || (tmpimage.height() % 2 == 1)) {
|
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.";
|
qWarning() << "Image has odd dimension!\nUse even-numbered dimension(s) for better compatibility with other HEIF implementations.";
|
||||||
if (save_alpha) {
|
if (save_alpha) {
|
||||||
// This helps to save alpha channel when image has odd dimension
|
// 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;
|
||||||
} catch (const heif::Error &err) {
|
heif_encoder_release(encoder);
|
||||||
qWarning() << "libheif error:" << err.get_message().c_str();
|
heif_image_release(h_image);
|
||||||
|
heif_context_free(context);
|
||||||
return false;
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -404,14 +430,35 @@ bool HEIFHandler::ensureDecoder()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
struct heif_context *ctx = heif_context_alloc();
|
||||||
heif::Context ctx;
|
struct heif_error err = heif_context_read_from_memory(ctx, static_cast<const void *>(buffer.constData()), buffer.size(), nullptr);
|
||||||
ctx.read_from_memory_without_copy(static_cast<const void *>(buffer.constData()), buffer.size());
|
|
||||||
|
|
||||||
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();
|
struct heif_image_handle *handle = nullptr;
|
||||||
const int bit_depth = handle.get_luma_bits_per_pixel();
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((heif_image_handle_get_width(handle) == 0) || (heif_image_handle_get_height(handle) == 0)) {
|
||||||
|
m_parseState = ParseHeicError;
|
||||||
|
heif_image_handle_release(handle);
|
||||||
|
heif_context_free(ctx);
|
||||||
|
qWarning() << "HEIC image has zero dimension";
|
||||||
|
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;
|
heif_chroma chroma;
|
||||||
|
|
||||||
QImage::Format target_image_format;
|
QImage::Format target_image_format;
|
||||||
@ -434,6 +481,8 @@ bool HEIFHandler::ensureDecoder()
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
m_parseState = ParseHeicError;
|
m_parseState = ParseHeicError;
|
||||||
|
heif_image_handle_release(handle);
|
||||||
|
heif_context_free(ctx);
|
||||||
if (bit_depth > 0) {
|
if (bit_depth > 0) {
|
||||||
qWarning() << "Unsupported bit depth:" << bit_depth;
|
qWarning() << "Unsupported bit depth:" << bit_depth;
|
||||||
} else {
|
} else {
|
||||||
@ -442,23 +491,48 @@ bool HEIFHandler::ensureDecoder()
|
|||||||
return false;
|
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);
|
#if LIBHEIF_HAVE_VERSION(1, 13, 0)
|
||||||
const int imageHeight = img.get_height(heif_channel_interleaved);
|
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);
|
QSize imageSize(imageWidth, imageHeight);
|
||||||
|
|
||||||
if (!imageSize.isValid()) {
|
if (!imageSize.isValid()) {
|
||||||
|
heif_image_release(img);
|
||||||
|
heif_image_handle_release(handle);
|
||||||
|
heif_context_free(ctx);
|
||||||
m_parseState = ParseHeicError;
|
m_parseState = ParseHeicError;
|
||||||
qWarning() << "HEIC image size invalid:" << imageSize;
|
qWarning() << "HEIC image size invalid:" << imageSize;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int stride = 0;
|
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) {
|
if (!src || stride <= 0) {
|
||||||
|
heif_image_release(img);
|
||||||
|
heif_image_handle_release(handle);
|
||||||
|
heif_context_free(ctx);
|
||||||
m_parseState = ParseHeicError;
|
m_parseState = ParseHeicError;
|
||||||
qWarning() << "HEIC data pixels information not valid!";
|
qWarning() << "HEIC data pixels information not valid!";
|
||||||
return false;
|
return false;
|
||||||
@ -466,6 +540,9 @@ bool HEIFHandler::ensureDecoder()
|
|||||||
|
|
||||||
m_current_image = imageAlloc(imageSize, target_image_format);
|
m_current_image = imageAlloc(imageSize, target_image_format);
|
||||||
if (m_current_image.isNull()) {
|
if (m_current_image.isNull()) {
|
||||||
|
heif_image_release(img);
|
||||||
|
heif_image_handle_release(handle);
|
||||||
|
heif_context_free(ctx);
|
||||||
m_parseState = ParseHeicError;
|
m_parseState = ParseHeicError;
|
||||||
qWarning() << "Unable to allocate memory!";
|
qWarning() << "Unable to allocate memory!";
|
||||||
return false;
|
return false;
|
||||||
@ -629,19 +706,21 @@ bool HEIFHandler::ensureDecoder()
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
heif_image_release(img);
|
||||||
|
heif_image_handle_release(handle);
|
||||||
|
heif_context_free(ctx);
|
||||||
m_parseState = ParseHeicError;
|
m_parseState = ParseHeicError;
|
||||||
qWarning() << "Unsupported bit depth:" << bit_depth;
|
qWarning() << "Unsupported bit depth:" << bit_depth;
|
||||||
return false;
|
return false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
heif_color_profile_type profileType = heif_image_handle_get_color_profile_type(handle.get_raw_image_handle());
|
heif_color_profile_type profileType = heif_image_handle_get_color_profile_type(handle);
|
||||||
struct heif_error err;
|
|
||||||
if (profileType == heif_color_profile_type_prof || profileType == heif_color_profile_type_rICC) {
|
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()) {
|
if (rawProfileSize > 0 && rawProfileSize < std::numeric_limits<int>::max()) {
|
||||||
QByteArray ba(rawProfileSize, 0);
|
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) {
|
if (err.code) {
|
||||||
qWarning() << "icc profile loading failed";
|
qWarning() << "icc profile loading failed";
|
||||||
} else {
|
} else {
|
||||||
@ -656,7 +735,7 @@ bool HEIFHandler::ensureDecoder()
|
|||||||
|
|
||||||
} else if (profileType == heif_color_profile_type_nclx) {
|
} else if (profileType == heif_color_profile_type_nclx) {
|
||||||
struct heif_color_profile_nclx *nclx = nullptr;
|
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) {
|
if (err.code || !nclx) {
|
||||||
qWarning() << "nclx profile loading failed";
|
qWarning() << "nclx profile loading failed";
|
||||||
} else {
|
} else {
|
||||||
@ -717,12 +796,9 @@ bool HEIFHandler::ensureDecoder()
|
|||||||
m_current_image.setColorSpace(QColorSpace(QColorSpace::SRgb));
|
m_current_image.setColorSpace(QColorSpace(QColorSpace::SRgb));
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (const heif::Error &err) {
|
heif_image_release(img);
|
||||||
m_parseState = ParseHeicError;
|
heif_image_handle_release(handle);
|
||||||
qWarning() << "libheif error:" << err.get_message().c_str();
|
heif_context_free(ctx);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_parseState = ParseHeicSuccess;
|
m_parseState = ParseHeicSuccess;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
SPDX-FileCopyrightText: 2003 Ignacio Castaño <castano@ludicon.com>
|
SPDX-FileCopyrightText: 2003 Ignacio Castaño <castano@ludicon.com>
|
||||||
SPDX-FileCopyrightText: 2015 Alex Merry <alex.merry@kde.org>
|
SPDX-FileCopyrightText: 2015 Alex Merry <alex.merry@kde.org>
|
||||||
SPDX-FileCopyrightText: 2022 Mirco Miranda <mircomir@outlook.com>
|
SPDX-FileCopyrightText: 2022-2023 Mirco Miranda <mircomir@outlook.com>
|
||||||
|
|
||||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||||
*/
|
*/
|
||||||
@ -21,7 +21,6 @@
|
|||||||
/*
|
/*
|
||||||
* Limitations of the current code:
|
* Limitations of the current code:
|
||||||
* - 32-bit float image are converted to 16-bit integer image.
|
* - 32-bit float image are converted to 16-bit integer image.
|
||||||
* NOTE: Qt 6.2 allow 32-bit float images (RGB only)
|
|
||||||
* - Other color spaces cannot directly be read due to lack of QImage support for
|
* - Other color spaces cannot directly be read due to lack of QImage support for
|
||||||
* color spaces other than RGB (and Grayscale). Where possible, a conversion
|
* color spaces other than RGB (and Grayscale). Where possible, a conversion
|
||||||
* to RGB is done:
|
* to RGB is done:
|
||||||
@ -33,6 +32,7 @@
|
|||||||
* color management engine (e.g. LittleCMS).
|
* color management engine (e.g. LittleCMS).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "fastmath_p.h"
|
||||||
#include "psd_p.h"
|
#include "psd_p.h"
|
||||||
#include "util_p.h"
|
#include "util_p.h"
|
||||||
|
|
||||||
@ -51,7 +51,7 @@ typedef quint8 uchar;
|
|||||||
* This should not be a problem because the Qt's QColorSpace supports the linear
|
* This should not be a problem because the Qt's QColorSpace supports the linear
|
||||||
* sRgb colorspace.
|
* sRgb colorspace.
|
||||||
*
|
*
|
||||||
* Using linear conversion, the loading speed is improved by 4x. Anyway, if you are using
|
* Using linear conversion, the loading speed is slightly improved. Anyway, if you are using
|
||||||
* an software that discard color info, you should comment it.
|
* an software that discard color info, you should comment it.
|
||||||
*
|
*
|
||||||
* At the time I'm writing (07/2022), Gwenview and Krita supports linear sRgb but KDE
|
* At the time I'm writing (07/2022), Gwenview and Krita supports linear sRgb but KDE
|
||||||
@ -845,6 +845,7 @@ inline void cmykToRgb(uchar *target, qint32 targetChannels, const char *source,
|
|||||||
auto s = reinterpret_cast<const T*>(source);
|
auto s = reinterpret_cast<const T*>(source);
|
||||||
auto t = reinterpret_cast<T*>(target);
|
auto t = reinterpret_cast<T*>(target);
|
||||||
auto max = double(std::numeric_limits<T>::max());
|
auto max = double(std::numeric_limits<T>::max());
|
||||||
|
auto invmax = 1.0 / max; // speed improvements by ~10%
|
||||||
|
|
||||||
if (sourceChannels < 4) {
|
if (sourceChannels < 4) {
|
||||||
qDebug() << "cmykToRgb: image is not a valid CMYK!";
|
qDebug() << "cmykToRgb: image is not a valid CMYK!";
|
||||||
@ -853,10 +854,10 @@ inline void cmykToRgb(uchar *target, qint32 targetChannels, const char *source,
|
|||||||
|
|
||||||
for (qint32 w = 0; w < width; ++w) {
|
for (qint32 w = 0; w < width; ++w) {
|
||||||
auto ps = s + sourceChannels * w;
|
auto ps = s + sourceChannels * w;
|
||||||
auto C = 1 - *(ps + 0) / max;
|
auto C = 1 - *(ps + 0) * invmax;
|
||||||
auto M = 1 - *(ps + 1) / max;
|
auto M = 1 - *(ps + 1) * invmax;
|
||||||
auto Y = 1 - *(ps + 2) / max;
|
auto Y = 1 - *(ps + 2) * invmax;
|
||||||
auto K = 1 - *(ps + 3) / max;
|
auto K = 1 - *(ps + 3) * invmax;
|
||||||
|
|
||||||
auto pt = t + targetChannels * w;
|
auto pt = t + targetChannels * w;
|
||||||
*(pt + 0) = T(std::min(max - (C * (1 - K) + K) * max + 0.5, max));
|
*(pt + 0) = T(std::min(max - (C * (1 - K) + K) * max + 0.5, max));
|
||||||
@ -881,8 +882,9 @@ inline double gammaCorrection(double linear)
|
|||||||
#ifdef PSD_FAST_LAB_CONVERSION
|
#ifdef PSD_FAST_LAB_CONVERSION
|
||||||
return linear;
|
return linear;
|
||||||
#else
|
#else
|
||||||
// NOTE: pow() slow down the performance by a 4 factor :(
|
// Replacing fastPow with std::pow the conversion time is 2/3 times longer: using fastPow
|
||||||
return (linear > 0.0031308 ? 1.055 * std::pow(linear, 1.0 / 2.4) - 0.055 : 12.92 * linear);
|
// there are minimal differences in the conversion that are not visually noticeable.
|
||||||
|
return (linear > 0.0031308 ? 1.055 * fastPow(linear, 1.0 / 2.4) - 0.055 : 12.92 * linear);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -892,6 +894,7 @@ inline void labToRgb(uchar *target, qint32 targetChannels, const char *source, q
|
|||||||
auto s = reinterpret_cast<const T*>(source);
|
auto s = reinterpret_cast<const T*>(source);
|
||||||
auto t = reinterpret_cast<T*>(target);
|
auto t = reinterpret_cast<T*>(target);
|
||||||
auto max = double(std::numeric_limits<T>::max());
|
auto max = double(std::numeric_limits<T>::max());
|
||||||
|
auto invmax = 1.0 / max;
|
||||||
|
|
||||||
if (sourceChannels < 3) {
|
if (sourceChannels < 3) {
|
||||||
qDebug() << "labToRgb: image is not a valid LAB!";
|
qDebug() << "labToRgb: image is not a valid LAB!";
|
||||||
@ -900,14 +903,14 @@ inline void labToRgb(uchar *target, qint32 targetChannels, const char *source, q
|
|||||||
|
|
||||||
for (qint32 w = 0; w < width; ++w) {
|
for (qint32 w = 0; w < width; ++w) {
|
||||||
auto ps = s + sourceChannels * w;
|
auto ps = s + sourceChannels * w;
|
||||||
auto L = (*(ps + 0) / max) * 100.0;
|
auto L = (*(ps + 0) * invmax) * 100.0;
|
||||||
auto A = (*(ps + 1) / max) * 255.0 - 128.0;
|
auto A = (*(ps + 1) * invmax) * 255.0 - 128.0;
|
||||||
auto B = (*(ps + 2) / max) * 255.0 - 128.0;
|
auto B = (*(ps + 2) * invmax) * 255.0 - 128.0;
|
||||||
|
|
||||||
// converting LAB to XYZ (D65 illuminant)
|
// converting LAB to XYZ (D65 illuminant)
|
||||||
auto Y = (L + 16.0) / 116.0;
|
auto Y = (L + 16.0) * (1.0 / 116.0);
|
||||||
auto X = A / 500.0 + Y;
|
auto X = A * (1.0 / 500.0) + Y;
|
||||||
auto Z = Y - B / 200.0;
|
auto Z = Y - B * (1.0 / 200.0);
|
||||||
|
|
||||||
// NOTE: use the constants of the illuminant of the target RGB color space
|
// NOTE: use the constants of the illuminant of the target RGB color space
|
||||||
X = finv(X) * 0.9504; // D50: * 0.9642
|
X = finv(X) * 0.9504; // D50: * 0.9642
|
||||||
|
@ -45,7 +45,6 @@ const auto supported_formats = QSet<QByteArray>{
|
|||||||
"dcs", "dc2", "dcr", "dng", "drf", "dxo",
|
"dcs", "dc2", "dcr", "dng", "drf", "dxo",
|
||||||
"eip", "erf",
|
"eip", "erf",
|
||||||
"fff",
|
"fff",
|
||||||
"hdr",
|
|
||||||
"iiq",
|
"iiq",
|
||||||
"k25", "kc2", "kdc",
|
"k25", "kc2", "kdc",
|
||||||
"mdc", "mef", "mfw", "mos", "mrw",
|
"mdc", "mef", "mfw", "mos", "mrw",
|
||||||
@ -141,7 +140,7 @@ public:
|
|||||||
if (whence == SEEK_END) {
|
if (whence == SEEK_END) {
|
||||||
pos = size + o;
|
pos = size + o;
|
||||||
}
|
}
|
||||||
if (pos < 0 || pos > size || m_device->isSequential()) {
|
if (pos < 0 || m_device->isSequential()) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
return m_device->seek(pos) ? 0 : -1;
|
return m_device->seek(pos) ? 0 : -1;
|
||||||
|
@ -7,7 +7,6 @@
|
|||||||
"dcs", "dc2", "dcr", "dng", "drf", "dxo",
|
"dcs", "dc2", "dcr", "dng", "drf", "dxo",
|
||||||
"eip", "erf",
|
"eip", "erf",
|
||||||
"fff",
|
"fff",
|
||||||
"hdr",
|
|
||||||
"iiq",
|
"iiq",
|
||||||
"k25", "kdc", "kc2",
|
"k25", "kdc", "kc2",
|
||||||
"mdc", "mef", "mfw", "mos", "mrw",
|
"mdc", "mef", "mfw", "mos", "mrw",
|
||||||
@ -27,7 +26,6 @@
|
|||||||
"image/x-kodak-dcs", "image/x-dc2", "image/x-kodak-dcr", "image/x-adobe-dng", "image/x-drf", "image/x-dxo",
|
"image/x-kodak-dcs", "image/x-dc2", "image/x-kodak-dcr", "image/x-adobe-dng", "image/x-drf", "image/x-dxo",
|
||||||
"image/x-epson-eip", "image/x-epson-erf",
|
"image/x-epson-eip", "image/x-epson-erf",
|
||||||
"image/x-fff",
|
"image/x-fff",
|
||||||
"image/x-hdr",
|
|
||||||
"image/x-iiq",
|
"image/x-iiq",
|
||||||
"image/x-kodak-k25", "image/x-kodak-kdc", "image/x-kodak-kc2",
|
"image/x-kodak-k25", "image/x-kodak-kdc", "image/x-kodak-kc2",
|
||||||
"image/x-minolta-mdc", "image/x-mamiya-mef", "image/x-mfw", "image/x-aptus-mos", "image/x-minolta-mrw",
|
"image/x-minolta-mdc", "image/x-mamiya-mef", "image/x-mfw", "image/x-aptus-mos", "image/x-minolta-mrw",
|
||||||
|
@ -430,6 +430,9 @@ bool TGAHandler::write(const QImage &image)
|
|||||||
|
|
||||||
const QImage &img = image;
|
const QImage &img = image;
|
||||||
const bool hasAlpha = (img.format() == QImage::Format_ARGB32);
|
const bool hasAlpha = (img.format() == QImage::Format_ARGB32);
|
||||||
|
static constexpr quint8 originTopLeft = TGA_ORIGIN_UPPER + TGA_ORIGIN_LEFT; // 0x20
|
||||||
|
static constexpr quint8 alphaChannel8Bits = 0x08;
|
||||||
|
|
||||||
for (int i = 0; i < 12; i++) {
|
for (int i = 0; i < 12; i++) {
|
||||||
s << targaMagic[i];
|
s << targaMagic[i];
|
||||||
}
|
}
|
||||||
@ -438,7 +441,7 @@ bool TGAHandler::write(const QImage &image)
|
|||||||
s << quint16(img.width()); // width
|
s << quint16(img.width()); // width
|
||||||
s << quint16(img.height()); // height
|
s << quint16(img.height()); // height
|
||||||
s << quint8(hasAlpha ? 32 : 24); // depth (24 bit RGB + 8 bit alpha)
|
s << quint8(hasAlpha ? 32 : 24); // depth (24 bit RGB + 8 bit alpha)
|
||||||
s << quint8(hasAlpha ? 0x24 : 0x20); // top left image (0x20) + 8 bit alpha (0x4)
|
s << quint8(hasAlpha ? originTopLeft + alphaChannel8Bits : originTopLeft); // top left image (0x20) + 8 bit alpha (0x8)
|
||||||
|
|
||||||
for (int y = 0; y < img.height(); y++) {
|
for (int y = 0; y < img.height(); y++) {
|
||||||
for (int x = 0; x < img.width(); x++) {
|
for (int x = 0; x < img.width(); x++) {
|
||||||
|