Compare commits

..

21 Commits

Author SHA1 Message Date
55227815d5 GIT_SILENT Upgrade ECM and KF version requirements for 5.107.0 release. 2023-06-03 09:46:49 +00:00
64d51ed610 pcx: multiple fixes (2)
- 1-bit writer: checks where is black and use NOT operator only if needed
- Fix images with witdh == 65536(*)
- Checks result of disk writes and reads on all formats

(*) PCX formats support images with with of 65536 but only if the header field bytesPerLine is valid (no overflow). This means that the width 65536 is supported on 1bpp images only.
The previous version of the plugins wrote an image with width of 65536px in the wrong way and it was unable to read it (wrong image returned). I verified that Photoshop and Gimp weren't able to read the image either.

(cherry picked from commit d57ff91f8b)
2023-05-25 23:58:42 +02:00
2ca57c9c59 Avoid unnecessary conversions
(cherry picked from commit edd6adcbac)
2023-05-25 23:58:42 +02:00
f7fd14d418 RGB/SGI writer: fix alpha detection and image limit size
(cherry picked from commit d787c12727)
2023-05-25 23:58:42 +02:00
c9aa1ff629 TGA writer: fix alpha detection and performance improvements
(cherry picked from commit c9fec5e408)
2023-05-25 23:58:42 +02:00
91d3bd5227 pcx: multiple fixes
- Fix wrong RGB channel order if image format is other than (A)RGB32
- Write right resolution
- Set right resolution on image load
- Return false on write error
- Save images with depth greater than 24-bits

(cherry picked from commit e60dfd4968)
2023-05-25 23:58:42 +02:00
bb66367bc8 PCX: Fix reading of the extended palette
The VGA palette starts 769 bytes before the end of the file. There may be PADs between the end of the image and the start of the palette.

BUG: 463951
(cherry picked from commit 14742cb502)
2023-05-25 23:58:42 +02:00
14770318a3 GIT_SILENT Upgrade ECM and KF version requirements for 5.106.0 release. 2023-05-06 09:25:52 +00:00
9b1fafe29b Fix wrong alpha conversion
and use tif for image comparison in this particular one instead of png

BUG: 468288
2023-04-12 20:34:33 +00:00
fa673b5df8 GIT_SILENT Upgrade ECM and KF version requirements for 5.105.0 release. 2023-03-31 09:08:23 +00:00
e96b43aef5 psd: Fix alpha blending (KF5)
PSD files are saved with as alpha premultiplied. The problem is that alpha refers to white instead of black so it requires transformation formulas. Then, to conver PS premultiplied to QImage premultiplied you have to use the following formula:

* V = Alpha + Vps - Max (C, M, Y, K, R, G, B, Gray, L\* components)
* V = Vps + (Alpha - Max + 1) / 2 (a\*, b\* components)

Where Max is the maximum value depending on the image depth and Vps is the valued read from the file.

This is a port of MR !143 to KF5.
2023-03-29 17:58:09 +00:00
64f3303ef0 GIT_SILENT Upgrade ECM and KF version requirements for 5.104.0 release. 2023-03-04 10:04:16 +00:00
63056c52f9 GIT_SILENT Upgrade ECM and KF version requirements for 5.103.0 release. 2023-02-05 09:22:42 +00:00
2997f7ae8d psd: conversion speed improvements (kf5)
- Improved performance converting CMYK files by \~10% by replacing divisions with multiplications.
- Improved performance converting LAB files by \~50% by replacing std::pow with fastPow (approximated pow function).
2023-02-03 20:55:49 +00:00
0b4741f4b7 Fix writing TGA alpha depth flag
Correctly write alpha channel depth as 8-bit.

(cherry picked from commit 20cec27ae8)
2023-02-02 01:11:32 +01:00
bc52c03981 HDR support removed from RAW plugin 2023-01-30 21:59:09 +00:00
c1c57d9a11 heif: reject invalid files with zero size 2023-01-29 16:21:01 +01:00
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
25 changed files with 868 additions and 606 deletions

View File

@ -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.107.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)

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

BIN
autotests/read/pcx/mono.pcx Normal file

Binary file not shown.

BIN
autotests/read/pcx/mono.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 983 B

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 189 KiB

After

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 114 KiB

View File

@ -159,17 +159,22 @@ int main(int argc, char **argv)
QTextStream(stdout) << "* Run on RANDOM ACCESS device\n"; QTextStream(stdout) << "* Run on RANDOM ACCESS device\n";
} }
for (const QFileInfo &fi : lstImgDir) { for (const QFileInfo &fi : lstImgDir) {
if (!fi.suffix().compare("png", Qt::CaseInsensitive)) { if (!fi.suffix().compare("png", Qt::CaseInsensitive) || !fi.suffix().compare("tif", Qt::CaseInsensitive)) {
continue; continue;
} }
int suffixPos = fi.filePath().count() - suffix.count(); int suffixPos = fi.filePath().count() - suffix.count();
QString inputfile = fi.filePath(); QString inputfile = fi.filePath();
QString expfile = fi.filePath().replace(suffixPos, suffix.count(), QStringLiteral("png")); QString fmt = QStringLiteral("png");
QString expfile = fi.filePath().replace(suffixPos, suffix.count(), fmt);
if (!QFile::exists(expfile)) { // try with tiff
fmt = QStringLiteral("tif");
expfile = fi.filePath().replace(suffixPos, suffix.count(), fmt);
}
QString expfilename = QFileInfo(expfile).fileName(); QString expfilename = QFileInfo(expfile).fileName();
std::unique_ptr<QIODevice> inputDevice(seq ? new SequentialFile(inputfile) : new QFile(inputfile)); std::unique_ptr<QIODevice> inputDevice(seq ? new SequentialFile(inputfile) : new QFile(inputfile));
QImageReader inputReader(inputDevice.get(), format); QImageReader inputReader(inputDevice.get(), format);
QImageReader expReader(expfile, "png"); QImageReader expReader(expfile, fmt.toLatin1());
QImage inputImage; QImage inputImage;
QImage expImage; QImage expImage;

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -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/)

View 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

View File

@ -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;
} }

View File

@ -230,7 +230,7 @@ PCXHEADER::PCXHEADER()
s >> *this; s >> *this;
} }
static void readLine(QDataStream &s, QByteArray &buf, const PCXHEADER &header) static bool readLine(QDataStream &s, QByteArray &buf, const PCXHEADER &header)
{ {
quint32 i = 0; quint32 i = 0;
quint32 size = buf.size(); quint32 size = buf.size();
@ -257,9 +257,11 @@ static void readLine(QDataStream &s, QByteArray &buf, const PCXHEADER &header)
buf[i++] = byte; buf[i++] = byte;
} }
} }
return (s.status() == QDataStream::Ok);
} }
static void readImage1(QImage &img, QDataStream &s, const PCXHEADER &header) static bool readImage1(QImage &img, QDataStream &s, const PCXHEADER &header)
{ {
QByteArray buf(header.BytesPerLine, 0); QByteArray buf(header.BytesPerLine, 0);
@ -268,16 +270,18 @@ static void readImage1(QImage &img, QDataStream &s, const PCXHEADER &header)
if (img.isNull()) { if (img.isNull()) {
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height()); qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());
return; return false;
} }
for (int y = 0; y < header.height(); ++y) { for (int y = 0; y < header.height(); ++y) {
if (s.atEnd()) { if (s.atEnd()) {
img = QImage(); return false;
return; }
if (!readLine(s, buf, header)) {
return false;
} }
readLine(s, buf, header);
uchar *p = img.scanLine(y); uchar *p = img.scanLine(y);
unsigned int bpl = qMin((quint16)((header.width() + 7) / 8), header.BytesPerLine); unsigned int bpl = qMin((quint16)((header.width() + 7) / 8), header.BytesPerLine);
for (unsigned int x = 0; x < bpl; ++x) { for (unsigned int x = 0; x < bpl; ++x) {
@ -288,9 +292,11 @@ static void readImage1(QImage &img, QDataStream &s, const PCXHEADER &header)
// Set the color palette // Set the color palette
img.setColor(0, qRgb(0, 0, 0)); img.setColor(0, qRgb(0, 0, 0));
img.setColor(1, qRgb(255, 255, 255)); img.setColor(1, qRgb(255, 255, 255));
return true;
} }
static void readImage4(QImage &img, QDataStream &s, const PCXHEADER &header) static bool readImage4(QImage &img, QDataStream &s, const PCXHEADER &header)
{ {
QByteArray buf(header.BytesPerLine * 4, 0); QByteArray buf(header.BytesPerLine * 4, 0);
QByteArray pixbuf(header.width(), 0); QByteArray pixbuf(header.width(), 0);
@ -299,17 +305,18 @@ static void readImage4(QImage &img, QDataStream &s, const PCXHEADER &header)
img.setColorCount(16); img.setColorCount(16);
if (img.isNull()) { if (img.isNull()) {
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height()); qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());
return; return false;
} }
for (int y = 0; y < header.height(); ++y) { for (int y = 0; y < header.height(); ++y) {
if (s.atEnd()) { if (s.atEnd()) {
img = QImage(); return false;
return;
} }
pixbuf.fill(0); pixbuf.fill(0);
readLine(s, buf, header); if (!readLine(s, buf, header)) {
return false;
}
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
quint32 offset = i * header.BytesPerLine; quint32 offset = i * header.BytesPerLine;
@ -333,9 +340,11 @@ static void readImage4(QImage &img, QDataStream &s, const PCXHEADER &header)
for (int i = 0; i < 16; ++i) { for (int i = 0; i < 16; ++i) {
img.setColor(i, header.ColorMap.color(i)); img.setColor(i, header.ColorMap.color(i));
} }
return true;
} }
static void readImage8(QImage &img, QDataStream &s, const PCXHEADER &header) static bool readImage8(QImage &img, QDataStream &s, const PCXHEADER &header)
{ {
QByteArray buf(header.BytesPerLine, 0); QByteArray buf(header.BytesPerLine, 0);
@ -344,21 +353,21 @@ static void readImage8(QImage &img, QDataStream &s, const PCXHEADER &header)
if (img.isNull()) { if (img.isNull()) {
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height()); qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());
return; return false;
} }
for (int y = 0; y < header.height(); ++y) { for (int y = 0; y < header.height(); ++y) {
if (s.atEnd()) { if (s.atEnd()) {
img = QImage(); return false;
return;
} }
readLine(s, buf, header); if (!readLine(s, buf, header)) {
return false;
}
uchar *p = img.scanLine(y); uchar *p = img.scanLine(y);
if (!p) { if (!p) {
return; return false;
} }
unsigned int bpl = qMin(header.BytesPerLine, (quint16)header.width()); unsigned int bpl = qMin(header.BytesPerLine, (quint16)header.width());
@ -367,10 +376,21 @@ static void readImage8(QImage &img, QDataStream &s, const PCXHEADER &header)
} }
} }
quint8 flag; // by specification, the extended palette starts at file.size() - 769
quint8 flag = 0;
if (auto device = s.device()) {
if (device->isSequential()) {
while (flag != 12 && s.status() == QDataStream::Ok) {
s >> flag; s >> flag;
// qDebug() << "Palette Flag: " << flag; }
}
else {
device->seek(device->size() - 769);
s >> flag;
}
}
// qDebug() << "Palette Flag: " << flag;
if (flag == 12 && (header.Version == 5 || header.Version == 2)) { if (flag == 12 && (header.Version == 5 || header.Version == 2)) {
// Read the palette // Read the palette
quint8 r; quint8 r;
@ -381,9 +401,11 @@ static void readImage8(QImage &img, QDataStream &s, const PCXHEADER &header)
img.setColor(i, qRgb(r, g, b)); img.setColor(i, qRgb(r, g, b));
} }
} }
return (s.status() == QDataStream::Ok);
} }
static void readImage24(QImage &img, QDataStream &s, const PCXHEADER &header) static bool readImage24(QImage &img, QDataStream &s, const PCXHEADER &header)
{ {
QByteArray r_buf(header.BytesPerLine, 0); QByteArray r_buf(header.BytesPerLine, 0);
QByteArray g_buf(header.BytesPerLine, 0); QByteArray g_buf(header.BytesPerLine, 0);
@ -393,27 +415,34 @@ static void readImage24(QImage &img, QDataStream &s, const PCXHEADER &header)
if (img.isNull()) { if (img.isNull()) {
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height()); qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());
return; return false;
} }
for (int y = 0; y < header.height(); ++y) { for (int y = 0; y < header.height(); ++y) {
if (s.atEnd()) { if (s.atEnd()) {
img = QImage(); return false;
return;
} }
readLine(s, r_buf, header); if (!readLine(s, r_buf, header)) {
readLine(s, g_buf, header); return false;
readLine(s, b_buf, header); }
if (!readLine(s, g_buf, header)) {
return false;
}
if (!readLine(s, b_buf, header)) {
return false;
}
uint *p = (uint *)img.scanLine(y); uint *p = (uint *)img.scanLine(y);
for (int x = 0; x < header.width(); ++x) { for (int x = 0; x < header.width(); ++x) {
p[x] = qRgb(r_buf[x], g_buf[x], b_buf[x]); p[x] = qRgb(r_buf[x], g_buf[x], b_buf[x]);
} }
} }
return true;
} }
static void writeLine(QDataStream &s, QByteArray &buf) static bool writeLine(QDataStream &s, QByteArray &buf)
{ {
quint32 i = 0; quint32 i = 0;
quint32 size = buf.size(); quint32 size = buf.size();
@ -439,15 +468,26 @@ static void writeLine(QDataStream &s, QByteArray &buf)
s << data; s << data;
} }
return (s.status() == QDataStream::Ok);
} }
static void writeImage1(QImage &img, QDataStream &s, PCXHEADER &header) static bool writeImage1(QImage &img, QDataStream &s, PCXHEADER &header)
{ {
if (img.format() != QImage::Format_Mono) {
img = img.convertToFormat(QImage::Format_Mono); img = img.convertToFormat(QImage::Format_Mono);
}
if (img.isNull() || img.colorCount() < 1) {
return false;
}
auto rgb = img.color(0);
auto minIsBlack = (qRed(rgb) + qGreen(rgb) + qBlue(rgb)) / 3 < 127;
header.Bpp = 1; header.Bpp = 1;
header.NPlanes = 1; header.NPlanes = 1;
header.BytesPerLine = img.bytesPerLine(); header.BytesPerLine = img.bytesPerLine();
if (header.BytesPerLine == 0) {
return false;
}
s << header; s << header;
@ -458,18 +498,24 @@ static void writeImage1(QImage &img, QDataStream &s, PCXHEADER &header)
// Invert as QImage uses reverse palette for monochrome images? // Invert as QImage uses reverse palette for monochrome images?
for (int i = 0; i < header.BytesPerLine; ++i) { for (int i = 0; i < header.BytesPerLine; ++i) {
buf[i] = ~p[i]; buf[i] = minIsBlack ? p[i] : ~p[i];
} }
writeLine(s, buf); if (!writeLine(s, buf)) {
return false;
} }
}
return true;
} }
static void writeImage4(QImage &img, QDataStream &s, PCXHEADER &header) static bool writeImage4(QImage &img, QDataStream &s, PCXHEADER &header)
{ {
header.Bpp = 1; header.Bpp = 1;
header.NPlanes = 4; header.NPlanes = 4;
header.BytesPerLine = header.width() / 8; header.BytesPerLine = header.width() / 8;
if (header.BytesPerLine == 0) {
return false;
}
for (int i = 0; i < 16; ++i) { for (int i = 0; i < 16; ++i) {
header.ColorMap.setColor(i, img.color(i)); header.ColorMap.setColor(i, img.color(i));
@ -499,16 +545,22 @@ static void writeImage4(QImage &img, QDataStream &s, PCXHEADER &header)
} }
for (int i = 0; i < 4; ++i) { for (int i = 0; i < 4; ++i) {
writeLine(s, buf[i]); if (!writeLine(s, buf[i])) {
return false;
} }
} }
}
return true;
} }
static void writeImage8(QImage &img, QDataStream &s, PCXHEADER &header) static bool writeImage8(QImage &img, QDataStream &s, PCXHEADER &header)
{ {
header.Bpp = 8; header.Bpp = 8;
header.NPlanes = 1; header.NPlanes = 1;
header.BytesPerLine = img.bytesPerLine(); header.BytesPerLine = img.bytesPerLine();
if (header.BytesPerLine == 0) {
return false;
}
s << header; s << header;
@ -521,7 +573,9 @@ static void writeImage8(QImage &img, QDataStream &s, PCXHEADER &header)
buf[i] = p[i]; buf[i] = p[i];
} }
writeLine(s, buf); if (!writeLine(s, buf)) {
return false;
}
} }
// Write palette flag // Write palette flag
@ -532,13 +586,25 @@ static void writeImage8(QImage &img, QDataStream &s, PCXHEADER &header)
for (int i = 0; i < 256; ++i) { for (int i = 0; i < 256; ++i) {
s << RGB::from(img.color(i)); s << RGB::from(img.color(i));
} }
return (s.status() == QDataStream::Ok);
} }
static void writeImage24(QImage &img, QDataStream &s, PCXHEADER &header) static bool writeImage24(QImage &img, QDataStream &s, PCXHEADER &header)
{ {
header.Bpp = 8; header.Bpp = 8;
header.NPlanes = 3; header.NPlanes = 3;
header.BytesPerLine = header.width(); header.BytesPerLine = header.width();
if (header.BytesPerLine == 0) {
return false;
}
if (img.format() != QImage::Format_ARGB32 && img.format() != QImage::Format_RGB32) {
img = img.convertToFormat(QImage::Format_RGB32);
}
if (img.isNull()) {
return false;
}
s << header; s << header;
@ -547,7 +613,7 @@ static void writeImage24(QImage &img, QDataStream &s, PCXHEADER &header)
QByteArray b_buf(header.width(), 0); QByteArray b_buf(header.width(), 0);
for (int y = 0; y < header.height(); ++y) { for (int y = 0; y < header.height(); ++y) {
uint *p = (uint *)img.scanLine(y); auto p = (QRgb*)img.scanLine(y);
for (int x = 0; x < header.width(); ++x) { for (int x = 0; x < header.width(); ++x) {
QRgb rgb = *p++; QRgb rgb = *p++;
@ -556,10 +622,18 @@ static void writeImage24(QImage &img, QDataStream &s, PCXHEADER &header)
b_buf[x] = qBlue(rgb); b_buf[x] = qBlue(rgb);
} }
writeLine(s, r_buf); if (!writeLine(s, r_buf)) {
writeLine(s, g_buf); return false;
writeLine(s, b_buf);
} }
if (!writeLine(s, g_buf)) {
return false;
}
if (!writeLine(s, b_buf)) {
return false;
}
}
return true;
} }
PCXHandler::PCXHandler() PCXHandler::PCXHandler()
@ -588,46 +662,30 @@ bool PCXHandler::read(QImage *outImage)
s >> header; s >> header;
if (header.Manufacturer != 10 || s.atEnd()) { if (header.Manufacturer != 10 || header.BytesPerLine == 0 || s.atEnd()) {
return false; return false;
} }
// int w = header.width(); auto ok = false;
// int h = header.height();
// qDebug() << "Manufacturer: " << header.Manufacturer;
// qDebug() << "Version: " << header.Version;
// qDebug() << "Encoding: " << header.Encoding;
// qDebug() << "Bpp: " << header.Bpp;
// qDebug() << "Width: " << w;
// qDebug() << "Height: " << h;
// qDebug() << "Window: " << header.XMin << "," << header.XMax << ","
// << header.YMin << "," << header.YMax << endl;
// qDebug() << "BytesPerLine: " << header.BytesPerLine;
// qDebug() << "NPlanes: " << header.NPlanes;
QImage img; QImage img;
if (header.Bpp == 1 && header.NPlanes == 1) { if (header.Bpp == 1 && header.NPlanes == 1) {
readImage1(img, s, header); ok = readImage1(img, s, header);
} else if (header.Bpp == 1 && header.NPlanes == 4) { } else if (header.Bpp == 1 && header.NPlanes == 4) {
readImage4(img, s, header); ok = readImage4(img, s, header);
} else if (header.Bpp == 8 && header.NPlanes == 1) { } else if (header.Bpp == 8 && header.NPlanes == 1) {
readImage8(img, s, header); ok = readImage8(img, s, header);
} else if (header.Bpp == 8 && header.NPlanes == 3) { } else if (header.Bpp == 8 && header.NPlanes == 3) {
readImage24(img, s, header); ok = readImage24(img, s, header);
} }
// qDebug() << "Image Bytes: " << img.numBytes(); if (img.isNull() || !ok) {
// qDebug() << "Image Bytes Per Line: " << img.bytesPerLine(); return false;
// qDebug() << "Image Depth: " << img.depth(); }
if (!img.isNull()) { img.setDotsPerMeterX(qRound(header.HDpi / 25.4 * 1000));
img.setDotsPerMeterY(qRound(header.YDpi / 25.4 * 1000));
*outImage = img; *outImage = img;
return true; return true;
} else {
return false;
}
} }
bool PCXHandler::write(const QImage &image) bool PCXHandler::write(const QImage &image)
@ -644,12 +702,6 @@ bool PCXHandler::write(const QImage &image)
return false; return false;
} }
// qDebug() << "Width: " << w;
// qDebug() << "Height: " << h;
// qDebug() << "Depth: " << img.depth();
// qDebug() << "BytesPerLine: " << img.bytesPerLine();
// qDebug() << "Color Count: " << img.colorCount();
PCXHEADER header; PCXHEADER header;
header.Manufacturer = 10; header.Manufacturer = 10;
@ -659,22 +711,23 @@ bool PCXHandler::write(const QImage &image)
header.YMin = 0; header.YMin = 0;
header.XMax = w - 1; header.XMax = w - 1;
header.YMax = h - 1; header.YMax = h - 1;
header.HDpi = 300; header.HDpi = qRound(image.dotsPerMeterX() * 25.4 / 1000);
header.YDpi = 300; header.YDpi = qRound(image.dotsPerMeterY() * 25.4 / 1000);
header.Reserved = 0; header.Reserved = 0;
header.PaletteInfo = 1; header.PaletteInfo = 1;
auto ok = false;
if (img.depth() == 1) { if (img.depth() == 1) {
writeImage1(img, s, header); ok = writeImage1(img, s, header);
} else if (img.depth() == 8 && img.colorCount() <= 16) { } else if (img.depth() == 8 && img.colorCount() <= 16) {
writeImage4(img, s, header); ok = writeImage4(img, s, header);
} else if (img.depth() == 8) { } else if (img.depth() == 8) {
writeImage8(img, s, header); ok = writeImage8(img, s, header);
} else if (img.depth() == 32) { } else if (img.depth() >= 24) {
writeImage24(img, s, header); ok = writeImage24(img, s, header);
} }
return true; return ok;
} }
bool PCXHandler::canRead(QIODevice *device) bool PCXHandler::canRead(QIODevice *device)

View File

@ -3,14 +3,14 @@
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
*/ */
/* /*
* This code is based on Thacher Ulrich PSD loading code released * The early version of this code was based on Thacher Ulrich PSD loading code
* into the public domain. See: http://tulrich.com/geekstuff/ * released into the public domain. See: http://tulrich.com/geekstuff/
*/ */
/* /*
@ -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
@ -733,9 +733,9 @@ static QImage::Format imageFormat(const PSDHeader &header, bool alpha)
switch(header.color_mode) { switch(header.color_mode) {
case CM_RGB: case CM_RGB:
if (header.depth == 16 || header.depth == 32) if (header.depth == 16 || header.depth == 32)
format = header.channel_count < 4 || !alpha ? QImage::Format_RGBX64 : QImage::Format_RGBA64; format = header.channel_count < 4 || !alpha ? QImage::Format_RGBX64 : QImage::Format_RGBA64_Premultiplied;
else else
format = header.channel_count < 4 || !alpha ? QImage::Format_RGB888 : QImage::Format_RGBA8888; format = header.channel_count < 4 || !alpha ? QImage::Format_RGB888 : QImage::Format_RGBA8888_Premultiplied;
break; break;
case CM_MULTICHANNEL: // Treat MCH as CMYK (number of channel check is done in IsSupported()) case CM_MULTICHANNEL: // Treat MCH as CMYK (number of channel check is done in IsSupported())
case CM_CMYK: // Photoshop supports CMYK/MCH 8-bits and 16-bits only case CM_CMYK: // Photoshop supports CMYK/MCH 8-bits and 16-bits only
@ -814,7 +814,7 @@ inline void planarToChunchy(uchar *target, const char *source, qint32 width, qin
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);
for (qint32 x = 0; x < width; ++x) { for (qint32 x = 0; x < width; ++x) {
t[x*cn+c] = xchg(s[x]); t[x * cn + c] = xchg(s[x]);
} }
} }
@ -826,7 +826,44 @@ inline void planarToChunchyFloat(uchar *target, const char *source, qint32 width
for (qint32 x = 0; x < width; ++x) { for (qint32 x = 0; x < width; ++x) {
auto tmp = xchg(s[x]); auto tmp = xchg(s[x]);
auto ftmp = (*reinterpret_cast<float*>(&tmp) - double(min)) / (double(max) - double(min)); auto ftmp = (*reinterpret_cast<float*>(&tmp) - double(min)) / (double(max) - double(min));
t[x*cn+c] = quint16(std::min(ftmp * std::numeric_limits<quint16>::max() + 0.5, double(std::numeric_limits<quint16>::max()))); t[x * cn + c] = quint16(std::min(ftmp * std::numeric_limits<quint16>::max() + 0.5, double(std::numeric_limits<quint16>::max())));
}
}
enum class PremulConversion {
PS2P, // Photoshop premul to qimage premul (required by RGB)
PS2A, // Photoshop premul to unassociated alpha (required by RGB, CMYK and L* components of LAB)
PSLab2A // Photoshop premul to unassociated alpha (required by a* and b* components of LAB)
};
template<class T>
inline void premulConversion(char *stride, qint32 width, qint32 ac, qint32 cn, const PremulConversion &conv)
{
auto s = reinterpret_cast<T *>(stride);
auto max = qint64(std::numeric_limits<T>::max());
for (qint32 c = 0; c < ac; ++c) {
if (conv == PremulConversion::PS2P) {
for (qint32 x = 0; x < width; ++x) {
auto xcn = x * cn;
auto alpha = *(s + xcn + ac);
*(s + xcn + c) = *(s + xcn + c) + alpha - max;
}
} else if (conv == PremulConversion::PS2A || (conv == PremulConversion::PSLab2A && c == 0)) {
for (qint32 x = 0; x < width; ++x) {
auto xcn = x * cn;
auto alpha = *(s + xcn + ac);
if (alpha > 0)
*(s + xcn + c) = ((*(s + xcn + c) + alpha - max) * max + alpha / 2) / alpha;
}
} else if (conv == PremulConversion::PSLab2A) {
for (qint32 x = 0; x < width; ++x) {
auto xcn = x * cn;
auto alpha = *(s + xcn + ac);
if (alpha > 0)
*(s + xcn + c) = ((*(s + xcn + c) + (alpha - max + 1) / 2) * max + alpha / 2) / alpha;
}
}
} }
} }
@ -839,12 +876,25 @@ inline void monoInvert(uchar *target, const char* source, qint32 bytes)
} }
} }
template<class T>
inline void rawChannelsCopy(uchar *target, qint32 targetChannels, const char *source, qint32 sourceChannels, qint32 width)
{
auto s = reinterpret_cast<const T *>(source);
auto t = reinterpret_cast<T *>(target);
for (qint32 c = 0, cs = std::min(targetChannels, sourceChannels); c < cs; ++c) {
for (qint32 x = 0; x < width; ++x) {
t[x * targetChannels + c] = s[x * sourceChannels + c];
}
}
}
template<class T> template<class T>
inline void cmykToRgb(uchar *target, qint32 targetChannels, const char *source, qint32 sourceChannels, qint32 width, bool alpha = false) inline void cmykToRgb(uchar *target, qint32 targetChannels, const char *source, qint32 sourceChannels, qint32 width, bool alpha = false)
{ {
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 +903,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 +931,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 +943,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 +952,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
@ -1057,7 +1109,15 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
QByteArray rawStride; QByteArray rawStride;
rawStride.resize(raw_count); rawStride.resize(raw_count);
if (header.color_mode == CM_CMYK || header.color_mode == CM_LABCOLOR || header.color_mode == CM_MULTICHANNEL) { // clang-format off
// checks the need of color conversion (that requires random access to the image)
auto randomAccess = (header.color_mode == CM_CMYK) ||
(header.color_mode == CM_LABCOLOR) ||
(header.color_mode == CM_MULTICHANNEL) ||
(header.color_mode != CM_INDEXED && img.hasAlphaChannel());
// clang-format on
if (randomAccess) {
// In order to make a colorspace transformation, we need all channels of a scanline // In order to make a colorspace transformation, we need all channels of a scanline
QByteArray psdScanline; QByteArray psdScanline;
psdScanline.resize(qsizetype(header.width * std::min(header.depth, quint16(16)) * header.channel_count + 7) / 8); psdScanline.resize(qsizetype(header.width * std::min(header.depth, quint16(16)) * header.channel_count + 7) / 8);
@ -1077,31 +1137,56 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
auto scanLine = reinterpret_cast<unsigned char*>(psdScanline.data()); auto scanLine = reinterpret_cast<unsigned char*>(psdScanline.data());
if (header.depth == 8) { if (header.depth == 8) {
planarToChunchy<quint8>(scanLine, rawStride.data(), header.width, c, header.channel_count); planarToChunchy<quint8>(scanLine, rawStride.data(), header.width, c, header.channel_count);
} } else if (header.depth == 16) {
else if (header.depth == 16) {
planarToChunchy<quint16>(scanLine, rawStride.data(), header.width, c, header.channel_count); planarToChunchy<quint16>(scanLine, rawStride.data(), header.width, c, header.channel_count);
} } else if (header.depth == 32) {
else if (header.depth == 32) { // Not currently used
planarToChunchyFloat<quint32>(scanLine, rawStride.data(), header.width, c, header.channel_count); planarToChunchyFloat<quint32>(scanLine, rawStride.data(), header.width, c, header.channel_count);
} }
} }
// Convert premultiplied data to unassociated data
if (img.hasAlphaChannel()) {
if (header.color_mode == CM_CMYK) {
if (header.depth == 8)
premulConversion<quint8>(psdScanline.data(), header.width, 4, header.channel_count, PremulConversion::PS2A);
else if (header.depth == 16)
premulConversion<quint16>(psdScanline.data(), header.width, 4, header.channel_count, PremulConversion::PS2A);
}
if (header.color_mode == CM_LABCOLOR) {
if (header.depth == 8)
premulConversion<quint8>(psdScanline.data(), header.width, 3, header.channel_count, PremulConversion::PSLab2A);
else if (header.depth == 16)
premulConversion<quint16>(psdScanline.data(), header.width, 3, header.channel_count, PremulConversion::PSLab2A);
}
if (header.color_mode == CM_RGB) {
if (header.depth == 8)
premulConversion<quint8>(psdScanline.data(), header.width, 3, header.channel_count, PremulConversion::PS2P);
else if (header.depth == 16 || header.depth == 32)
premulConversion<quint16>(psdScanline.data(), header.width, 3, header.channel_count, PremulConversion::PS2P);
}
}
// Conversion to RGB // Conversion to RGB
if (header.color_mode == CM_CMYK || header.color_mode == CM_MULTICHANNEL) { if (header.color_mode == CM_CMYK || header.color_mode == CM_MULTICHANNEL) {
if (header.depth == 8) if (header.depth == 8)
cmykToRgb<quint8>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha); cmykToRgb<quint8>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha);
else else if (header.depth == 16)
cmykToRgb<quint16>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha); cmykToRgb<quint16>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha);
} }
if (header.color_mode == CM_LABCOLOR) { if (header.color_mode == CM_LABCOLOR) {
if (header.depth == 8) if (header.depth == 8)
labToRgb<quint8>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha); labToRgb<quint8>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha);
else else if (header.depth == 16)
labToRgb<quint16>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha); labToRgb<quint16>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha);
} }
if (header.color_mode == CM_RGB) {
if (header.depth == 8)
rawChannelsCopy<quint8>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width);
else if (header.depth == 16 || header.depth == 32)
rawChannelsCopy<quint16>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width);
} }
} }
else { } else {
// Linear read (no position jumps): optimized code usable only for the colorspaces supported by QImage // Linear read (no position jumps): optimized code usable only for the colorspaces supported by QImage
for (qint32 c = 0; c < channel_num; ++c) { for (qint32 c = 0; c < channel_num; ++c) {
for (qint32 y = 0, h = header.height; y < h; ++y) { for (qint32 y = 0, h = header.height; y < h; ++y) {
@ -1114,14 +1199,11 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
auto scanLine = img.scanLine(y); auto scanLine = img.scanLine(y);
if (header.depth == 1) { // Bitmap if (header.depth == 1) { // Bitmap
monoInvert(scanLine, rawStride.data(), std::min(rawStride.size(), img.bytesPerLine())); monoInvert(scanLine, rawStride.data(), std::min(rawStride.size(), img.bytesPerLine()));
} } else if (header.depth == 8) { // 8-bits images: Indexed, Grayscale, RGB/RGBA
else if (header.depth == 8) { // 8-bits images: Indexed, Grayscale, RGB/RGBA
planarToChunchy<quint8>(scanLine, rawStride.data(), header.width, c, imgChannels); planarToChunchy<quint8>(scanLine, rawStride.data(), header.width, c, imgChannels);
} } else if (header.depth == 16) { // 16-bits integer images: Grayscale, RGB/RGBA
else if (header.depth == 16) { // 16-bits integer images: Grayscale, RGB/RGBA
planarToChunchy<quint16>(scanLine, rawStride.data(), header.width, c, imgChannels); planarToChunchy<quint16>(scanLine, rawStride.data(), header.width, c, imgChannels);
} } else if (header.depth == 32) { // 32-bits float images: Grayscale, RGB/RGBA (coverted to equivalent integer 16-bits)
else if (header.depth == 32) { // 32-bits float images: Grayscale, RGB/RGBA (coverted to equivalent integer 16-bits)
planarToChunchyFloat<quint32>(scanLine, rawStride.data(), header.width, c, imgChannels); planarToChunchyFloat<quint32>(scanLine, rawStride.data(), header.width, c, imgChannels);
} }
} }
@ -1264,6 +1346,9 @@ bool PSDHandler::canRead(QIODevice *device)
if (header.color_mode == CM_CMYK || header.color_mode == CM_LABCOLOR || header.color_mode == CM_MULTICHANNEL) { if (header.color_mode == CM_CMYK || header.color_mode == CM_LABCOLOR || header.color_mode == CM_MULTICHANNEL) {
return false; return false;
} }
if (header.color_mode == CM_RGB && header.channel_count > 3) {
return false; // supposing extra channel as alpha
}
} }
return IsSupported(header); return IsSupported(header);

View File

@ -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;

View File

@ -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",

View File

@ -672,11 +672,16 @@ bool SGIImage::writeImage(const QImage &image)
_dim = 3, _zsize = 3; _dim = 3, _zsize = 3;
} }
if (img.format() == QImage::Format_ARGB32) { auto hasAlpha = img.hasAlphaChannel();
if (hasAlpha) {
_dim = 3, _zsize++; _dim = 3, _zsize++;
} }
if (hasAlpha && img.format() != QImage::Format_ARGB32) {
img = img.convertToFormat(QImage::Format_ARGB32);
} else if (!hasAlpha && img.format() != QImage::Format_RGB32) {
img = img.convertToFormat(QImage::Format_RGB32); img = img.convertToFormat(QImage::Format_RGB32);
}
if (img.isNull()) { if (img.isNull()) {
// qDebug() << "can't convert image to depth 32"; // qDebug() << "can't convert image to depth 32";
return false; return false;
@ -685,7 +690,7 @@ bool SGIImage::writeImage(const QImage &image)
const int w = img.width(); const int w = img.width();
const int h = img.height(); const int h = img.height();
if (w > 65536 || h > 65536) { if (w > 65535 || h > 65535) {
return false; return false;
} }
@ -712,12 +717,6 @@ bool SGIImage::writeImage(const QImage &image)
rle_size += _rlevector[i]->size(); rle_size += _rlevector[i]->size();
} }
// qDebug() << "minimum intensity: " << _pixmin;
// qDebug() << "maximum intensity: " << _pixmax;
// qDebug() << "saved scanlines: " << _numrows - _rlemap.size();
// qDebug() << "total savings: " << (verbatim_size - rle_size) << " bytes";
// qDebug() << "compression: " << (rle_size * 100.0 / verbatim_size) << '%';
if (verbatim_size <= rle_size) { if (verbatim_size <= rle_size) {
writeVerbatim(img); writeVerbatim(img);
} else { } else {

View File

@ -428,8 +428,20 @@ bool TGAHandler::write(const QImage &image)
QDataStream s(device()); QDataStream s(device());
s.setByteOrder(QDataStream::LittleEndian); s.setByteOrder(QDataStream::LittleEndian);
const QImage &img = image; QImage img(image);
const bool hasAlpha = (img.format() == QImage::Format_ARGB32); const bool hasAlpha = img.hasAlphaChannel();
if (hasAlpha && img.format() != QImage::Format_ARGB32) {
img = img.convertToFormat(QImage::Format_ARGB32);
} else if (!hasAlpha && img.format() != QImage::Format_RGB32) {
img = img.convertToFormat(QImage::Format_RGB32);
}
if (img.isNull()) {
qDebug() << "TGAHandler::write: image conversion to 32 bits failed!";
return false;
}
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,11 +450,12 @@ 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++) {
auto ptr = reinterpret_cast<QRgb *>(img.scanLine(y));
for (int x = 0; x < img.width(); x++) { for (int x = 0; x < img.width(); x++) {
const QRgb color = img.pixel(x, y); auto color = *(ptr + x);
s << quint8(qBlue(color)); s << quint8(qBlue(color));
s << quint8(qGreen(color)); s << quint8(qGreen(color));
s << quint8(qRed(color)); s << quint8(qRed(color));