Compare commits

...

45 Commits

Author SHA1 Message Date
bfc73ca260 GIT_SILENT Upgrade ECM and KF version requirements for 5.112.0 release. 2023-11-04 10:03:03 +00:00
f6bb59228e GIT_SILENT Upgrade ECM and KF version requirements for 5.111.0 release. 2023-10-07 10:20:46 +00:00
ee381958b2 avif: support repetition count
and minor performance optimizations.
2023-10-06 12:52:48 +02:00
478e49b8a6 raw: fix multi image load
Fixes not loading a second image in the file. This patch allow code like the following.

QImageReader r(file);
do {
    auto qi = r.read();
    if (!qi.isNull()) {
        qi.save(QString("/tmp/%1_%2.tif")
                .arg(QFileInfo(file).baseName())
                .arg(r.currentImageNumber()));
    }
}
while (r.jumpToNextImage());

m_startPos is used to reposition the device if you decide to do a subsequent read: libraw wants it to be at the beginning of the RAW stream

(cherry picked from commit 18ea0492bc)
2023-09-25 22:56:39 +02:00
c3daf86079 hdr: fix oss-fuzz issue 62197
Fixes the following error:

| /src/kimageformats/src/imageformats/hdr.cpp:56:31: runtime error: shift exponent 32 is too large for 32-bit type 'int' |
|------------------------------------------------------------------------------------------------------------------------|
2023-09-11 09:07:53 +00:00
a981cefdd2 hdr: fix crash (oss-fuzz)
This patch Fixes crash when RLE data is corrupted (test cases attached).

Should also fixes (no test cases available):
- Issue 62044 in oss-fuzz: kimageformats:kimgio_hdr_fuzzer: Undefined-shift in RGBE_To_QRgbLine
- Issue 62057 in oss-fuzz: kimageformats:kimgio_hdr_fuzzer: Heap-buffer-overflow in Read_Old_Line


[crash-646a4364479f54278ff8c30c69b0c9665e5869af.hdr](/uploads/44760f0286cde4236feab8e352493556/crash-646a4364479f54278ff8c30c69b0c9665e5869af.hdr)

[crash-88c33e2b49e57e6d1d4ec6945476f605f00e714a.hdr](/uploads/e35bf53ee717134a796c4b17cfacca42/crash-88c33e2b49e57e6d1d4ec6945476f605f00e714a.hdr)
2023-09-07 16:22:33 +00:00
723f72930b xcf: fix crash (oss-fuzz issue 62075) 2023-09-07 16:13:18 +00:00
99bb24803a xcf: fix oss-fuzz issue
May fix possible crash on QPainter.
2023-09-05 10:17:22 +00:00
63a9de758f GIT_SILENT Upgrade ECM and KF version requirements for 5.110.0 release. 2023-09-03 07:35:19 +00:00
240e28aac5 QOI: Advertise write support in the desktop file 2023-08-30 14:56:55 +02:00
906ecce500 qoi: write support backported from master 2023-08-30 06:36:28 +00:00
b2b677b8a5 xcf: format v12 support (kf5)
The code is the same of MR !166 (master) but test cases are slightly different due to rounding.
2023-08-29 22:03:34 +00:00
bcec942cc9 Support libavif 1.0
Make cmake find libavif 1.0 and adapt to API changes (which is reverting to pre 0.9.2 API)

(cherry picked from commit 4badb3088e)
2023-08-29 11:05:53 +02:00
66cb8c91d0 No longer needed to explicitly include CMakeParseArguments
NO_CHANGELOG

(cherry picked from commit 4bd9d5baec)
2023-08-28 22:55:25 +02:00
899a2df42d exr: multiple fixes (kf5)
The code is the same of MR !170 (master) but test cases are slightly different due to rounding.
* Support for images with transparency
* Precise colorspace conversion using QT color spaces
* Set the correct resolution
* Set useful metadata
* Creates 16-bits images
* Speed improvements
2023-08-28 17:29:34 +00:00
47920ed63c Fix missing qoi.desktop 2023-08-19 08:53:00 +02:00
274f30e008 qoi: fix buffer overflow kf5
Backport of MR !167:
- fix buffer overflow with corrupted images without image data
- fix unable to read very small images (e.g. 1x1 px)
- new test cases added
- detect incomplete files by checking the end of streams as written in the specs
2023-08-18 15:11:41 +00:00
4348a09733 Renamed qoi.h to qoi_p.h
(cherry picked from commit 7a0d95af92)
2023-08-14 00:15:55 +02:00
dd4576a472 Minor improvements
(cherry picked from commit 4c3ade04dd)
2023-08-14 00:15:52 +02:00
9438540735 Add support for the QOI image format
This MR adds read-only QOI (https://qoiformat.org/) image format support for KImageFormats.

This format has received it's MIME type inclusion in shared-mime-info 4 months ago: ff51a0a9e3

The code is based on the reference QOI implementation at https://github.com/phoboslab/qoi/blob/master/qoi.h

Official test images: https://qoiformat.org/qoi_test_images.zip

![pngvsqoi](/uploads/e386aa5057641057106e21940c770d97/pngvsqoi.png)

Also: This is my first MR to KDE ;)
(cherry picked from commit b209e54b6f)
2023-08-11 22:47:48 +02:00
cf78907ff4 Set linear color space and round fix 2023-08-08 19:37:46 +00:00
bcb5308545 GIT_SILENT Upgrade ECM and KF version requirements for 5.109.0 release. 2023-08-05 12:37:26 +00:00
c3a91c3bc6 psd: Fix UB type punning
BUGS: 471829
2023-07-16 08:03:58 +00:00
034b8f331b Treat 3-channel MCH images as CMY images
(cherry picked from commit 6559bf8994)
2023-07-03 23:26:50 +02:00
ed6a3c520d Add explicit moc includes to sources for moc-covered headers
* speeds up incremental builds as changes to a header will not always
  need the full mocs_compilation.cpp for all the target's headers rebuild,
  while having a moc file sourced into a source file only adds minor
  extra costs, due to small own code and the used headers usually
  already covered by the source file, being for the same class/struct
* seems to not slow down clean builds, due to empty mocs_compilation.cpp
  resulting in those quickly processed, while the minor extra cost of the
  sourced moc files does not outweigh that in summary.
  Measured times actually improved by some percent points.
  (ideally CMake would just skip empty mocs_compilation.cpp & its object
  file one day)
* enables compiler to see all methods of a class in same compilation unit
  to do some sanity checks
* potentially more inlining in general, due to more in the compilation unit
* allows to keep using more forward declarations in the header, as with the
  moc code being sourced into the cpp file there definitions can be ensured
  and often are already for the needs of the normal class methods

(cherry picked from commit 34ed3bad27)
2023-07-02 11:55:33 +02:00
bf1c7e8508 GIT_SILENT Upgrade ECM and KF version requirements for 5.108.0 release. 2023-07-01 09:58:24 +00:00
3cb6519dcc jxl: add support for libjxl v0.9, drop support for old 0.6.1 2023-06-27 23:05:15 +00:00
6cbf7529ee Remove qt6 CI builds
The Qt6 builds for kf5 are no longer needed
2023-06-27 18:33:03 +02:00
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
108 changed files with 2513 additions and 595 deletions

View File

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

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

BIN
autotests/read/qoi/1px.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 551 B

BIN
autotests/read/qoi/1px.qoi Normal file

Binary file not shown.

BIN
autotests/read/qoi/2px.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 561 B

BIN
autotests/read/qoi/2px.qoi Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 KiB

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 115 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.

BIN
autotests/write/rgb.qoi Normal file

Binary file not shown.

BIN
autotests/write/rgba.qoi Normal file

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

View File

@ -104,9 +104,6 @@ endif()
if (LibJXL_FOUND AND LibJXLThreads_FOUND) if (LibJXL_FOUND AND LibJXLThreads_FOUND)
kimageformats_add_plugin(kimg_jxl SOURCES jxl.cpp) kimageformats_add_plugin(kimg_jxl SOURCES jxl.cpp)
target_link_libraries(kimg_jxl PkgConfig::LibJXL PkgConfig::LibJXLThreads) 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") if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES jxl.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/) install(FILES jxl.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
@ -136,6 +133,13 @@ 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) kimageformats_add_plugin(kimg_ras SOURCES ras.cpp)
if (QT_MAJOR_VERSION STREQUAL "5") if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES ras.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/) install(FILES ras.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)

View File

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

View File

@ -330,6 +330,10 @@ bool QAVIFHandler::decode_one_frame()
avifRGBImage rgb; avifRGBImage rgb;
avifRGBImageSetDefaults(&rgb, m_decoder->image); avifRGBImageSetDefaults(&rgb, m_decoder->image);
#if AVIF_VERSION >= 1000000
rgb.maxThreads = m_decoder->maxThreads;
#endif
if (m_decoder->image->depth > 8) { if (m_decoder->image->depth > 8) {
rgb.depth = 16; rgb.depth = 16;
rgb.format = AVIF_RGB_FORMAT_RGBA; rgb.format = AVIF_RGB_FORMAT_RGBA;
@ -424,7 +428,7 @@ bool QAVIFHandler::decode_one_frame()
} }
if (m_decoder->image->transformFlags & AVIF_TRANSFORM_IMIR) { if (m_decoder->image->transformFlags & AVIF_TRANSFORM_IMIR) {
#if AVIF_VERSION > 90100 #if AVIF_VERSION > 90100 && AVIF_VERSION < 1000000
switch (m_decoder->image->imir.mode) { switch (m_decoder->image->imir.mode) {
#else #else
switch (m_decoder->image->imir.axis) { switch (m_decoder->image->imir.axis) {
@ -714,9 +718,9 @@ bool QAVIFHandler::write(const QImage &image)
if (save_depth == 8) { if (save_depth == 8) {
save_depth = 10; save_depth = 10;
if (tmpcolorimage.hasAlphaChannel()) { if (tmpcolorimage.hasAlphaChannel()) {
tmpcolorimage = tmpcolorimage.convertToFormat(QImage::Format_RGBA64); tmpcolorimage.convertTo(QImage::Format_RGBA64);
} else { } else {
tmpcolorimage = tmpcolorimage.convertToFormat(QImage::Format_RGBX64); tmpcolorimage.convertTo(QImage::Format_RGBX64);
} }
} }
@ -1039,6 +1043,11 @@ int QAVIFHandler::loopCount() const
return 0; 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 // Endless loop to work around https://github.com/AOMediaCodec/libavif/issues/347
return -1; return -1;
} }
@ -1103,3 +1112,5 @@ QImageIOHandler *QAVIFPlugin::create(QIODevice *device, const QByteArray &format
handler->setFormat(format); handler->setFormat(format);
return handler; return handler;
} }
#include "moc_avif_p.cpp"

View File

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

View File

@ -3,10 +3,27 @@
in the high dynamic range EXR format. in the high dynamic range EXR format.
SPDX-FileCopyrightText: 2003 Brad Hards <bradh@frogmouth.net> SPDX-FileCopyrightText: 2003 Brad Hards <bradh@frogmouth.net>
SPDX-FileCopyrightText: 2023 Mirco Miranda <mircomir@outlook.com>
SPDX-License-Identifier: LGPL-2.0-or-later 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 "exr_p.h"
#include "util_p.h" #include "util_p.h"
@ -30,11 +47,24 @@
#include <iostream> #include <iostream>
#include <QColorSpace>
#include <QDataStream> #include <QDataStream>
#include <QDebug> #include <QDebug>
#include <QFloat16>
#include <QImage> #include <QImage>
#include <QImageIOPlugin> #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 class K_IStream : public Imf::IStream
{ {
public: public:
@ -94,77 +124,21 @@ void K_IStream::clear()
// TODO // TODO
} }
/* this does a conversion from the ILM Half (equal to Nvidia Half) #ifdef EXR_USE_LEGACY_CONVERSIONS
* format into the normal 32 bit pixel format. Process is from the // source: https://openexr.com/en/latest/ReadingAndWritingImageFiles.html
* ILM code. inline unsigned char gamma(float x)
*/
QRgb RgbaToQrgba(struct Imf::Rgba &imagePixel)
{ {
float r; x = std::pow(5.5555f * std::max(0.f, x), 0.4545f) * 84.66f;
float g; return (unsigned char)qBound(0.f, x, 255.f);
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() EXRHandler::EXRHandler()
{ {
@ -188,29 +162,98 @@ bool EXRHandler::read(QImage *outImage)
K_IStream istr(device(), QByteArray()); K_IStream istr(device(), QByteArray());
Imf::RgbaInputFile file(istr); Imf::RgbaInputFile file(istr);
Imath::Box2i dw = file.dataWindow(); Imath::Box2i dw = file.dataWindow();
bool isRgba = file.channels() & Imf::RgbaChannels::WRITE_A;
width = dw.max.x - dw.min.x + 1; width = dw.max.x - dw.min.x + 1;
height = dw.max.y - dw.min.y + 1; height = dw.max.y - dw.min.y + 1;
QImage image = imageAlloc(width, height, QImage::Format_RGB32); #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
if (image.isNull()) { if (image.isNull()) {
qWarning() << "Failed to allocate image, invalid size?" << QSize(width, height); qWarning() << "Failed to allocate image, invalid size?" << QSize(width, height);
return false; return false;
} }
Imf::Array2D<Imf::Rgba> pixels; // set some useful metadata
pixels.resizeErase(height, width); auto &&h = file.header();
if (auto comments = h.findTypedAttribute<Imf::StringAttribute>("comments")) {
file.setFrameBuffer(&pixels[0][0] - dw.min.x - dw.min.y * width, 1, width); image.setText(QStringLiteral("Comment"), QString::fromStdString(comments->value()));
file.readPixels(dw.min.y, dw.max.y); }
if (auto owner = h.findTypedAttribute<Imf::StringAttribute>("owner")) {
// somehow copy pixels into image image.setText(QStringLiteral("Owner"), QString::fromStdString(owner->value()));
for (int y = 0; y < height; y++) { }
for (int x = 0; x < width; x++) { if (auto capDate = h.findTypedAttribute<Imf::StringAttribute>("capDate")) {
// copy pixels(x,y) into image(x,y) float off = 0;
image.setPixel(x, y, RgbaToQrgba(pixels[y][x])); 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::Array<Imf::Rgba> pixels;
pixels.resizeErase(width);
// 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
}
}
// 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; *outImage = image;
@ -259,3 +302,5 @@ QImageIOHandler *EXRPlugin::create(QIODevice *device, const QByteArray &format)
handler->setFormat(format); handler->setFormat(format);
return handler; return handler;
} }
#include "moc_exr_p.cpp"

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

@ -46,33 +46,6 @@ typedef enum {
// From GIMP "libgimp/gimpenums.h" v2.4 // 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 // From GIMP "paint_funcs.c" v1.2
/*! /*!

View File

@ -9,6 +9,7 @@
#include "hdr_p.h" #include "hdr_p.h"
#include "util_p.h" #include "util_p.h"
#include <QColorSpace>
#include <QDataStream> #include <QDataStream>
#include <QImage> #include <QImage>
#include <QLoggingCategory> #include <QLoggingCategory>
@ -28,11 +29,8 @@ namespace // Private.
static inline uchar ClipToByte(float value) static inline uchar ClipToByte(float value)
{ {
if (value > 255.0f) { // we know value is positive.
return 255; return uchar(std::min(value + 0.5f, 255.0f));
}
// 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 // read an old style line from the hdr image file
@ -42,6 +40,7 @@ static bool Read_Old_Line(uchar *image, int width, QDataStream &s)
int rshift = 0; int rshift = 0;
int i; int i;
uchar *start = image;
while (width > 0) { while (width > 0) {
s >> image[0]; s >> image[0];
s >> image[1]; s >> image[1];
@ -53,7 +52,14 @@ static bool Read_Old_Line(uchar *image, int width, QDataStream &s)
} }
if ((image[0] == 1) && (image[1] == 1) && (image[2] == 1)) { if ((image[0] == 1) && (image[1] == 1) && (image[2] == 1)) {
for (i = image[3] << rshift; i > 0; i--) { // 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
}
// memcpy(image, image-4, 4); // memcpy(image, image-4, 4);
(uint &)image[0] = (uint &)image[0 - 4]; (uint &)image[0] = (uint &)image[0 - 4];
image += 4; image += 4;
@ -74,7 +80,7 @@ static void RGBE_To_QRgbLine(uchar *image, QRgb *scanline, int width)
for (int j = 0; j < width; j++) { for (int j = 0; j < width; j++) {
// v = ldexp(1.0, int(image[3]) - 128); // v = ldexp(1.0, int(image[3]) - 128);
float v; float v;
int e = int(image[3]) - 128; int e = qBound(-31, int(image[3]) - 128, 31);
if (e > 0) { if (e > 0) {
v = float(1 << e); v = float(1 << e);
} else { } else {
@ -148,7 +154,7 @@ static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &i
} }
// read each component // read each component
for (int i = 0; i < 4; i++) { for (int i = 0, len = int(lineArray.size()); i < 4; i++) {
for (int j = 0; j < width;) { for (int j = 0; j < width;) {
s >> code; s >> code;
if (s.atEnd()) { if (s.atEnd()) {
@ -160,14 +166,20 @@ static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &i
code &= 127; code &= 127;
s >> val; s >> val;
while (code != 0) { while (code != 0) {
image[i + j * 4] = val; auto idx = i + j * 4;
if (idx < len) {
image[idx] = val;
}
j++; j++;
code--; code--;
} }
} else { } else {
// non-run // non-run
while (code != 0) { while (code != 0) {
s >> image[i + j * 4]; auto idx = i + j * 4;
if (idx < len) {
s >> image[idx];
}
j++; j++;
code--; code--;
} }
@ -242,6 +254,9 @@ bool HDRHandler::read(QImage *outImage)
// qDebug() << "Error loading HDR file."; // qDebug() << "Error loading HDR file.";
return false; 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; *outImage = img;
return true; return true;
@ -296,3 +311,5 @@ QImageIOHandler *HDRPlugin::create(QIODevice *device, const QByteArray &format)
handler->setFormat(format); handler->setFormat(format);
return handler; return handler;
} }
#include "moc_hdr_p.cpp"

View File

@ -449,6 +449,14 @@ bool HEIFHandler::ensureDecoder()
return false; 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 bool hasAlphaChannel = heif_image_handle_has_alpha_channel(handle);
const int bit_depth = heif_image_handle_get_luma_bits_per_pixel(handle); const int bit_depth = heif_image_handle_get_luma_bits_per_pixel(handle);
heif_chroma chroma; heif_chroma chroma;
@ -918,3 +926,5 @@ QImageIOHandler *HEIFPlugin::create(QIODevice *device, const QByteArray &format)
handler->setFormat(format); handler->setFormat(format);
return handler; return handler;
} }
#include "moc_heif_p.cpp"

View File

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

View File

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

View File

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

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
s >> flag; quint8 flag = 0;
// qDebug() << "Palette Flag: " << flag; 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;
}
}
// 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)
{ {
img = img.convertToFormat(QImage::Format_Mono); 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;
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();
// qDebug() << "Image Depth: " << img.depth();
if (!img.isNull()) {
*outImage = img;
return true;
} else {
return false; 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) 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)
@ -739,3 +792,5 @@ QImageIOHandler *PCXPlugin::create(QIODevice *device, const QByteArray &format)
handler->setFormat(format); handler->setFormat(format);
return handler; return handler;
} }
#include "moc_pcx_p.cpp"

View File

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

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"
@ -42,6 +42,7 @@
#include <QColorSpace> #include <QColorSpace>
#include <cmath> #include <cmath>
#include <cstring>
typedef quint32 uint; typedef quint32 uint;
typedef quint16 ushort; typedef quint16 ushort;
@ -51,7 +52,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
@ -669,7 +670,7 @@ static bool IsSupported(const PSDHeader &header)
return false; return false;
} }
if (header.color_mode == CM_MULTICHANNEL && if (header.color_mode == CM_MULTICHANNEL &&
header.channel_count < 4) { header.channel_count < 3) {
return false; return false;
} }
return true; return true;
@ -733,9 +734,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
@ -808,25 +809,80 @@ inline quint32 xchg(quint32 v) {
#endif #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> template<class T>
inline void planarToChunchy(uchar *target, const char *source, qint32 width, qint32 c, qint32 cn) inline void planarToChunchy(uchar *target, const char *source, qint32 width, qint32 c, qint32 cn)
{ {
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]);
} }
} }
template<class T, T min = 0, T max = 1> template<class T>
inline void planarToChunchyFloat(uchar *target, const char *source, qint32 width, qint32 c, qint32 cn) inline void planarToChunchyFloatToUInt16(uchar *target, const char *source, qint32 width, qint32 c, qint32 cn)
{ {
auto s = reinterpret_cast<const T*>(source); auto s = reinterpret_cast<const T*>(source);
auto t = reinterpret_cast<quint16*>(target); auto t = reinterpret_cast<quint16*>(target);
for (qint32 x = 0; x < width; ++x) { for (qint32 x = 0; x < width; ++x) {
auto tmp = xchg(s[x]); t[x * cn + c] = quint16(std::min(xchg(s[x]) * std::numeric_limits<quint16>::max() + 0.5, double(std::numeric_limits<quint16>::max())));
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()))); }
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,24 +895,37 @@ 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 < 3) {
qDebug() << "cmykToRgb: image is not a valid CMYK!"; qDebug() << "cmykToRgb: image is not a valid CMY/CMYK!";
return; return;
} }
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 = sourceChannels > 3 ? 1 - *(ps + 3) * invmax : 0.0;
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 +950,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 +962,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 +971,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 +1128,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,12 +1156,32 @@ 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) {
planarToChunchyFloatToUInt16<float>(scanLine, rawStride.data(), header.width, c, header.channel_count);
} }
else if (header.depth == 32) { // Not currently used }
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);
} }
} }
@ -1090,18 +1189,23 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
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) {
@ -1112,17 +1216,14 @@ 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) planarToChunchyFloatToUInt16<float>(scanLine, rawStride.data(), header.width, c, imgChannels);
planarToChunchyFloat<quint32>(scanLine, rawStride.data(), header.width, c, imgChannels);
} }
} }
} }
@ -1264,6 +1365,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);
@ -1295,3 +1399,5 @@ QImageIOHandler *PSDPlugin::create(QIODevice *device, const QByteArray &format)
handler->setFormat(format); handler->setFormat(format);
return handler; return handler;
} }
#include "moc_psd_p.cpp"

475
src/imageformats/qoi.cpp Normal file
View File

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

View File

@ -0,0 +1,7 @@
[Desktop Entry]
Type=Service
X-KDE-ServiceTypes=QImageIOPlugins
X-KDE-ImageFormat=qoi
X-KDE-MimeType=image/qoi
X-KDE-Read=true
X-KDE-Write=true

View File

@ -0,0 +1,4 @@
{
"Keys": [ "qoi" ],
"MimeTypes": [ "image/qoi" ]
}

38
src/imageformats/qoi_p.h Normal file
View File

@ -0,0 +1,38 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2023 Ernest Gupik <ernestgupik@wp.pl>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KIMG_QOI_P_H
#define KIMG_QOI_P_H
#include <QImageIOPlugin>
class QOIHandler : public QImageIOHandler
{
public:
QOIHandler();
bool canRead() const override;
bool read(QImage *image) override;
bool write(const QImage &image) override;
bool supportsOption(QImageIOHandler::ImageOption option) const override;
QVariant option(QImageIOHandler::ImageOption option) const override;
static bool canRead(QIODevice *device);
};
class QOIPlugin : public QImageIOPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QImageIOHandlerFactoryInterface" FILE "qoi.json")
public:
Capabilities capabilities(QIODevice *device, const QByteArray &format) const override;
QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const override;
};
#endif // KIMG_QOI_P_H

View File

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

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