Compare commits

..

1 Commits

Author SHA1 Message Date
0ee63388c8 Fix condition for installing desktop files
(cherry picked from commit 9ad82ed608)
2022-11-07 13:41:41 +00:00
112 changed files with 1040 additions and 3062 deletions

View File

@ -6,5 +6,9 @@ include:
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux-static.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/android.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/freebsd.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux-qt6.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/android-qt6.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/windows.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/windows-static.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/freebsd-qt6.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/windows-qt6.yml

View File

@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.16)
project(KImageFormats)
include(FeatureSummary)
find_package(ECM 5.111.0 NO_MODULE)
find_package(ECM 5.100.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)
@ -51,10 +51,7 @@ set_package_properties(OpenEXR PROPERTIES
PURPOSE "Required for the QImage plugin for OpenEXR images"
)
find_package(libavif 0.8.2 CONFIG QUIET)
if(NOT libavif_FOUND)
find_package(libavif 1 CONFIG)
endif()
find_package(libavif 0.8.2 CONFIG)
set_package_properties(libavif PROPERTIES
TYPE OPTIONAL
PURPOSE "Required for the QImage plugin for AVIF images"
@ -68,8 +65,8 @@ add_feature_info(LibHeif LibHeif_FOUND "required for the QImage plugin for HEIF/
option(KIMAGEFORMATS_JXL "Enable plugin for JPEG XL format" ON)
if(KIMAGEFORMATS_JXL)
pkg_check_modules(LibJXL IMPORTED_TARGET libjxl>=0.7.0)
pkg_check_modules(LibJXLThreads IMPORTED_TARGET libjxl_threads>=0.7.0)
pkg_check_modules(LibJXL IMPORTED_TARGET libjxl>=0.6.1)
pkg_check_modules(LibJXLThreads IMPORTED_TARGET libjxl_threads>=0.6.1)
endif()
add_feature_info(LibJXL LibJXL_FOUND "required for the QImage plugin for JPEG XL images")

View File

@ -14,12 +14,11 @@ image formats.
The following image formats have read-only support:
- Animated Windows cursors (ani)
- Camera RAW images (arw, cr2, cr3, dcs, dng, ...)
- Gimp (xcf)
- OpenEXR (exr)
- Photoshop documents (psd, psb, pdd, psdt)
- Radiance HDR (hdr)
- Sun Raster (ras)
- Camera RAW images (arw, cr2, cr3, dcs, dng, ...)
The following image formats have read and write support:
@ -27,7 +26,6 @@ The following image formats have read and write support:
- Encapsulated PostScript (eps)
- JPEG XL (jxl)
- Personal Computer Exchange (pcx)
- Quite OK Image format (qoi)
- SGI images (rgb, rgba, sgi, bw)
- Softimage PIC (pic)
- Targa (tga): supports more formats than Qt's version

View File

@ -1,6 +1,7 @@
#find_package(Qt5Test ${REQUIRED_QT_VERSION} NO_MODULE)
include(ECMMarkAsTest)
include(CMakeParseArguments)
add_definitions(-DPLUGIN_DIR="${CMAKE_CURRENT_BINARY_DIR}/../bin")
remove_definitions(-DQT_NO_CAST_FROM_ASCII)
@ -69,7 +70,6 @@ kimageformats_read_tests(
hdr
pcx
psd
qoi
ras
rgb
tga
@ -125,7 +125,6 @@ kimageformats_read_tests(FUZZ 1
kimageformats_write_tests(
pcx-lossless
pic-lossless
qoi-lossless
rgb-lossless
tga # fixme: the alpha images appear not to be written properly
)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 983 B

Binary file not shown.

After

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: 114 KiB

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 551 B

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 561 B

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 115 KiB

View File

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

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -12,4 +12,3 @@ Ignacio Castaño <castano@ludicon.com> -- DDS and PDS format reader.
Christoph Hormann <chris_hormann@gmx.de> -- HDR format read support.
Michael Ritzert <kde@ritzert.de> -- JPEG 2000 format read/write support
Troy Unrau <troy@kde.org> -- Sun RASter read support
Ernest Gupik <ernestgupik@wp.pl> -- QOI format read support

View File

@ -93,6 +93,7 @@ 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/)
@ -104,6 +105,9 @@ endif()
if (LibJXL_FOUND AND LibJXLThreads_FOUND)
kimageformats_add_plugin(kimg_jxl SOURCES jxl.cpp)
target_link_libraries(kimg_jxl PkgConfig::LibJXL PkgConfig::LibJXLThreads)
if (LibJXL_VERSION VERSION_GREATER_EQUAL "0.7.0")
target_compile_definitions(kimg_jxl PRIVATE KIMG_JXL_API_VERSION=70)
endif()
if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES jxl.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
@ -133,13 +137,6 @@ endif()
##################################
kimageformats_add_plugin(kimg_qoi SOURCES qoi.cpp scanlineconverter.cpp)
if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES qoi.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
##################################
kimageformats_add_plugin(kimg_ras SOURCES ras.cpp)
if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES ras.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)

View File

@ -570,5 +570,3 @@ QImageIOHandler *ANIPlugin::create(QIODevice *device, const QByteArray &format)
handler->setFormat(format);
return handler;
}
#include "moc_ani_p.cpp"

View File

@ -63,7 +63,7 @@ bool QAVIFHandler::canRead(QIODevice *device)
}
avifROData input;
input.data = reinterpret_cast<const uint8_t *>(header.constData());
input.data = (const uint8_t *)header.constData();
input.size = header.size();
if (avifPeekCompatibleFileType(&input)) {
@ -116,7 +116,7 @@ bool QAVIFHandler::ensureDecoder()
m_rawData = device()->readAll();
m_rawAvifData.data = reinterpret_cast<const uint8_t *>(m_rawData.constData());
m_rawAvifData.data = (const uint8_t *)m_rawData.constData();
m_rawAvifData.size = m_rawData.size();
if (avifPeekCompatibleFileType(&m_rawAvifData) == AVIF_FALSE) {
@ -330,10 +330,6 @@ bool QAVIFHandler::decode_one_frame()
avifRGBImage rgb;
avifRGBImageSetDefaults(&rgb, m_decoder->image);
#if AVIF_VERSION >= 1000000
rgb.maxThreads = m_decoder->maxThreads;
#endif
if (m_decoder->image->depth > 8) {
rgb.depth = 16;
rgb.format = AVIF_RGB_FORMAT_RGBA;
@ -428,7 +424,7 @@ bool QAVIFHandler::decode_one_frame()
}
if (m_decoder->image->transformFlags & AVIF_TRANSFORM_IMIR) {
#if AVIF_VERSION > 90100 && AVIF_VERSION < 1000000
#if AVIF_VERSION > 90100
switch (m_decoder->image->imir.mode) {
#else
switch (m_decoder->image->imir.axis) {
@ -718,9 +714,9 @@ bool QAVIFHandler::write(const QImage &image)
if (save_depth == 8) {
save_depth = 10;
if (tmpcolorimage.hasAlphaChannel()) {
tmpcolorimage.convertTo(QImage::Format_RGBA64);
tmpcolorimage = tmpcolorimage.convertToFormat(QImage::Format_RGBA64);
} else {
tmpcolorimage.convertTo(QImage::Format_RGBX64);
tmpcolorimage = tmpcolorimage.convertToFormat(QImage::Format_RGBX64);
}
}
@ -1043,11 +1039,6 @@ int QAVIFHandler::loopCount() const
return 0;
}
#if AVIF_VERSION >= 1000000
if (m_decoder->repetitionCount >= 0) {
return m_decoder->repetitionCount;
}
#endif
// Endless loop to work around https://github.com/AOMediaCodec/libavif/issues/347
return -1;
}
@ -1066,26 +1057,12 @@ QPointF QAVIFHandler::CompatibleChromacity(qreal chrX, qreal chrY)
QImageIOPlugin::Capabilities QAVIFPlugin::capabilities(QIODevice *device, const QByteArray &format) const
{
static const bool isAvifDecoderAvailable(avifCodecName(AVIF_CODEC_CHOICE_AUTO, AVIF_CODEC_FLAG_CAN_DECODE) != nullptr);
static const bool isAvifEncoderAvailable(avifCodecName(AVIF_CODEC_CHOICE_AUTO, AVIF_CODEC_FLAG_CAN_ENCODE) != nullptr);
if (format == "avif") {
Capabilities format_cap;
if (isAvifDecoderAvailable) {
format_cap |= CanRead;
}
if (isAvifEncoderAvailable) {
format_cap |= CanWrite;
}
return format_cap;
return Capabilities(CanRead | CanWrite);
}
if (format == "avifs") {
Capabilities format_cap;
if (isAvifDecoderAvailable) {
format_cap |= CanRead;
}
return format_cap;
return Capabilities(CanRead);
}
if (!format.isEmpty()) {
@ -1096,10 +1073,10 @@ QImageIOPlugin::Capabilities QAVIFPlugin::capabilities(QIODevice *device, const
}
Capabilities cap;
if (device->isReadable() && QAVIFHandler::canRead(device) && isAvifDecoderAvailable) {
if (device->isReadable() && QAVIFHandler::canRead(device)) {
cap |= CanRead;
}
if (device->isWritable() && isAvifEncoderAvailable) {
if (device->isWritable()) {
cap |= CanWrite;
}
return cap;
@ -1112,5 +1089,3 @@ QImageIOHandler *QAVIFPlugin::create(QIODevice *device, const QByteArray &format
handler->setFormat(format);
return handler;
}
#include "moc_avif_p.cpp"

View File

@ -365,5 +365,3 @@ QImageIOHandler *EPSPlugin::create(QIODevice *device, const QByteArray &format)
handler->setFormat(format);
return handler;
}
#include "moc_eps_p.cpp"

View File

@ -3,27 +3,10 @@
in the high dynamic range EXR format.
SPDX-FileCopyrightText: 2003 Brad Hards <bradh@frogmouth.net>
SPDX-FileCopyrightText: 2023 Mirco Miranda <mircomir@outlook.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
/* *** EXR_USE_LEGACY_CONVERSIONS ***
* If defined, the result image is an 8-bit RGB(A) converted
* without icc profiles. Otherwise, a 16-bit images is generated.
* NOTE: The use of legacy conversions are discouraged due to
* imprecise image result.
*/
//#define EXR_USE_LEGACY_CONVERSIONS // default commented -> you should define it in your cmake file
/* *** EXR_ALLOW_LINEAR_COLORSPACE ***
* If defined, the linear data is kept and it is the display program that
* must convert to the monitor profile. Otherwise the data is converted to sRGB
* to accommodate programs that do not support color profiles.
* NOTE: If EXR_USE_LEGACY_CONVERSIONS is active, this is ignored.
*/
//#define EXR_ALLOW_LINEAR_COLORSPACE // default: commented -> you should define it in your cmake file
#include "exr_p.h"
#include "util_p.h"
@ -47,24 +30,11 @@
#include <iostream>
#include <QColorSpace>
#include <QDataStream>
#include <QDebug>
#include <QFloat16>
#include <QImage>
#include <QImageIOPlugin>
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
#include <QTimeZone>
#endif
// Allow the code to works on all QT versions supported by KDE
// project (Qt 5.15 and Qt 6.x) to easy backports fixes.
#if (QT_VERSION_MAJOR >= 6) && !defined(EXR_USE_LEGACY_CONVERSIONS)
// If uncommented, the image is rendered in a float16 format, the result is very precise
#define EXR_USE_QT6_FLOAT_IMAGE // default uncommented
#endif
class K_IStream : public Imf::IStream
{
public:
@ -124,21 +94,77 @@ void K_IStream::clear()
// TODO
}
#ifdef EXR_USE_LEGACY_CONVERSIONS
// source: https://openexr.com/en/latest/ReadingAndWritingImageFiles.html
inline unsigned char gamma(float x)
/* this does a conversion from the ILM Half (equal to Nvidia Half)
* format into the normal 32 bit pixel format. Process is from the
* ILM code.
*/
QRgb RgbaToQrgba(struct Imf::Rgba &imagePixel)
{
x = std::pow(5.5555f * std::max(0.f, x), 0.4545f) * 84.66f;
return (unsigned char)qBound(0.f, x, 255.f);
float r;
float g;
float b;
float a;
// 1) Compensate for fogging by subtracting defog
// from the raw pixel values.
// Response: We work with defog of 0.0, so this is a no-op
// 2) Multiply the defogged pixel values by
// 2^(exposure + 2.47393).
// Response: We work with exposure of 0.0.
// (2^2.47393) is 5.55555
r = imagePixel.r * 5.55555;
g = imagePixel.g * 5.55555;
b = imagePixel.b * 5.55555;
a = imagePixel.a * 5.55555;
// 3) Values, which are now 1.0, are called "middle gray".
// If defog and exposure are both set to 0.0, then
// middle gray corresponds to a raw pixel value of 0.18.
// In step 6, middle gray values will be mapped to an
// intensity 3.5 f-stops below the display's maximum
// intensity.
// Response: no apparent content.
// 4) Apply a knee function. The knee function has two
// parameters, kneeLow and kneeHigh. Pixel values
// below 2^kneeLow are not changed by the knee
// function. Pixel values above kneeLow are lowered
// according to a logarithmic curve, such that the
// value 2^kneeHigh is mapped to 2^3.5 (in step 6,
// this value will be mapped to the display's
// maximum intensity).
// Response: kneeLow = 0.0 (2^0.0 => 1); kneeHigh = 5.0 (2^5 =>32)
if (r > 1.0) {
r = 1.0 + std::log((r - 1.0) * 0.184874 + 1) / 0.184874;
}
if (g > 1.0) {
g = 1.0 + std::log((g - 1.0) * 0.184874 + 1) / 0.184874;
}
if (b > 1.0) {
b = 1.0 + std::log((b - 1.0) * 0.184874 + 1) / 0.184874;
}
if (a > 1.0) {
a = 1.0 + std::log((a - 1.0) * 0.184874 + 1) / 0.184874;
}
//
// 5) Gamma-correct the pixel values, assuming that the
// screen's gamma is 0.4545 (or 1/2.2).
r = std::pow(r, 0.4545);
g = std::pow(g, 0.4545);
b = std::pow(b, 0.4545);
a = std::pow(a, 0.4545);
// 6) Scale the values such that pixels middle gray
// pixels are mapped to 84.66 (or 3.5 f-stops below
// the display's maximum intensity).
//
// 7) Clamp the values to [0, 255].
return qRgba((unsigned char)(Imath::clamp(r * 84.66f, 0.f, 255.f)),
(unsigned char)(Imath::clamp(g * 84.66f, 0.f, 255.f)),
(unsigned char)(Imath::clamp(b * 84.66f, 0.f, 255.f)),
(unsigned char)(Imath::clamp(a * 84.66f, 0.f, 255.f)));
}
inline QRgb RgbaToQrgba(struct Imf::Rgba &imagePixel)
{
return qRgba(gamma(float(imagePixel.r)),
gamma(float(imagePixel.g)),
gamma(float(imagePixel.b)),
(unsigned char)(qBound(0.f, imagePixel.a * 255.f, 255.f) + 0.5f));
}
#endif
EXRHandler::EXRHandler()
{
@ -162,99 +188,30 @@ bool EXRHandler::read(QImage *outImage)
K_IStream istr(device(), QByteArray());
Imf::RgbaInputFile file(istr);
Imath::Box2i dw = file.dataWindow();
bool isRgba = file.channels() & Imf::RgbaChannels::WRITE_A;
width = dw.max.x - dw.min.x + 1;
height = dw.max.y - dw.min.y + 1;
#if defined(EXR_USE_LEGACY_CONVERSIONS)
QImage image = imageAlloc(width, height, isRgba ? QImage::Format_ARGB32 : QImage::Format_RGB32);
#elif defined(EXR_USE_QT6_FLOAT_IMAGE)
QImage image = imageAlloc(width, height, isRgba ? QImage::Format_RGBA16FPx4 : QImage::Format_RGBX16FPx4);
#else
QImage image = imageAlloc(width, height, isRgba ? QImage::Format_RGBA64 : QImage::Format_RGBX64);
#endif
QImage image = imageAlloc(width, height, QImage::Format_RGB32);
if (image.isNull()) {
qWarning() << "Failed to allocate image, invalid size?" << QSize(width, height);
return false;
}
// set some useful metadata
auto &&h = file.header();
if (auto comments = h.findTypedAttribute<Imf::StringAttribute>("comments")) {
image.setText(QStringLiteral("Comment"), QString::fromStdString(comments->value()));
}
if (auto owner = h.findTypedAttribute<Imf::StringAttribute>("owner")) {
image.setText(QStringLiteral("Owner"), QString::fromStdString(owner->value()));
}
if (auto capDate = h.findTypedAttribute<Imf::StringAttribute>("capDate")) {
float off = 0;
if (auto utcOffset = h.findTypedAttribute<Imf::FloatAttribute>("utcOffset")) {
off = utcOffset->value();
}
auto dateTime = QDateTime::fromString(QString::fromStdString(capDate->value()), QStringLiteral("yyyy:MM:dd HH:mm:ss"));
if (dateTime.isValid()) {
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
dateTime.setTimeZone(QTimeZone::fromSecondsAheadOfUtc(off));
#else
dateTime.setOffsetFromUtc(off);
#endif
image.setText(QStringLiteral("Date"), dateTime.toString(Qt::ISODate));
}
}
if (auto xDensity = h.findTypedAttribute<Imf::FloatAttribute>("xDensity")) {
float par = 1;
if (auto pixelAspectRatio = h.findTypedAttribute<Imf::FloatAttribute>("pixelAspectRatio")) {
par = pixelAspectRatio->value();
}
image.setDotsPerMeterX(qRound(xDensity->value() * 100.0 / 2.54));
image.setDotsPerMeterY(qRound(xDensity->value() * par * 100.0 / 2.54));
}
Imf::Array2D<Imf::Rgba> pixels;
pixels.resizeErase(height, width);
Imf::Array<Imf::Rgba> pixels;
pixels.resizeErase(width);
file.setFrameBuffer(&pixels[0][0] - dw.min.x - dw.min.y * width, 1, width);
file.readPixels(dw.min.y, dw.max.y);
// somehow copy pixels into image
for (int y = 0; y < height; ++y) {
auto my = dw.min.y + y;
if (my <= dw.max.y) { // paranoia check
file.setFrameBuffer(&pixels[0] - dw.min.x - qint64(my) * width, 1, width);
file.readPixels(my, my);
#if defined(EXR_USE_LEGACY_CONVERSIONS)
auto scanLine = reinterpret_cast<QRgb *>(image.scanLine(y));
for (int x = 0; x < width; ++x) {
*(scanLine + x) = RgbaToQrgba(pixels[x]);
}
#elif defined(EXR_USE_QT6_FLOAT_IMAGE)
auto scanLine = reinterpret_cast<qfloat16 *>(image.scanLine(y));
for (int x = 0; x < width; ++x) {
auto xcs = x * 4;
*(scanLine + xcs) = qfloat16(qBound(0.f, float(pixels[x].r), 1.f));
*(scanLine + xcs + 1) = qfloat16(qBound(0.f, float(pixels[x].g), 1.f));
*(scanLine + xcs + 2) = qfloat16(qBound(0.f, float(pixels[x].b), 1.f));
*(scanLine + xcs + 3) = qfloat16(isRgba ? qBound(0.f, float(pixels[x].a), 1.f) : 1.f);
}
#else
auto scanLine = reinterpret_cast<QRgba64 *>(image.scanLine(y));
for (int x = 0; x < width; ++x) {
*(scanLine + x) = QRgba64::fromRgba64(quint16(qBound(0.f, float(pixels[x].r) * 65535.f + 0.5f, 65535.f)),
quint16(qBound(0.f, float(pixels[x].g) * 65535.f + 0.5f, 65535.f)),
quint16(qBound(0.f, float(pixels[x].b) * 65535.f + 0.5f, 65535.f)),
isRgba ? quint16(qBound(0.f, float(pixels[x].a) * 65535.f + 0.5f, 65535.f)) : quint16(65535));
}
#endif
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
// copy pixels(x,y) into image(x,y)
image.setPixel(x, y, RgbaToQrgba(pixels[y][x]));
}
}
// final color operations
#ifndef EXR_USE_LEGACY_CONVERSIONS
image.setColorSpace(QColorSpace(QColorSpace::SRgbLinear));
#ifndef EXR_ALLOW_LINEAR_COLORSPACE
image.convertToColorSpace(QColorSpace(QColorSpace::SRgb));
#endif // !EXR_ALLOW_LINEAR_COLORSPACE
#endif // !EXR_USE_LEGACY_CONVERSIONS
*outImage = image;
return true;
@ -302,5 +259,3 @@ QImageIOHandler *EXRPlugin::create(QIODevice *device, const QByteArray &format)
handler->setFormat(format);
return handler;
}
#include "moc_exr_p.cpp"

View File

@ -1,35 +0,0 @@
/*
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

@ -46,6 +46,33 @@ typedef enum {
// From GIMP "libgimp/gimpenums.h" v2.4
//! Effect to apply when layers are merged together.
typedef enum {
NORMAL_MODE,
DISSOLVE_MODE,
BEHIND_MODE,
MULTIPLY_MODE,
SCREEN_MODE,
OVERLAY_MODE,
DIFFERENCE_MODE,
ADDITION_MODE,
SUBTRACT_MODE,
DARKEN_ONLY_MODE,
LIGHTEN_ONLY_MODE,
HUE_MODE,
SATURATION_MODE,
COLOR_MODE,
VALUE_MODE,
DIVIDE_MODE,
DODGE_MODE,
BURN_MODE,
HARDLIGHT_MODE,
SOFTLIGHT_MODE,
GRAIN_EXTRACT_MODE,
GRAIN_MERGE_MODE
} LayerModeEffects;
// From GIMP "paint_funcs.c" v1.2
/*!

View File

@ -9,7 +9,6 @@
#include "hdr_p.h"
#include "util_p.h"
#include <QColorSpace>
#include <QDataStream>
#include <QImage>
#include <QLoggingCategory>
@ -29,8 +28,11 @@ namespace // Private.
static inline uchar ClipToByte(float value)
{
// we know value is positive.
return uchar(std::min(value + 0.5f, 255.0f));
if (value > 255.0f) {
return 255;
}
// else if (value < 0.0f) return 0; // we know value is positive.
return uchar(value);
}
// read an old style line from the hdr image file
@ -40,7 +42,6 @@ static bool Read_Old_Line(uchar *image, int width, QDataStream &s)
int rshift = 0;
int i;
uchar *start = image;
while (width > 0) {
s >> image[0];
s >> image[1];
@ -52,14 +53,7 @@ static bool Read_Old_Line(uchar *image, int width, QDataStream &s)
}
if ((image[0] == 1) && (image[1] == 1) && (image[2] == 1)) {
// NOTE: we don't have an image sample that cover this code
if (rshift > 31) {
return false;
}
for (i = image[3] << rshift; i > 0 && width > 0; i--) {
if (image == start) {
return false; // you cannot be here at the first run
}
for (i = image[3] << rshift; i > 0; i--) {
// memcpy(image, image-4, 4);
(uint &)image[0] = (uint &)image[0 - 4];
image += 4;
@ -80,7 +74,7 @@ static void RGBE_To_QRgbLine(uchar *image, QRgb *scanline, int width)
for (int j = 0; j < width; j++) {
// v = ldexp(1.0, int(image[3]) - 128);
float v;
int e = qBound(-31, int(image[3]) - 128, 31);
int e = int(image[3]) - 128;
if (e > 0) {
v = float(1 << e);
} else {
@ -154,7 +148,7 @@ static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &i
}
// read each component
for (int i = 0, len = int(lineArray.size()); i < 4; i++) {
for (int i = 0; i < 4; i++) {
for (int j = 0; j < width;) {
s >> code;
if (s.atEnd()) {
@ -166,20 +160,14 @@ static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &i
code &= 127;
s >> val;
while (code != 0) {
auto idx = i + j * 4;
if (idx < len) {
image[idx] = val;
}
image[i + j * 4] = val;
j++;
code--;
}
} else {
// non-run
while (code != 0) {
auto idx = i + j * 4;
if (idx < len) {
s >> image[idx];
}
s >> image[i + j * 4];
j++;
code--;
}
@ -254,9 +242,6 @@ bool HDRHandler::read(QImage *outImage)
// qDebug() << "Error loading HDR file.";
return false;
}
// The images read by Gimp and Photoshop (including those of the tests) are interpreted with linear color space.
// By setting the linear color space, programs that support profiles display HDR files as in GIMP and Photoshop.
img.setColorSpace(QColorSpace(QColorSpace::SRgbLinear));
*outImage = img;
return true;
@ -311,5 +296,3 @@ QImageIOHandler *HDRPlugin::create(QIODevice *device, const QByteArray &format)
handler->setFormat(format);
return handler;
}
#include "moc_hdr_p.cpp"

File diff suppressed because it is too large Load Diff

View File

@ -150,7 +150,9 @@ bool QJpegXLHandler::ensureDecoder()
return false;
}
#ifdef KIMG_JXL_API_VERSION
JxlDecoderCloseInput(m_decoder);
#endif
JxlDecoderStatus status = JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING | JXL_DEC_FRAME);
if (status == JXL_DEC_ERROR) {
@ -267,31 +269,18 @@ bool QJpegXLHandler::countALLFrames()
}
}
status = JxlDecoderGetColorAsEncodedProfile(m_decoder,
#if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0, 9, 0)
&m_input_pixel_format,
#endif
JXL_COLOR_PROFILE_TARGET_DATA,
&color_encoding);
status = JxlDecoderGetColorAsEncodedProfile(m_decoder, &m_input_pixel_format, JXL_COLOR_PROFILE_TARGET_DATA, &color_encoding);
if (status == JXL_DEC_SUCCESS && color_encoding.color_space == JXL_COLOR_SPACE_RGB && color_encoding.white_point == JXL_WHITE_POINT_D65
&& color_encoding.primaries == JXL_PRIMARIES_SRGB && color_encoding.transfer_function == JXL_TRANSFER_FUNCTION_SRGB) {
m_colorspace = QColorSpace(QColorSpace::SRgb);
} else {
size_t icc_size = 0;
if (JxlDecoderGetICCProfileSize(m_decoder,
#if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0, 9, 0)
&m_input_pixel_format,
#endif
JXL_COLOR_PROFILE_TARGET_DATA,
&icc_size)
== JXL_DEC_SUCCESS) {
if (JxlDecoderGetICCProfileSize(m_decoder, &m_input_pixel_format, JXL_COLOR_PROFILE_TARGET_DATA, &icc_size) == JXL_DEC_SUCCESS) {
if (icc_size > 0) {
QByteArray icc_data(icc_size, 0);
if (JxlDecoderGetColorAsICCProfile(m_decoder,
#if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0, 9, 0)
&m_input_pixel_format,
#endif
JXL_COLOR_PROFILE_TARGET_DATA,
reinterpret_cast<uint8_t *>(icc_data.data()),
icc_data.size())
@ -545,7 +534,9 @@ bool QJpegXLHandler::write(const QImage &image)
if (save_depth == 16 && (image.hasAlphaChannel() || output_info.uses_original_profile)) {
output_info.have_container = JXL_TRUE;
JxlEncoderUseContainer(encoder, JXL_TRUE);
#ifdef KIMG_JXL_API_VERSION
JxlEncoderSetCodestreamLevel(encoder, 10);
#endif
}
void *runner = nullptr;
@ -659,11 +650,19 @@ bool QJpegXLHandler::write(const QImage &image)
}
}
#ifdef KIMG_JXL_API_VERSION
JxlEncoderFrameSettings *encoder_options = JxlEncoderFrameSettingsCreate(encoder, nullptr);
JxlEncoderSetFrameDistance(encoder_options, (100.0f - m_quality) / 10.0f);
JxlEncoderSetFrameLossless(encoder_options, (m_quality == 100) ? JXL_TRUE : JXL_FALSE);
#else
JxlEncoderOptions *encoder_options = JxlEncoderOptionsCreate(encoder, nullptr);
JxlEncoderOptionsSetDistance(encoder_options, (100.0f - m_quality) / 10.0f);
JxlEncoderOptionsSetLossless(encoder_options, (m_quality == 100) ? JXL_TRUE : JXL_FALSE);
#endif
if (image.hasAlphaChannel() || ((save_depth == 8) && (xsize % 4 == 0))) {
status = JxlEncoderAddImageFrame(encoder_options, &pixel_format, static_cast<const void *>(tmpimage.constBits()), buffer_size);
@ -958,7 +957,9 @@ bool QJpegXLHandler::rewind()
return false;
}
#ifdef KIMG_JXL_API_VERSION
JxlDecoderCloseInput(m_decoder);
#endif
if (m_basicinfo.uses_original_profile) {
if (JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_FULL_IMAGE) != JXL_DEC_SUCCESS) {
@ -1020,5 +1021,3 @@ QImageIOHandler *QJpegXLPlugin::create(QIODevice *device, const QByteArray &form
handler->setFormat(format);
return handler;
}
#include "moc_jxl_p.cpp"

View File

@ -95,5 +95,3 @@ QImageIOHandler *KraPlugin::create(QIODevice *device, const QByteArray &format)
handler->setFormat(format);
return handler;
}
#include "moc_kra.cpp"

View File

@ -94,5 +94,3 @@ QImageIOHandler *OraPlugin::create(QIODevice *device, const QByteArray &format)
handler->setFormat(format);
return handler;
}
#include "moc_ora.cpp"

View File

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

View File

@ -452,5 +452,3 @@ QImageIOHandler *SoftimagePICPlugin::create(QIODevice *device, const QByteArray
handler->setFormat(format);
return handler;
}
#include "moc_pic_p.cpp"

View File

@ -3,14 +3,14 @@
SPDX-FileCopyrightText: 2003 Ignacio Castaño <castano@ludicon.com>
SPDX-FileCopyrightText: 2015 Alex Merry <alex.merry@kde.org>
SPDX-FileCopyrightText: 2022-2023 Mirco Miranda <mircomir@outlook.com>
SPDX-FileCopyrightText: 2022 Mirco Miranda <mircomir@outlook.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
/*
* The early version of this code was based on Thacher Ulrich PSD loading code
* released into the public domain. See: http://tulrich.com/geekstuff/
* This code is based on Thacher Ulrich PSD loading code released
* into the public domain. See: http://tulrich.com/geekstuff/
*/
/*
@ -21,6 +21,7 @@
/*
* Limitations of the current code:
* - 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
* color spaces other than RGB (and Grayscale). Where possible, a conversion
* to RGB is done:
@ -32,7 +33,6 @@
* color management engine (e.g. LittleCMS).
*/
#include "fastmath_p.h"
#include "psd_p.h"
#include "util_p.h"
@ -42,7 +42,6 @@
#include <QColorSpace>
#include <cmath>
#include <cstring>
typedef quint32 uint;
typedef quint16 ushort;
@ -52,7 +51,7 @@ typedef quint8 uchar;
* This should not be a problem because the Qt's QColorSpace supports the linear
* sRgb colorspace.
*
* Using linear conversion, the loading speed is slightly improved. Anyway, if you are using
* Using linear conversion, the loading speed is improved by 4x. Anyway, if you are using
* 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
@ -647,9 +646,6 @@ static bool IsValid(const PSDHeader &header)
// Check that the header is supported by this plugin.
static bool IsSupported(const PSDHeader &header)
{
if (!IsValid(header)) {
return false;
}
if (header.version != 1 && header.version != 2) {
return false;
}
@ -664,15 +660,10 @@ static bool IsSupported(const PSDHeader &header)
header.color_mode != CM_INDEXED &&
header.color_mode != CM_DUOTONE &&
header.color_mode != CM_CMYK &&
header.color_mode != CM_MULTICHANNEL &&
header.color_mode != CM_LABCOLOR &&
header.color_mode != CM_BITMAP) {
return false;
}
if (header.color_mode == CM_MULTICHANNEL &&
header.channel_count < 3) {
return false;
}
return true;
}
@ -734,18 +725,17 @@ static QImage::Format imageFormat(const PSDHeader &header, bool alpha)
switch(header.color_mode) {
case CM_RGB:
if (header.depth == 16 || header.depth == 32)
format = header.channel_count < 4 || !alpha ? QImage::Format_RGBX64 : QImage::Format_RGBA64_Premultiplied;
format = header.channel_count < 4 || !alpha ? QImage::Format_RGBX64 : QImage::Format_RGBA64;
else
format = header.channel_count < 4 || !alpha ? QImage::Format_RGB888 : QImage::Format_RGBA8888_Premultiplied;
format = header.channel_count < 4 || !alpha ? QImage::Format_RGB888 : QImage::Format_RGBA8888;
break;
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 8-bits and 16-bits only
if (header.depth == 16)
format = header.channel_count < 5 || !alpha ? QImage::Format_RGBX64 : QImage::Format_RGBA64;
else if (header.depth == 8)
format = header.channel_count < 5 || !alpha ? QImage::Format_RGB888 : QImage::Format_RGBA8888;
break;
case CM_LABCOLOR: // Photoshop supports LAB 8-bits and 16-bits only
case CM_LABCOLOR: // Photoshop supports LAB 8-bits and 16-bits only
if (header.depth == 16)
format = header.channel_count < 4 || !alpha ? QImage::Format_RGBX64 : QImage::Format_RGBA64;
else if (header.depth == 8)
@ -809,80 +799,25 @@ inline quint32 xchg(quint32 v) {
#endif
}
inline float xchg(float v)
{
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
# ifdef Q_CC_MSVC
float *pf = &v;
quint32 f = xchg(*reinterpret_cast<quint32*>(pf));
quint32 *pi = &f;
return *reinterpret_cast<float*>(pi);
# else
quint32 t;
std::memcpy(&t, &v, sizeof(quint32));
t = xchg(t);
std::memcpy(&v, &t, sizeof(quint32));
return v;
# endif
#else
return v; // never tested
#endif
}
template<class T>
inline void planarToChunchy(uchar *target, const char *source, qint32 width, qint32 c, qint32 cn)
{
auto s = reinterpret_cast<const T*>(source);
auto t = reinterpret_cast<T*>(target);
for (qint32 x = 0; x < width; ++x) {
t[x * cn + c] = xchg(s[x]);
t[x*cn+c] = xchg(s[x]);
}
}
template<class T>
inline void planarToChunchyFloatToUInt16(uchar *target, const char *source, qint32 width, qint32 c, qint32 cn)
template<class T, T min = 0, T max = 1>
inline void planarToChunchyFloat(uchar *target, const char *source, qint32 width, qint32 c, qint32 cn)
{
auto s = reinterpret_cast<const T*>(source);
auto t = reinterpret_cast<quint16*>(target);
for (qint32 x = 0; x < width; ++x) {
t[x * cn + c] = quint16(std::min(xchg(s[x]) * 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;
}
}
auto tmp = xchg(s[x]);
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())));
}
}
@ -895,37 +830,24 @@ 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>
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 t = reinterpret_cast<T*>(target);
auto max = double(std::numeric_limits<T>::max());
auto invmax = 1.0 / max; // speed improvements by ~10%
if (sourceChannels < 3) {
qDebug() << "cmykToRgb: image is not a valid CMY/CMYK!";
if (sourceChannels < 4) {
qDebug() << "cmykToRgb: image is not a valid CMYK!";
return;
}
for (qint32 w = 0; w < width; ++w) {
auto ps = s + sourceChannels * w;
auto C = 1 - *(ps + 0) * invmax;
auto M = 1 - *(ps + 1) * invmax;
auto Y = 1 - *(ps + 2) * invmax;
auto K = sourceChannels > 3 ? 1 - *(ps + 3) * invmax : 0.0;
auto C = 1 - *(ps + 0) / max;
auto M = 1 - *(ps + 1) / max;
auto Y = 1 - *(ps + 2) / max;
auto K = 1 - *(ps + 3) / max;
auto pt = t + targetChannels * w;
*(pt + 0) = T(std::min(max - (C * (1 - K) + K) * max + 0.5, max));
@ -950,9 +872,8 @@ inline double gammaCorrection(double linear)
#ifdef PSD_FAST_LAB_CONVERSION
return linear;
#else
// Replacing fastPow with std::pow the conversion time is 2/3 times longer: using fastPow
// 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);
// NOTE: pow() slow down the performance by a 4 factor :(
return (linear > 0.0031308 ? 1.055 * std::pow(linear, 1.0 / 2.4) - 0.055 : 12.92 * linear);
#endif
}
@ -962,7 +883,6 @@ inline void labToRgb(uchar *target, qint32 targetChannels, const char *source, q
auto s = reinterpret_cast<const T*>(source);
auto t = reinterpret_cast<T*>(target);
auto max = double(std::numeric_limits<T>::max());
auto invmax = 1.0 / max;
if (sourceChannels < 3) {
qDebug() << "labToRgb: image is not a valid LAB!";
@ -971,14 +891,14 @@ inline void labToRgb(uchar *target, qint32 targetChannels, const char *source, q
for (qint32 w = 0; w < width; ++w) {
auto ps = s + sourceChannels * w;
auto L = (*(ps + 0) * invmax) * 100.0;
auto A = (*(ps + 1) * invmax) * 255.0 - 128.0;
auto B = (*(ps + 2) * invmax) * 255.0 - 128.0;
auto L = (*(ps + 0) / max) * 100.0;
auto A = (*(ps + 1) / max) * 255.0 - 128.0;
auto B = (*(ps + 2) / max) * 255.0 - 128.0;
// converting LAB to XYZ (D65 illuminant)
auto Y = (L + 16.0) * (1.0 / 116.0);
auto X = A * (1.0 / 500.0) + Y;
auto Z = Y - B * (1.0 / 200.0);
auto Y = (L + 16.0) / 116.0;
auto X = A / 500.0 + Y;
auto Z = Y - B / 200.0;
// NOTE: use the constants of the illuminant of the target RGB color space
X = finv(X) * 0.9504; // D50: * 0.9642
@ -1128,15 +1048,7 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
QByteArray rawStride;
rawStride.resize(raw_count);
// 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) {
if (header.color_mode == CM_CMYK || header.color_mode == CM_LABCOLOR || header.color_mode == CM_MULTICHANNEL) {
// In order to make a colorspace transformation, we need all channels of a scanline
QByteArray psdScanline;
psdScanline.resize(qsizetype(header.width * std::min(header.depth, quint16(16)) * header.channel_count + 7) / 8);
@ -1156,56 +1068,31 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
auto scanLine = reinterpret_cast<unsigned char*>(psdScanline.data());
if (header.depth == 8) {
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);
} else if (header.depth == 32) {
planarToChunchyFloatToUInt16<float>(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);
else if (header.depth == 32) { // Not currently used
planarToChunchyFloat<quint32>(scanLine, rawStride.data(), header.width, c, header.channel_count);
}
}
// Conversion to RGB
if (header.color_mode == CM_CMYK || header.color_mode == CM_MULTICHANNEL) {
if (header.color_mode == CM_CMYK) {
if (header.depth == 8)
cmykToRgb<quint8>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha);
else if (header.depth == 16)
else
cmykToRgb<quint16>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha);
}
if (header.color_mode == CM_LABCOLOR) {
if (header.depth == 8)
labToRgb<quint8>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha);
else if (header.depth == 16)
else
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
for (qint32 c = 0; c < channel_num; ++c) {
for (qint32 y = 0, h = header.height; y < h; ++y) {
@ -1216,14 +1103,17 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
}
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()));
} 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);
} 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);
} else if (header.depth == 32) { // 32-bits float images: Grayscale, RGB/RGBA (coverted to equivalent integer 16-bits)
planarToChunchyFloatToUInt16<float>(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)
planarToChunchyFloat<quint32>(scanLine, rawStride.data(), header.width, c, imgChannels);
}
}
}
@ -1365,12 +1255,9 @@ bool PSDHandler::canRead(QIODevice *device)
if (header.color_mode == CM_CMYK || header.color_mode == CM_LABCOLOR || header.color_mode == CM_MULTICHANNEL) {
return false;
}
if (header.color_mode == CM_RGB && header.channel_count > 3) {
return false; // supposing extra channel as alpha
}
}
return IsSupported(header);
return IsValid(header);
}
QImageIOPlugin::Capabilities PSDPlugin::capabilities(QIODevice *device, const QByteArray &format) const
@ -1399,5 +1286,3 @@ QImageIOHandler *PSDPlugin::create(QIODevice *device, const QByteArray &format)
handler->setFormat(format);
return handler;
}
#include "moc_psd_p.cpp"

View File

@ -1,475 +0,0 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2023 Ernest Gupik <ernestgupik@wp.pl>
SPDX-FileCopyrightText: 2023 Mirco Miranda <mircomir@outlook.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "qoi_p.h"
#include "scanlineconverter_p.h"
#include "util_p.h"
#include <QColorSpace>
#include <QFile>
#include <QIODevice>
#include <QImage>
namespace // Private
{
#define QOI_OP_INDEX 0x00 /* 00xxxxxx */
#define QOI_OP_DIFF 0x40 /* 01xxxxxx */
#define QOI_OP_LUMA 0x80 /* 10xxxxxx */
#define QOI_OP_RUN 0xc0 /* 11xxxxxx */
#define QOI_OP_RGB 0xfe /* 11111110 */
#define QOI_OP_RGBA 0xff /* 11111111 */
#define QOI_MASK_2 0xc0 /* 11000000 */
#define QOI_MAGIC (((unsigned int)'q') << 24 | ((unsigned int)'o') << 16 | ((unsigned int)'i') << 8 | ((unsigned int)'f'))
#define QOI_HEADER_SIZE 14
#define QOI_END_STREAM_PAD 8
struct QoiHeader {
quint32 MagicNumber;
quint32 Width;
quint32 Height;
quint8 Channels;
quint8 Colorspace;
};
struct Px {
bool operator==(const Px &other) const
{
return r == other.r && g == other.g && b == other.b && a == other.a;
}
quint8 r;
quint8 g;
quint8 b;
quint8 a;
};
static QDataStream &operator>>(QDataStream &s, QoiHeader &head)
{
s >> head.MagicNumber;
s >> head.Width;
s >> head.Height;
s >> head.Channels;
s >> head.Colorspace;
return s;
}
static QDataStream &operator<<(QDataStream &s, const QoiHeader &head)
{
s << head.MagicNumber;
s << head.Width;
s << head.Height;
s << head.Channels;
s << head.Colorspace;
return s;
}
static bool IsSupported(const QoiHeader &head)
{
// Check magic number
if (head.MagicNumber != QOI_MAGIC) {
return false;
}
// Check if the header is a valid QOI header
if (head.Width == 0 || head.Height == 0 || head.Channels < 3 || head.Colorspace > 1) {
return false;
}
// Set a reasonable upper limit
if (head.Width > 300000 || head.Height > 300000) {
return false;
}
return true;
}
static int QoiHash(const Px &px)
{
return px.r * 3 + px.g * 5 + px.b * 7 + px.a * 11;
}
static QImage::Format imageFormat(const QoiHeader &head)
{
if (IsSupported(head)) {
return (head.Channels == 3 ? QImage::Format_RGB32 : QImage::Format_ARGB32);
}
return QImage::Format_Invalid;
}
static bool LoadQOI(QIODevice *device, const QoiHeader &qoi, QImage &img)
{
Px index[64] = {Px{0, 0, 0, 0}};
Px px = Px{0, 0, 0, 255};
// The px_len should be enough to read a complete "compressed" row: an uncompressible row can become
// larger than the row itself. It should never be more than 1/3 (RGB) or 1/4 (RGBA) the length of the
// row itself (see test bnm_rgb*.qoi) so I set the extra data to 1/2.
// The minimum value is to ensure that enough bytes are read when the image is very small (e.g. 1x1px):
// it can be set as large as you like.
quint64 px_len = std::max(quint64(1024), quint64(qoi.Width) * qoi.Channels * 3 / 2);
if (px_len > kMaxQVectorSize) {
return false;
}
// Allocate image
img = imageAlloc(qoi.Width, qoi.Height, imageFormat(qoi));
if (img.isNull()) {
return false;
}
// Set the image colorspace based on the qoi.Colorspace value
// As per specification: 0 = sRGB with linear alpha, 1 = all channels linear
if (qoi.Colorspace) {
img.setColorSpace(QColorSpace(QColorSpace::SRgbLinear));
} else {
img.setColorSpace(QColorSpace(QColorSpace::SRgb));
}
// Handle the byte stream
QByteArray ba;
for (quint32 y = 0, run = 0; y < qoi.Height; ++y) {
if (quint64(ba.size()) < px_len) {
ba.append(device->read(px_len));
}
if (ba.size() < QOI_END_STREAM_PAD) {
return false;
}
quint64 chunks_len = ba.size() - QOI_END_STREAM_PAD;
quint64 p = 0;
QRgb *scanline = reinterpret_cast<QRgb *>(img.scanLine(y));
const quint8 *input = reinterpret_cast<const quint8 *>(ba.constData());
for (quint32 x = 0; x < qoi.Width; ++x) {
if (run > 0) {
run--;
} else if (p < chunks_len) {
quint32 b1 = input[p++];
if (b1 == QOI_OP_RGB) {
px.r = input[p++];
px.g = input[p++];
px.b = input[p++];
} else if (b1 == QOI_OP_RGBA) {
px.r = input[p++];
px.g = input[p++];
px.b = input[p++];
px.a = input[p++];
} else if ((b1 & QOI_MASK_2) == QOI_OP_INDEX) {
px = index[b1];
} else if ((b1 & QOI_MASK_2) == QOI_OP_DIFF) {
px.r += ((b1 >> 4) & 0x03) - 2;
px.g += ((b1 >> 2) & 0x03) - 2;
px.b += (b1 & 0x03) - 2;
} else if ((b1 & QOI_MASK_2) == QOI_OP_LUMA) {
quint32 b2 = input[p++];
quint32 vg = (b1 & 0x3f) - 32;
px.r += vg - 8 + ((b2 >> 4) & 0x0f);
px.g += vg;
px.b += vg - 8 + (b2 & 0x0f);
} else if ((b1 & QOI_MASK_2) == QOI_OP_RUN) {
run = (b1 & 0x3f);
}
index[QoiHash(px) & 0x3F] = px;
}
// Set the values for the pixel at (x, y)
scanline[x] = qRgba(px.r, px.g, px.b, px.a);
}
if (p) {
ba.remove(0, p);
}
}
// From specs the byte stream's end is marked with 7 0x00 bytes followed by a single 0x01 byte.
// NOTE: Instead of using "ba == QByteArray::fromRawData("\x00\x00\x00\x00\x00\x00\x00\x01", 8)"
// we preferred a generic check that allows data to exist after the end of the file.
return (ba.startsWith(QByteArray::fromRawData("\x00\x00\x00\x00\x00\x00\x00\x01", 8)));
}
static bool SaveQOI(QIODevice *device, const QoiHeader &qoi, const QImage &img)
{
Px index[64] = {Px{0, 0, 0, 0}};
Px px = Px{0, 0, 0, 255};
Px px_prev = px;
auto run = 0;
auto channels = qoi.Channels;
QByteArray ba;
ba.reserve(img.width() * channels * 3 / 2);
ScanLineConverter converter(channels == 3 ? QImage::Format_RGB888 : QImage::Format_RGBA8888);
converter.setTargetColorSpace(QColorSpace(qoi.Colorspace == 1 ? QColorSpace::SRgbLinear : QColorSpace::SRgb));
for (auto h = img.height(), y = 0; y < h; ++y) {
auto pixels = converter.convertedScanLine(img, y);
if (pixels == nullptr) {
return false;
}
for (auto w = img.width() * channels, px_pos = 0; px_pos < w; px_pos += channels) {
px.r = pixels[px_pos + 0];
px.g = pixels[px_pos + 1];
px.b = pixels[px_pos + 2];
if (channels == 4) {
px.a = pixels[px_pos + 3];
}
if (px == px_prev) {
run++;
if (run == 62 || (px_pos == w - channels && y == h - 1)) {
ba.append(QOI_OP_RUN | (run - 1));
run = 0;
}
} else {
int index_pos;
if (run > 0) {
ba.append(QOI_OP_RUN | (run - 1));
run = 0;
}
index_pos = QoiHash(px) & 0x3F;
if (index[index_pos] == px) {
ba.append(QOI_OP_INDEX | index_pos);
} else {
index[index_pos] = px;
if (px.a == px_prev.a) {
signed char vr = px.r - px_prev.r;
signed char vg = px.g - px_prev.g;
signed char vb = px.b - px_prev.b;
signed char vg_r = vr - vg;
signed char vg_b = vb - vg;
if (vr > -3 && vr < 2 && vg > -3 && vg < 2 && vb > -3 && vb < 2) {
ba.append(QOI_OP_DIFF | (vr + 2) << 4 | (vg + 2) << 2 | (vb + 2));
} else if (vg_r > -9 && vg_r < 8 && vg > -33 && vg < 32 && vg_b > -9 && vg_b < 8) {
ba.append(QOI_OP_LUMA | (vg + 32));
ba.append((vg_r + 8) << 4 | (vg_b + 8));
} else {
ba.append(char(QOI_OP_RGB));
ba.append(px.r);
ba.append(px.g);
ba.append(px.b);
}
} else {
ba.append(char(QOI_OP_RGBA));
ba.append(px.r);
ba.append(px.g);
ba.append(px.b);
ba.append(px.a);
}
}
}
px_prev = px;
}
auto written = device->write(ba);
if (written < 0) {
return false;
}
if (written) {
ba.remove(0, written);
}
}
// QOI end of stream
ba.append(QByteArray::fromRawData("\x00\x00\x00\x00\x00\x00\x00\x01", 8));
// write remaining data
for (qint64 w = 0, write = 0, size = ba.size(); write < size; write += w) {
w = device->write(ba.constData() + write, size - write);
if (w < 0) {
return false;
}
}
return true;
}
} // namespace
QOIHandler::QOIHandler()
{
}
bool QOIHandler::canRead() const
{
if (canRead(device())) {
setFormat("qoi");
return true;
}
return false;
}
bool QOIHandler::canRead(QIODevice *device)
{
if (!device) {
qWarning("QOIHandler::canRead() called with no device");
return false;
}
device->startTransaction();
QByteArray head = device->read(QOI_HEADER_SIZE);
qsizetype readBytes = head.size();
device->rollbackTransaction();
if (readBytes < QOI_HEADER_SIZE) {
return false;
}
QDataStream stream(head);
stream.setByteOrder(QDataStream::BigEndian);
QoiHeader qoi = {0, 0, 0, 0, 2};
stream >> qoi;
return IsSupported(qoi);
}
bool QOIHandler::read(QImage *image)
{
QDataStream s(device());
s.setByteOrder(QDataStream::BigEndian);
// Read image header
QoiHeader qoi = {0, 0, 0, 0, 2};
s >> qoi;
// Check if file is supported
if (!IsSupported(qoi)) {
return false;
}
QImage img;
bool result = LoadQOI(s.device(), qoi, img);
if (result == false) {
return false;
}
*image = img;
return true;
}
bool QOIHandler::write(const QImage &image)
{
if (image.isNull()) {
return false;
}
QoiHeader qoi;
qoi.MagicNumber = QOI_MAGIC;
qoi.Width = image.width();
qoi.Height = image.height();
qoi.Channels = image.hasAlphaChannel() ? 4 : 3;
qoi.Colorspace = image.colorSpace().transferFunction() == QColorSpace::TransferFunction::Linear ? 1 : 0;
if (!IsSupported(qoi)) {
return false;
}
QDataStream s(device());
s.setByteOrder(QDataStream::BigEndian);
s << qoi;
if (s.status() != QDataStream::Ok) {
return false;
}
return SaveQOI(s.device(), qoi, image);
}
bool QOIHandler::supportsOption(ImageOption option) const
{
if (option == QImageIOHandler::Size) {
return true;
}
if (option == QImageIOHandler::ImageFormat) {
return true;
}
return false;
}
QVariant QOIHandler::option(ImageOption option) const
{
QVariant v;
if (option == QImageIOHandler::Size) {
if (auto d = device()) {
// transactions works on both random and sequential devices
d->startTransaction();
auto ba = d->read(sizeof(QoiHeader));
d->rollbackTransaction();
QDataStream s(ba);
s.setByteOrder(QDataStream::BigEndian);
QoiHeader header = {0, 0, 0, 0, 2};
s >> header;
if (s.status() == QDataStream::Ok && IsSupported(header)) {
v = QVariant::fromValue(QSize(header.Width, header.Height));
}
}
}
if (option == QImageIOHandler::ImageFormat) {
if (auto d = device()) {
// transactions works on both random and sequential devices
d->startTransaction();
auto ba = d->read(sizeof(QoiHeader));
d->rollbackTransaction();
QDataStream s(ba);
s.setByteOrder(QDataStream::BigEndian);
QoiHeader header = {0, 0, 0, 0, 2};
s >> header;
if (s.status() == QDataStream::Ok && IsSupported(header)) {
v = QVariant::fromValue(imageFormat(header));
}
}
}
return v;
}
QImageIOPlugin::Capabilities QOIPlugin::capabilities(QIODevice *device, const QByteArray &format) const
{
if (format == "qoi" || format == "QOI") {
return Capabilities(CanRead | CanWrite);
}
if (!format.isEmpty()) {
return {};
}
if (!device->isOpen()) {
return {};
}
Capabilities cap;
if (device->isReadable() && QOIHandler::canRead(device)) {
cap |= CanRead;
}
if (device->isWritable()) {
cap |= CanWrite;
}
return cap;
}
QImageIOHandler *QOIPlugin::create(QIODevice *device, const QByteArray &format) const
{
QImageIOHandler *handler = new QOIHandler;
handler->setDevice(device);
handler->setFormat(format);
return handler;
}
#include "moc_qoi_p.cpp"

Some files were not shown because too many files have changed in this diff Show More