Compare commits

..

60 Commits

Author SHA1 Message Date
db0adee62f GIT_SILENT: Port to new CI template 2023-11-20 07:14:46 +01:00
91a342e90d Cleanup pre-Qt 6.5 code 2023-11-19 14:14:36 +01:00
7864ad4bc6 There's no QVector anymore, QList is the QVector in Qt6 2023-10-20 07:10:28 +02:00
524711f633 Add missing license texts
BUG: 475299
2023-10-07 00:00:52 +02:00
b8b980f400 avif: support repetition count
and minor performance optimizations.
2023-10-06 13:03:28 +02:00
d932e0d16b Multi-image, same behavior as the TIFF plugin 2023-09-25 23:02:37 +02:00
18ea0492bc 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
2023-09-25 20:55:53 +00:00
7899c27a80 exr: write support and more
- Added support for writing EXR files
- Large image support (tested with 32GiB 16-bit image)
- OpenEXR multithreaded read/write support through the use of line blocks
- Build option to enable conversion to sRGB when read (disabled by default)
- Multi-view image reading support
- Options support: Size, ImageFormat, Quality, CompressRatio
- Metadata write/read support via EXR attributes

The plugin is now quite complete. It should also work on KF5 (not tested): if there is the will to include this MR also on KF5, let me know and I will do all the necessary tests.
2023-09-17 08:07:47 +00:00
6a51407556 Remove unused Qt5 code 2023-09-15 13:29:13 +02:00
75e1280073 hdr: options support and bugfixes
- Support for Size and Format options
- Added compile option to generate 16-bit instead of 32-bit images
- Includes MR !189 and MR !192 fixes
2023-09-11 09:08:18 +00:00
0a6fbd88e9 xcf: fix crash (oss-fuzz issue 62075) 2023-09-07 16:12:41 +00:00
fbf60f8bbb xcf: fix possible overflow
Port to KF6 of MR !187
2023-09-05 11:32:32 +00:00
2799382c21 Drop code for Qt < 6.5.0
...now that we depend on Qt >= 6.5.0.
2023-08-30 00:27:47 +02:00
adc7da4f41 GIT_SILENT Upgrade Qt6 version requirement to 6.5.0. 2023-08-29 23:31:26 +02:00
ac808679cd qoi: initialize structure before use
It was reported by Code Quality scans as Critical (CWE-457)
2023-08-29 21:04:00 +02:00
2aea982e9e qoi: fix reports from quality scanner
struct QoiHeader is initialized to invalid values prior use,
it’s good to detect situations when we read incomplete data.
Add include in scanlineconverter.cpp so it can be used without
change in kf5 branch.
2023-08-29 16:22:52 +02:00
9173f02ea3 ras: rle decode
Added support for RLE compressed images. This patch makes the player almost complete allowing to read all type 1,2 and 3 RAS files. Only types 4 and 5 are missing which are images converted from TIFF and IFF.
2023-08-29 09:11:35 +00:00
4badb3088e Support libavif 1.0
Make cmake find libavif 1.0 and adapt to API changes (which is reverting to pre 0.9.2 API)
2023-08-29 09:21:37 +02:00
8dc685df26 qoi: write support
As a base I used the reference implementation found on the official site at https://qoiformat.org/ (MIT license).
I added a class to convert scan lines in scanlineconverter.cpp. The class takes advantage of the QImage conversion and contrary to what one might expect, with large images it improves performance (compared to converting the whole image) 😄 

In progressive mode, for each line, the following conversions (only if needed) are made before saving:
1. If the icc profile is set, the line is converted to sRGB or sRGB Linear.
2. The line is scaled to 8 bits with RGBA order.
2023-08-28 22:25:10 +00:00
4bd9d5baec No longer needed to explicitly include CMakeParseArguments
NO_CHANGELOG
2023-08-28 22:54:41 +02:00
79e8e183eb ras: code revamped
- Progressive load from file
- Added support for 1-bit image
- Images with palette are now Index (instead of RGB32)
- Added options support (Size, Format)
- Added some test cases
- Improved performance by directly accessing the scanline
- Support for more RAS extension (taken from GIMP)

The code should works "as is" also on KF5.
2023-08-28 18:02:02 +00:00
7d63a1d8fa exr: multiple fixes
- Support for images with transparency
- Precise colorspace conversion using QT color spaces
- Set the correct resolution
- Set useful metadata
- Support for RGBX16FPx4 format in Qt 6
- Speed improvements

![image](/uploads/bb36492d71acce4995e8b4229a813031/image.png)
2023-08-28 17:30:50 +00:00
c11c5eff4f xcf: format v12 support
This is a patch over MR !108 by @sandsmark. Martin has done a great job implementing support and I find it a shame not to use it.

I made sure that the results are the same as the current version and fixed the problems of pixels with wrong colors with color depth grater than 8 bits. I also fixed conversion errors on mask and gray images (16/32 bits).
Unfortunately the internal rendering engine of the original code is 8-bit so I always forced the output of 8-bit images to correct the problems (see image below). Since it is a plugin with a "rendering engine", the tests to seriously validate it are potentially endless (as the original version is not perfect).

Errors of the original version of the MR (right) which should no longer occur:
![image](/uploads/9ef24eb5436bd19ff1fb428242a9c119/image.png)
2023-08-28 17:27:08 +00:00
c02bf3dbcc Readme.md: Update supported formats list
- (+) Krita (kra)
- (+) OpenRaster (ora)
- (-) XView (xv)
2023-08-20 08:04:21 +00:00
6254529d2d qoi: fix buffer overflow
- 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 14:09:00 +00:00
35ff3efbbc hdr: improve precision
- Improve the precision of HDR format (it is a floating point format) by using the RGBA32FPx4 image format.
2023-08-13 22:19:22 +00:00
7a0d95af92 Renamed qoi.h to qoi_p.h 2023-08-12 23:47:29 +02:00
4c3ade04dd Minor improvements 2023-08-12 09:03:56 +02:00
b209e54b6f 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 ;)
2023-08-11 20:44:17 +00:00
4dc2099fa4 Set linear color space for proper viewing 2023-07-21 14:17:11 +02:00
491b223c15 psd: Fix UB type punning
BUGS: 471829
2023-07-16 08:07:13 +00:00
6559bf8994 Treat 3-channel MCH images as CMY images 2023-07-03 12:34:54 +02:00
34ed3bad27 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
2023-07-02 03:08:44 +02:00
9c579fc1f8 jxl: add support for libjxl v0.9, drop support for old 0.6.1 2023-06-27 14:32:08 +02:00
93adb22632 raw: change the use of the quality parameter
- Standardized the quality parameter between 0-100
- The value -1 is the default value of Qt plugins and is managed
- Negative values other than -1 are used as flags to activate the custom mode
2023-05-17 17:45:23 +00:00
d57ff91f8b 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.
2023-05-12 08:53:50 +00:00
edd6adcbac Avoid unnecessary conversions 2023-05-10 15:34:14 +02:00
d787c12727 RGB/SGI writer: fix alpha detection and image limit size 2023-05-10 11:43:21 +00:00
c9fec5e408 TGA writer: fix alpha detection and performance improvements 2023-05-10 11:43:21 +00:00
e60dfd4968 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
2023-05-10 11:43:04 +00:00
f5a9dd46d2 Removed unused jxl.desktop file 2023-04-22 09:31:30 +02:00
41f0411b62 PSD: fix test failure on some systems and fix wrong check on alpha conversion
- Fix PSD alpha conversion
- Fix autoread test to use also TIFF images
2023-04-18 22:14:41 +00:00
e1a3751936 CMake: make use of qt_add_plugin for plugins
Currently it will only produce shared library binaries as plugin.
This could make building static imageformats plugin much easier.

Related: https://invent.kde.org/frameworks/kimageformats/-/issues/4
2023-03-30 03:42:41 +00:00
402dfb5de3 psd: Fix alpha blending
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.
2023-03-29 17:55:55 +00:00
a3f7c03b61 heif: Add format read-write support in README 2023-02-15 08:36:24 +00:00
20cec27ae8 Fix writing TGA alpha depth flag
Correctly write alpha channel depth as 8-bit.
2023-02-01 16:26:31 -06:00
d34c1668aa Bump Qt deprecation level to 6.4 2023-02-01 17:16:45 +01:00
560d0483ae Replace deprecated functions
Improve the code by replacing deprecated functions as suggested by the Qt documentation.
2023-01-31 21:32:30 +00:00
085c9c4841 HDR format removed from RAW plugin 2023-01-30 21:58:36 +00:00
b654f20ece heif: reject invalid files with zero size 2023-01-29 16:16:52 +01:00
21211cd63b psd: LAB conversion speed improved
Using an approximated pow function improves the conversion speed of LAB images by more than 50% without an appreciable visual difference.
2023-01-27 23:19:48 +00:00
5cc7a2b45c psd: native 32-bits RGB support
This patch maps RGB 32-bits PSD to QImage::Format_RGBA32FPx4 or QImage::Format_RGBX32FPx4.
2023-01-26 23:17:09 +00:00
4451737d2f Remove Qt 5 support 2023-01-24 17:15:14 +01:00
01ab0876f1 Debug code removed 2023-01-22 11:04:48 +01:00
a67dcac7d1 LAB/CMYK conversion speed improved by ~10% 2023-01-22 10:57:54 +01:00
cef8d08ad4 Bump KF_DEP_VERSION for KF6 2023-01-21 13:30:04 +01:00
a4b9dd9400 Rename CMake targets/config/libraries for KF6 2023-01-20 23:58:58 +01:00
6e6c1ab5f4 Require Qt6
master is for Qt6-based development now

Qt5 development continues in the 'kf5' branch
2023-01-18 23:37:26 +01:00
f205adf2e0 Remove Qt5 CI
master is Qt6 now
2023-01-18 23:37:26 +01:00
14742cb502 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
2023-01-11 22:56:38 +00:00
138 changed files with 3470 additions and 1020 deletions

View File

@ -2,13 +2,9 @@
# SPDX-License-Identifier: CC0-1.0
include:
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux.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/freebsd.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux-qt6.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/android-qt6.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/windows.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/windows-static.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/freebsd-qt6.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/windows-qt6.yml
- project: sysadmin/ci-utilities
file:
- /gitlab-templates/linux-qt6.yml
- /gitlab-templates/android-qt6.yml
- /gitlab-templates/freebsd-qt6.yml
- /gitlab-templates/windows-qt6.yml

View File

@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.16)
project(KImageFormats)
include(FeatureSummary)
find_package(ECM 5.107.0 NO_MODULE)
find_package(ECM 5.240.0 NO_MODULE)
set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://commits.kde.org/extra-cmake-modules")
feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES)
@ -19,11 +19,11 @@ include(ECMDeprecationSettings)
include(CheckIncludeFiles)
include(FindPkgConfig)
set(REQUIRED_QT_VERSION 5.15.2)
find_package(Qt${QT_MAJOR_VERSION}Gui ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE)
set(REQUIRED_QT_VERSION 6.5.0)
find_package(Qt6Gui ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE)
find_package(KF5Archive)
set_package_properties(KF5Archive PROPERTIES
find_package(KF6Archive)
set_package_properties(KF6Archive PROPERTIES
TYPE OPTIONAL
PURPOSE "Required for the QImage plugin for Krita and OpenRaster images"
)
@ -32,12 +32,12 @@ set_package_properties(KF5Archive PROPERTIES
# this available in PATH
set(BUILD_EPS_PLUGIN FALSE)
if (UNIX)
find_package(Qt${QT_MAJOR_VERSION}PrintSupport ${REQUIRED_QT_VERSION} NO_MODULE)
set_package_properties(Qt${QT_MAJOR_VERSION}PrintSupport PROPERTIES
find_package(Qt6PrintSupport ${REQUIRED_QT_VERSION} NO_MODULE)
set_package_properties(Qt6PrintSupport PROPERTIES
PURPOSE "Required for the QImage plugin for EPS images"
TYPE OPTIONAL
)
if (TARGET Qt${QT_MAJOR_VERSION}::PrintSupport)
if (TARGET Qt6::PrintSupport)
set(BUILD_EPS_PLUGIN TRUE)
endif()
endif()
@ -51,7 +51,10 @@ set_package_properties(OpenEXR PROPERTIES
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
TYPE OPTIONAL
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)
if(KIMAGEFORMATS_JXL)
pkg_check_modules(LibJXL IMPORTED_TARGET libjxl>=0.6.1)
pkg_check_modules(LibJXLThreads IMPORTED_TARGET libjxl_threads>=0.6.1)
pkg_check_modules(LibJXL IMPORTED_TARGET libjxl>=0.7.0)
pkg_check_modules(LibJXLThreads IMPORTED_TARGET libjxl_threads>=0.7.0)
endif()
add_feature_info(LibJXL LibJXL_FOUND "required for the QImage plugin for JPEG XL images")
@ -78,8 +81,8 @@ set_package_properties(LibRaw PROPERTIES
)
ecm_set_disabled_deprecation_versions(
QT 5.15.2
KF 5.95
QT 6.5
KF 5.102
)
add_subdirectory(src)

View File

@ -0,0 +1,9 @@
Copyright (c) <year> <owner>
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

11
LICENSES/BSD-3-Clause.txt Normal file
View File

@ -0,0 +1,11 @@
Copyright (c) <year> <owner>.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -14,22 +14,26 @@ image formats.
The following image formats have read-only support:
- Animated Windows cursors (ani)
- Gimp (xcf)
- OpenEXR (exr)
- Photoshop documents (psd, psb, pdd, psdt)
- Sun Raster (ras)
- Camera RAW images (arw, cr2, cr3, dcs, dng, ...)
- Gimp (xcf)
- Krita (kra)
- OpenRaster (ora)
- Photoshop documents (psd, psb, pdd, psdt)
- Radiance HDR (hdr)
- Sun Raster (im1, im8, im24, im32, ras, sun)
The following image formats have read and write support:
- AV1 Image File Format (AVIF)
- Encapsulated PostScript (eps)
- High Efficiency Image File Format (heif). Can be enabled with the KIMAGEFORMATS_HEIF build option.
- JPEG XL (jxl)
- OpenEXR (exr)
- Personal Computer Exchange (pcx)
- Quite OK Image format (qoi)
- SGI images (rgb, rgba, sgi, bw)
- Softimage PIC (pic)
- Targa (tga): supports more formats than Qt's version
- XView (xv)
## Contributing
@ -41,10 +45,6 @@ of Qt is the license. As such, if you write an imageformat plugin and
you are willing to sign the Qt Project contributor agreement, it may be
better to submit the plugin directly to the Qt Project.
Note that the imageformat plugins provided by this module also provide a
desktop file. This is for the benefit of KImageIO in the KDE4 Support
framework.
## Duplicated Plugins
The TGA plugin supports more formats than Qt's own TGA plugin;

View File

@ -1,7 +1,4 @@
#find_package(Qt5Test ${REQUIRED_QT_VERSION} NO_MODULE)
include(ECMMarkAsTest)
include(CMakeParseArguments)
add_definitions(-DPLUGIN_DIR="${CMAKE_CURRENT_BINARY_DIR}/../bin")
remove_definitions(-DQT_NO_CAST_FROM_ASCII)
@ -15,7 +12,7 @@ macro(kimageformats_read_tests)
if (NOT TARGET readtest)
add_executable(readtest readtest.cpp)
target_link_libraries(readtest Qt${QT_MAJOR_VERSION}::Gui)
target_link_libraries(readtest Qt6::Gui)
target_compile_definitions(readtest
PRIVATE IMAGEDIR="${CMAKE_CURRENT_SOURCE_DIR}/read")
ecm_mark_as_test(readtest)
@ -38,7 +35,7 @@ macro(kimageformats_write_tests)
if (NOT TARGET writetest)
add_executable(writetest writetest.cpp)
target_link_libraries(writetest Qt${QT_MAJOR_VERSION}::Gui)
target_link_libraries(writetest Qt6::Gui)
target_compile_definitions(writetest
PRIVATE IMAGEDIR="${CMAKE_CURRENT_SOURCE_DIR}/write")
ecm_mark_as_test(writetest)
@ -70,12 +67,13 @@ kimageformats_read_tests(
hdr
pcx
psd
qoi
ras
rgb
tga
)
if (KF5Archive_FOUND)
if (KF6Archive_FOUND)
kimageformats_read_tests(
kra
ora
@ -125,6 +123,7 @@ kimageformats_read_tests(FUZZ 1
kimageformats_write_tests(
pcx-lossless
pic-lossless
qoi-lossless
rgb-lossless
tga # fixme: the alpha images appear not to be written properly
)
@ -139,6 +138,11 @@ if (OpenEXR_FOUND)
kimageformats_read_tests(
exr
)
# Color space conversions from sRGB to linear on saving and
# from linear to sRGB on loading result in some rounding errors.
kimageformats_write_tests(FUZZ 5
exr-nodatacheck-lossless
)
endif()
if (LibRaw_FOUND)
@ -147,19 +151,19 @@ if (LibRaw_FOUND)
)
endif()
find_package(Qt${QT_MAJOR_VERSION}Test ${REQUIRED_QT_VERSION} CONFIG QUIET)
find_package(Qt6Test ${REQUIRED_QT_VERSION} CONFIG QUIET)
if(NOT TARGET Qt${QT_MAJOR_VERSION}::Test)
message(STATUS "Qt${QT_MAJOR_VERSION}Test not found, some autotests will not be built.")
if(NOT TARGET Qt6::Test)
message(STATUS "Qt6Test not found, some autotests will not be built.")
return()
endif()
add_executable(pictest pictest.cpp)
target_link_libraries(pictest Qt${QT_MAJOR_VERSION}::Gui Qt${QT_MAJOR_VERSION}::Test)
target_link_libraries(pictest Qt6::Gui Qt6::Test)
ecm_mark_as_test(pictest)
add_test(NAME kimageformats-pic COMMAND pictest)
add_executable(anitest anitest.cpp)
target_link_libraries(anitest Qt${QT_MAJOR_VERSION}::Gui Qt${QT_MAJOR_VERSION}::Test)
target_link_libraries(anitest Qt6::Gui Qt6::Test)
ecm_mark_as_test(anitest)
add_test(NAME kimageformats-ani COMMAND anitest)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Binary file not shown.

After

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

Before

Width:  |  Height:  |  Size: 189 KiB

After

Width:  |  Height:  |  Size: 189 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: 192 B

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 470 B

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 B

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 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: 96 KiB

Binary file not shown.

Binary file not shown.

After

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

@ -162,13 +162,13 @@ int main(int argc, char **argv)
if (!fi.suffix().compare("png", Qt::CaseInsensitive) || !fi.suffix().compare("tif", Qt::CaseInsensitive)) {
continue;
}
int suffixPos = fi.filePath().count() - suffix.count();
int suffixPos = fi.filePath().size() - suffix.size();
QString inputfile = fi.filePath();
QString fmt = QStringLiteral("png");
QString expfile = fi.filePath().replace(suffixPos, suffix.count(), fmt);
QString expfile = fi.filePath().replace(suffixPos, suffix.size(), fmt);
if (!QFile::exists(expfile)) { // try with tiff
fmt = QStringLiteral("tif");
expfile = fi.filePath().replace(suffixPos, suffix.count(), fmt);
expfile = fi.filePath().replace(suffixPos, suffix.size(), fmt);
}
QString expfilename = QFileInfo(expfile).fileName();

BIN
autotests/write/rgb.qoi Normal file

Binary file not shown.

BIN
autotests/write/rgba.qoi Normal file

Binary file not shown.

View File

@ -7,6 +7,7 @@
#include <stdio.h>
#include <QBuffer>
#include <QColorSpace>
#include <QCommandLineParser>
#include <QCoreApplication>
#include <QDir>
@ -23,8 +24,8 @@ int main(int argc, char **argv)
QCoreApplication app(argc, argv);
QCoreApplication::removeLibraryPath(QStringLiteral(PLUGIN_DIR));
QCoreApplication::addLibraryPath(QStringLiteral(PLUGIN_DIR));
QCoreApplication::setApplicationName(QStringLiteral("readtest"));
QCoreApplication::setApplicationVersion(QStringLiteral("1.0.0"));
QCoreApplication::setApplicationName(QStringLiteral("writetest"));
QCoreApplication::setApplicationVersion(QStringLiteral("1.1.0"));
QCommandLineParser parser;
parser.setApplicationDescription(QStringLiteral("Performs basic image conversion checking."));
@ -85,8 +86,8 @@ int main(int argc, char **argv)
if (parser.isSet(ignoreDataCheck)) {
pngfile = fi.filePath();
} else {
int suffixPos = fi.filePath().count() - suffix.count();
pngfile = fi.filePath().replace(suffixPos, suffix.count(), QStringLiteral("png"));
int suffixPos = fi.filePath().size() - suffix.size();
pngfile = fi.filePath().replace(suffixPos, suffix.size(), QStringLiteral("png"));
}
QString pngfilename = QFileInfo(pngfile).fileName();
@ -148,6 +149,14 @@ int main(int argc, char **argv)
++failed;
continue;
}
if (reReadImage.colorSpace().isValid()) {
QColorSpace toColorSpace;
if (pngImage.colorSpace().isValid()) {
reReadImage.convertToColorSpace(pngImage.colorSpace());
} else {
reReadImage.convertToColorSpace(QColorSpace(QColorSpace::SRgb));
}
}
reReadImage = reReadImage.convertToFormat(pngImage.format());
}

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.
Michael Ritzert <kde@ritzert.de> -- JPEG 2000 format read/write support
Troy Unrau <troy@kde.org> -- Sun RASter read support
Ernest Gupik <ernestgupik@wp.pl> -- QOI format read support

View File

@ -11,9 +11,10 @@ function(kimageformats_add_plugin plugin)
message(FATAL_ERROR "kimageformats_add_plugin called without SOURCES parameter")
endif()
add_library(${plugin} MODULE ${KIF_ADD_PLUGIN_SOURCES})
set_target_properties(${plugin} PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/imageformats")
target_link_libraries(${plugin} Qt${QT_MAJOR_VERSION}::Gui)
qt_add_plugin(${plugin} PLUGIN_TYPE imageformats)
target_sources(${plugin} PRIVATE ${KIF_ADD_PLUGIN_SOURCES})
set_target_properties(${plugin} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/imageformats)
target_link_libraries(${plugin} PRIVATE Qt6::Gui)
install(TARGETS ${plugin} DESTINATION ${KDE_INSTALL_QTPLUGINDIR}/imageformats)
endfunction()
@ -21,172 +22,105 @@ endfunction()
kimageformats_add_plugin(kimg_ani SOURCES ani.cpp)
if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES ani.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
##################################
if (TARGET avif)
kimageformats_add_plugin(kimg_avif SOURCES "avif.cpp")
target_link_libraries(kimg_avif "avif")
if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES avif.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
endif()
##################################
if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES dds-qt.desktop RENAME dds.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
target_link_libraries(kimg_avif PRIVATE "avif")
endif()
##################################
if (BUILD_EPS_PLUGIN)
if (TARGET Qt${QT_MAJOR_VERSION}::PrintSupport)
if (TARGET Qt6::PrintSupport)
kimageformats_add_plugin(kimg_eps SOURCES eps.cpp)
target_link_libraries(kimg_eps Qt${QT_MAJOR_VERSION}::PrintSupport)
if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES eps.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
target_link_libraries(kimg_eps PRIVATE Qt6::PrintSupport)
endif()
endif()
##################################
if (QT_MAJOR_VERSION STREQUAL "5")
# need this for Qt's version of the plugin
install(FILES jp2.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
##################################
if(OpenEXR_FOUND)
kimageformats_add_plugin(kimg_exr SOURCES exr.cpp)
kimageformats_add_plugin(kimg_exr SOURCES exr.cpp scanlineconverter.cpp)
if(TARGET OpenEXR::OpenEXR)
target_link_libraries(kimg_exr OpenEXR::OpenEXR)
target_link_libraries(kimg_exr PRIVATE OpenEXR::OpenEXR)
else()
if(OpenEXR_VERSION_STRING VERSION_LESS 2.3.0)
# Older OpenEXR versions use dynamic exception specifications, so
# cannot use C++17 with them
set_target_properties(kimg_exr PROPERTIES CXX_STANDARD 14)
endif()
target_link_libraries(kimg_exr OpenEXR::IlmImf)
target_link_libraries(kimg_exr PRIVATE OpenEXR::IlmImf)
endif()
kde_target_enable_exceptions(kimg_exr PRIVATE)
if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES exr.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
endif()
##################################
kimageformats_add_plugin(kimg_hdr SOURCES hdr.cpp)
if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES hdr.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
##################################
if (LibHeif_FOUND)
kimageformats_add_plugin(kimg_heif SOURCES heif.cpp)
target_link_libraries(kimg_heif PkgConfig::LibHeif)
if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES heif.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
target_link_libraries(kimg_heif PRIVATE PkgConfig::LibHeif)
endif()
##################################
if (LibJXL_FOUND AND LibJXLThreads_FOUND)
kimageformats_add_plugin(kimg_jxl SOURCES jxl.cpp)
target_link_libraries(kimg_jxl PkgConfig::LibJXL PkgConfig::LibJXLThreads)
if (LibJXL_VERSION VERSION_GREATER_EQUAL "0.7.0")
target_compile_definitions(kimg_jxl PRIVATE KIMG_JXL_API_VERSION=70)
endif()
if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES jxl.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
target_link_libraries(kimg_jxl PRIVATE PkgConfig::LibJXL PkgConfig::LibJXLThreads)
endif()
##################################
kimageformats_add_plugin(kimg_pcx SOURCES pcx.cpp)
if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES pcx.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
##################################
kimageformats_add_plugin(kimg_pic SOURCES pic.cpp)
if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES pic.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
##################################
kimageformats_add_plugin(kimg_psd SOURCES psd.cpp)
if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES psd.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
##################################
kimageformats_add_plugin(kimg_qoi SOURCES qoi.cpp scanlineconverter.cpp)
##################################
kimageformats_add_plugin(kimg_ras SOURCES ras.cpp)
if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES ras.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
##################################
kimageformats_add_plugin(kimg_rgb SOURCES rgb.cpp)
if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES rgb.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
##################################
kimageformats_add_plugin(kimg_tga SOURCES tga.cpp)
if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES tga.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
##################################
kimageformats_add_plugin(kimg_xcf SOURCES xcf.cpp)
if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES xcf.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
##################################
if (LibRaw_FOUND)
kimageformats_add_plugin(kimg_raw SOURCES raw.cpp)
kde_enable_exceptions()
target_link_libraries(kimg_raw LibRaw::LibRaw)
if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES raw.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
target_link_libraries(kimg_raw PRIVATE LibRaw::LibRaw)
endif()
##################################
if (KF5Archive_FOUND)
if (KF6Archive_FOUND)
kimageformats_add_plugin(kimg_kra SOURCES kra.cpp)
target_link_libraries(kimg_kra KF5::Archive)
if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES kra.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
target_link_libraries(kimg_kra PRIVATE KF6::Archive)
kimageformats_add_plugin(kimg_ora SOURCES ora.cpp)
target_link_libraries(kimg_ora KF5::Archive)
if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES ora.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
target_link_libraries(kimg_ora PRIVATE KF6::Archive)
endif()

View File

@ -90,7 +90,7 @@ bool ANIHandler::read(QImage *outImage)
}
const QByteArray frameSizeData = device()->read(sizeof(quint32_le));
if (frameSizeData.count() != sizeof(quint32_le)) {
if (frameSizeData.size() != sizeof(quint32_le)) {
return false;
}
@ -383,8 +383,8 @@ bool ANIHandler::ensureScanned() const
// TODO should we check that the number of rate entries matches nSteps?
auto *dataPtr = data.data();
QVector<int> list;
for (int i = 0; i < data.count(); i += sizeof(quint32_le)) {
QList<int> list;
for (int i = 0; i < data.size(); i += sizeof(quint32_le)) {
const auto entry = *(reinterpret_cast<const quint32_le *>(dataPtr + i));
list.append(entry);
}
@ -570,3 +570,5 @@ QImageIOHandler *ANIPlugin::create(QIODevice *device, const QByteArray &format)
handler->setFormat(format);
return handler;
}
#include "moc_ani_p.cpp"

View File

@ -1,7 +0,0 @@
[Desktop Entry]
Type=Service
X-KDE-ServiceTypes=QImageIOPlugins
X-KDE-ImageFormat=ani
X-KDE-MimeType=application/x-navi-animation
X-KDE-Read=true
X-KDE-Write=false

View File

@ -41,14 +41,14 @@ private:
int m_frameCount = 0; // "physical" frames
int m_imageCount = 0; // logical images
// Stores a custom sequence of images
QVector<int> m_imageSequence;
QList<int> m_imageSequence;
// and the corresponding offsets where they are
// since we can't read the image data sequentally in this case then
QVector<qint64> m_frameOffsets;
QList<qint64> m_frameOffsets;
qint64 m_firstFrameOffset = 0;
int m_displayRate = 0;
QVector<int> m_displayRates;
QList<int> m_displayRates;
QString m_name;
QString m_artist;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,13 +1,66 @@
/*
KImageIO Routines to read (and perhaps in the future, write) images
in the high dynamic range EXR format.
The high dynamic range EXR format support for QImage.
SPDX-FileCopyrightText: 2003 Brad Hards <bradh@frogmouth.net>
SPDX-FileCopyrightText: 2023 Mirco Miranda <mircomir@outlook.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
/* *** EXR_USE_LEGACY_CONVERSIONS ***
* If defined, the result image is an 8-bit RGB(A) converted
* without icc profiles. Otherwise, a 16-bit images is generated.
* NOTE: The use of legacy conversions are discouraged due to
* imprecise image result.
*/
//#define EXR_USE_LEGACY_CONVERSIONS // default commented -> you should define it in your cmake file
/* *** EXR_CONVERT_TO_SRGB ***
* If defined, the linear data is converted to sRGB on read to accommodate
* programs that do not support color profiles.
* Otherwise the data are kept as is and it is the display program that
* must convert to the monitor profile.
* NOTE: If EXR_USE_LEGACY_CONVERSIONS is active, this is ignored.
*/
//#define EXR_CONVERT_TO_SRGB // default: commented -> you should define it in your cmake file
/* *** EXR_STORE_XMP_ATTRIBUTE ***
* If defined, disables the stores XMP values in a non-standard attribute named "xmp".
* The QImage metadata used is "XML:com.adobe.xmp".
* NOTE: The use of non-standard attributes is possible but discouraged by the specification. However,
* metadata is essential for good image management and programs like darktable also set this
* attribute. Gimp reads the "xmp" attribute and Darktable writes it as well.
*/
//#define EXR_DISABLE_XMP_ATTRIBUTE // default: commented -> you should define it in your cmake file
/* *** EXR_MAX_IMAGE_WIDTH and EXR_MAX_IMAGE_HEIGHT ***
* The maximum size in pixel allowed by the plugin.
*/
#ifndef EXR_MAX_IMAGE_WIDTH
#define EXR_MAX_IMAGE_WIDTH 300000
#endif
#ifndef EXR_MAX_IMAGE_HEIGHT
#define EXR_MAX_IMAGE_HEIGHT EXR_MAX_IMAGE_WIDTH
#endif
/* *** EXR_LINES_PER_BLOCK ***
* Allows certain compression schemes to work in multithreading
* Requires up to "LINES_PER_BLOCK * MAX_IMAGE_WIDTH * 8"
* additional RAM (e.g. if 128, up to 307MiB of RAM).
* There is a performance gain with the following parameters (based on empirical tests):
* - PIZ compression needs 64+ lines
* - ZIPS compression needs 8+ lines
* - ZIP compression needs 32+ lines
* - Others not tested
*
* NOTE: The OpenEXR documentation states that the higher the better :)
*/
#ifndef EXR_LINES_PER_BLOCK
#define EXR_LINES_PER_BLOCK 128
#endif
#include "exr_p.h"
#include "scanlineconverter_p.h"
#include "util_p.h"
#include <IexThrowErrnoExc.h>
@ -22,18 +75,29 @@
#include <ImfInt64.h>
#include <ImfIntAttribute.h>
#include <ImfLineOrderAttribute.h>
#include <ImfPreviewImage.h>
#include <ImfRgbaFile.h>
#include <ImfStandardAttributes.h>
#include <ImfStringAttribute.h>
#include <ImfVecAttribute.h>
#include <ImfVersion.h>
#include <iostream>
#include <QColorSpace>
#include <QDataStream>
#include <QDebug>
#include <QFloat16>
#include <QImage>
#include <QImageIOPlugin>
#include <QLocale>
#include <QThread>
#include <QTimeZone>
// 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
{
@ -94,80 +158,82 @@ void K_IStream::clear()
// TODO
}
/* this does a conversion from the ILM Half (equal to Nvidia Half)
* format into the normal 32 bit pixel format. Process is from the
* ILM code.
*/
QRgb RgbaToQrgba(struct Imf::Rgba &imagePixel)
class K_OStream : public Imf::OStream
{
float r;
float g;
float b;
float a;
// 1) Compensate for fogging by subtracting defog
// from the raw pixel values.
// Response: We work with defog of 0.0, so this is a no-op
// 2) Multiply the defogged pixel values by
// 2^(exposure + 2.47393).
// Response: We work with exposure of 0.0.
// (2^2.47393) is 5.55555
r = imagePixel.r * 5.55555;
g = imagePixel.g * 5.55555;
b = imagePixel.b * 5.55555;
a = imagePixel.a * 5.55555;
// 3) Values, which are now 1.0, are called "middle gray".
// If defog and exposure are both set to 0.0, then
// middle gray corresponds to a raw pixel value of 0.18.
// In step 6, middle gray values will be mapped to an
// intensity 3.5 f-stops below the display's maximum
// intensity.
// Response: no apparent content.
// 4) Apply a knee function. The knee function has two
// parameters, kneeLow and kneeHigh. Pixel values
// below 2^kneeLow are not changed by the knee
// function. Pixel values above kneeLow are lowered
// according to a logarithmic curve, such that the
// value 2^kneeHigh is mapped to 2^3.5 (in step 6,
// this value will be mapped to the display's
// maximum intensity).
// Response: kneeLow = 0.0 (2^0.0 => 1); kneeHigh = 5.0 (2^5 =>32)
if (r > 1.0) {
r = 1.0 + std::log((r - 1.0) * 0.184874 + 1) / 0.184874;
public:
K_OStream(QIODevice *dev, const QByteArray &fileName)
: OStream(fileName.data())
, m_dev(dev)
{
}
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)));
void write(const char c[/*n*/], int n) override;
#if OPENEXR_VERSION_MAJOR > 2
uint64_t tellp() override;
void seekp(uint64_t pos) override;
#else
Imf::Int64 tellp() override;
void seekp(Imf::Int64 pos) override;
#endif
private:
QIODevice *m_dev;
};
void K_OStream::write(const char c[], int n)
{
qint64 result = m_dev->write(c, n);
if (result > 0) {
return;
} else { // negative value {
Iex::throwErrnoExc("Error in write", result);
}
return;
}
EXRHandler::EXRHandler()
#if OPENEXR_VERSION_MAJOR > 2
uint64_t K_OStream::tellp()
#else
Imf::Int64 K_OStream::tellg()
#endif
{
return m_dev->pos();
}
#if OPENEXR_VERSION_MAJOR > 2
void K_OStream::seekp(uint64_t pos)
#else
void K_OStream::seekg(Imf::Int64 pos)
#endif
{
m_dev->seek(pos);
}
#ifdef EXR_USE_LEGACY_CONVERSIONS
// source: https://openexr.com/en/latest/ReadingAndWritingImageFiles.html
inline unsigned char gamma(float x)
{
x = std::pow(5.5555f * std::max(0.f, x), 0.4545f) * 84.66f;
return (unsigned char)qBound(0.f, x, 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()
: m_compressionRatio(-1)
, m_quality(-1)
, m_imageNumber(0)
, m_imageCount(0)
, m_startPos(-1)
{
// Set the number of threads to use (0 is allowed)
Imf::setGlobalThreadCount(QThread::idealThreadCount() / 2);
}
bool EXRHandler::canRead() const
@ -179,48 +245,567 @@ bool EXRHandler::canRead() const
return false;
}
static QImage::Format imageFormat(const Imf::RgbaInputFile &file)
{
auto isRgba = file.channels() & Imf::RgbaChannels::WRITE_A;
#if defined(EXR_USE_LEGACY_CONVERSIONS)
return (isRgba ? QImage::Format_ARGB32 : QImage::Format_RGB32);
#elif defined(EXR_USE_QT6_FLOAT_IMAGE)
return (isRgba ? QImage::Format_RGBA16FPx4 : QImage::Format_RGBX16FPx4);
#else
return (isRgba ? QImage::Format_RGBA64 : QImage::Format_RGBX64);
#endif
}
/*!
* \brief viewList
* \param header
* \return The list of available views.
*/
static QStringList viewList(const Imf::Header &h)
{
QStringList l;
if (auto views = h.findTypedAttribute<Imf::StringVectorAttribute>("multiView")) {
for (auto &&v : views->value()) {
l << QString::fromStdString(v);
}
}
return l;
}
#ifdef QT_DEBUG
void printAttributes(const Imf::Header &h)
{
for (auto i = h.begin(); i != h.end(); ++i) {
qDebug() << i.name();
}
}
#endif
/*!
* \brief readMetadata
* Reads EXR attributes from the \a header and set its as metadata in the \a image.
*/
static void readMetadata(const Imf::Header &header, QImage &image)
{
// set some useful metadata
if (auto comments = header.findTypedAttribute<Imf::StringAttribute>("comments")) {
image.setText(QStringLiteral("Comment"), QString::fromStdString(comments->value()));
}
if (auto owner = header.findTypedAttribute<Imf::StringAttribute>("owner")) {
image.setText(QStringLiteral("Owner"), QString::fromStdString(owner->value()));
}
if (auto lat = header.findTypedAttribute<Imf::FloatAttribute>("latitude")) {
image.setText(QStringLiteral("Latitude"), QLocale::c().toString(lat->value()));
}
if (auto lon = header.findTypedAttribute<Imf::FloatAttribute>("longitude")) {
image.setText(QStringLiteral("Longitude"), QLocale::c().toString(lon->value()));
}
if (auto alt = header.findTypedAttribute<Imf::FloatAttribute>("altitude")) {
image.setText(QStringLiteral("Altitude"), QLocale::c().toString(alt->value()));
}
if (auto capDate = header.findTypedAttribute<Imf::StringAttribute>("capDate")) {
float off = 0;
if (auto utcOffset = header.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()) {
dateTime.setTimeZone(QTimeZone::fromSecondsAheadOfUtc(off));
image.setText(QStringLiteral("CreationDate"), dateTime.toString(Qt::ISODate));
}
}
if (auto xDensity = header.findTypedAttribute<Imf::FloatAttribute>("xDensity")) {
float par = 1;
if (auto pixelAspectRatio = header.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));
}
// Non-standard attribute
if (auto xmp = header.findTypedAttribute<Imf::StringAttribute>("xmp")) {
image.setText(QStringLiteral("XML:com.adobe.xmp"), QString::fromStdString(xmp->value()));
}
/* TODO: OpenEXR 3.2 metadata
*
* New Optional Standard Attributes:
* - Support automated editorial workflow:
* reelName, imageCounter, ascFramingDecisionList
*
* - Support forensics (“which other shots used that camera and lens before the camera firmware was updated?”):
* cameraMake, cameraModel, cameraSerialNumber, cameraFirmware, cameraUuid, cameraLabel, lensMake, lensModel,
* lensSerialNumber, lensFirmware, cameraColorBalance
*
* -Support pickup shots (reproduce critical camera settings):
* shutterAngle, cameraCCTSetting, cameraTintSetting
*
* - Support metadata-driven match move:
* sensorCenterOffset, sensorOverallDimensions, sensorPhotositePitch, sensorAcquisitionRectanglenominalFocalLength,
* effectiveFocalLength, pinholeFocalLength, entrancePupilOffset, tStop(complementing existing 'aperture')
*/
}
/*!
* \brief readColorSpace
* Reads EXR chromaticities from the \a header and set its as color profile in the \a image.
*/
static void readColorSpace(const Imf::Header &header, QImage &image)
{
// final color operations
#ifndef EXR_USE_LEGACY_CONVERSIONS
QColorSpace cs;
if (auto chroma = header.findTypedAttribute<Imf::ChromaticitiesAttribute>("chromaticities")) {
auto &&v = chroma->value();
cs = QColorSpace(QPointF(v.white.x, v.white.y),
QPointF(v.red.x, v.red.y),
QPointF(v.green.x, v.green.y),
QPointF(v.blue.x, v.blue.y),
QColorSpace::TransferFunction::Linear);
}
if (!cs.isValid()) {
cs = QColorSpace(QColorSpace::SRgbLinear);
}
image.setColorSpace(cs);
#ifdef EXR_CONVERT_TO_SRGB
image.convertToColorSpace(QColorSpace(QColorSpace::SRgb));
#endif
#endif // !EXR_USE_LEGACY_CONVERSIONS
}
bool EXRHandler::read(QImage *outImage)
{
try {
int width;
int height;
auto d = device();
K_IStream istr(device(), QByteArray());
// set the image position after the first run.
if (!d->isSequential()) {
if (m_startPos < 0) {
m_startPos = d->pos();
} else {
d->seek(m_startPos);
}
}
K_IStream istr(d, QByteArray());
Imf::RgbaInputFile file(istr);
auto &&header = file.header();
// set the image to load
if (m_imageNumber > -1) {
auto views = viewList(header);
if (m_imageNumber < views.count()) {
file.setLayerName(views.at(m_imageNumber).toStdString());
}
}
// get image info
Imath::Box2i dw = file.dataWindow();
qint32 width = dw.max.x - dw.min.x + 1;
qint32 height = dw.max.y - dw.min.y + 1;
width = dw.max.x - dw.min.x + 1;
height = dw.max.y - dw.min.y + 1;
// limiting the maximum image size on a reasonable size (as done in other plugins)
if (width > EXR_MAX_IMAGE_WIDTH || height > EXR_MAX_IMAGE_HEIGHT) {
qWarning() << "The maximum image size is limited to" << EXR_MAX_IMAGE_WIDTH << "x" << EXR_MAX_IMAGE_HEIGHT << "px";
return false;
}
QImage image = imageAlloc(width, height, QImage::Format_RGB32);
// creating the image
QImage image = imageAlloc(width, height, imageFormat(file));
if (image.isNull()) {
qWarning() << "Failed to allocate image, invalid size?" << QSize(width, height);
return false;
}
Imf::Array2D<Imf::Rgba> pixels;
pixels.resizeErase(height, width);
file.setFrameBuffer(&pixels[0][0] - dw.min.x - dw.min.y * width, 1, width);
file.readPixels(dw.min.y, dw.max.y);
pixels.resizeErase(EXR_LINES_PER_BLOCK, width);
bool isRgba = image.hasAlphaChannel();
// somehow copy pixels into image
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
// copy pixels(x,y) into image(x,y)
image.setPixel(x, y, RgbaToQrgba(pixels[y][x]));
for (int y = 0, n = 0; y < height; y += n) {
auto my = dw.min.y + y;
if (my > dw.max.y) { // paranoia check
break;
}
file.setFrameBuffer(&pixels[0][0] - dw.min.x - qint64(my) * width, 1, width);
file.readPixels(my, std::min(my + EXR_LINES_PER_BLOCK - 1, dw.max.y));
for (n = 0; n < std::min(EXR_LINES_PER_BLOCK, height - y); ++n) {
#if defined(EXR_USE_LEGACY_CONVERSIONS)
Q_UNUSED(isRgba)
auto scanLine = reinterpret_cast<QRgb *>(image.scanLine(y + n));
for (int x = 0; x < width; ++x) {
*(scanLine + x) = RgbaToQrgba(pixels[n][x]);
}
#elif defined(EXR_USE_QT6_FLOAT_IMAGE)
auto scanLine = reinterpret_cast<qfloat16 *>(image.scanLine(y + n));
for (int x = 0; x < width; ++x) {
auto xcs = x * 4;
*(scanLine + xcs) = qfloat16(qBound(0.f, float(pixels[n][x].r), 1.f));
*(scanLine + xcs + 1) = qfloat16(qBound(0.f, float(pixels[n][x].g), 1.f));
*(scanLine + xcs + 2) = qfloat16(qBound(0.f, float(pixels[n][x].b), 1.f));
*(scanLine + xcs + 3) = qfloat16(isRgba ? qBound(0.f, float(pixels[n][x].a), 1.f) : 1.f);
}
#else
auto scanLine = reinterpret_cast<QRgba64 *>(image.scanLine(y + n));
for (int x = 0; x < width; ++x) {
*(scanLine + x) = QRgba64::fromRgba64(quint16(qBound(0.f, float(pixels[n][x].r) * 65535.f + 0.5f, 65535.f)),
quint16(qBound(0.f, float(pixels[n][x].g) * 65535.f + 0.5f, 65535.f)),
quint16(qBound(0.f, float(pixels[n][x].b) * 65535.f + 0.5f, 65535.f)),
isRgba ? quint16(qBound(0.f, float(pixels[n][x].a) * 65535.f + 0.5f, 65535.f)) : quint16(65535));
}
#endif
}
}
// set some useful metadata
readMetadata(header, image);
// final color operations
readColorSpace(header, image);
*outImage = image;
return true;
} catch (const std::exception &exc) {
// qDebug() << exc.what();
} catch (const std::exception &) {
return false;
}
}
/*!
* \brief makePreview
* Creates a preview of maximum 256 x 256 pixels from the \a image.
*/
bool makePreview(const QImage &image, Imf::Array2D<Imf::PreviewRgba> &pixels)
{
auto w = image.width();
auto h = image.height();
QImage preview;
if (w > h) {
preview = image.scaledToWidth(256).convertToFormat(QImage::Format_ARGB32);
} else {
preview = image.scaledToHeight(256).convertToFormat(QImage::Format_ARGB32);
}
if (preview.isNull()) {
return false;
}
w = preview.width();
h = preview.height();
pixels.resizeErase(h, w);
preview.convertToColorSpace(QColorSpace(QColorSpace::SRgb));
for (int y = 0; y < h; ++y) {
auto scanLine = reinterpret_cast<const QRgb *>(preview.constScanLine(y));
for (int x = 0; x < w; ++x) {
auto &&out = pixels[y][x];
out.r = qRed(*(scanLine + x));
out.g = qGreen(*(scanLine + x));
out.b = qBlue(*(scanLine + x));
out.a = qAlpha(*(scanLine + x));
}
}
return true;
}
/*!
* \brief setMetadata
* Reades the metadata from \a image and set its as attributes in the \a header.
*/
static void setMetadata(const QImage &image, Imf::Header &header)
{
auto dateTime = QDateTime::currentDateTime();
for (auto &&key : image.textKeys()) {
auto text = image.text(key);
if (!key.compare(QStringLiteral("Comment"), Qt::CaseInsensitive)) {
header.insert("comments", Imf::StringAttribute(text.toStdString()));
}
if (!key.compare(QStringLiteral("Owner"), Qt::CaseInsensitive)) {
header.insert("owner", Imf::StringAttribute(text.toStdString()));
}
// clang-format off
if (!key.compare(QStringLiteral("Latitude"), Qt::CaseInsensitive) ||
!key.compare(QStringLiteral("Longitude"), Qt::CaseInsensitive) ||
!key.compare(QStringLiteral("Altitude"), Qt::CaseInsensitive)) {
// clang-format on
auto ok = false;
auto value = QLocale::c().toFloat(text, &ok);
if (ok) {
header.insert(qPrintable(key.toLower()), Imf::FloatAttribute(value));
}
}
if (!key.compare(QStringLiteral("CreationDate"), Qt::CaseInsensitive)) {
auto dt = QDateTime::fromString(text, Qt::ISODate);
if (dt.isValid()) {
dateTime = dt;
}
}
#ifndef EXR_DISABLE_XMP_ATTRIBUTE // warning: Non-standard attribute!
if (!key.compare(QStringLiteral("XML:com.adobe.xmp"), Qt::CaseInsensitive)) {
header.insert("xmp", Imf::StringAttribute(text.toStdString()));
}
#endif
}
if (dateTime.isValid()) {
header.insert("capDate", Imf::StringAttribute(dateTime.toString(QStringLiteral("yyyy:MM:dd HH:mm:ss")).toStdString()));
header.insert("utcOffset", Imf::FloatAttribute(dateTime.offsetFromUtc()));
}
if (image.dotsPerMeterX() && image.dotsPerMeterY()) {
header.insert("xDensity", Imf::FloatAttribute(image.dotsPerMeterX() * 2.54f / 100.f));
header.insert("pixelAspectRatio", Imf::FloatAttribute(float(image.dotsPerMeterX()) / float(image.dotsPerMeterY())));
}
// set default chroma (default constructor ITU-R BT.709-3 -> sRGB)
// The image is converted to Linear sRGB so, the chroma is the default EXR value.
// If a file doesnt have a chromaticities attribute, display software should assume that the
// files primaries and the white point match Rec. ITU-R BT.709-3.
// header.insert("chromaticities", Imf::ChromaticitiesAttribute(Imf::Chromaticities()));
// TODO: EXR 3.2 attributes (see readMetadata())
}
bool EXRHandler::write(const QImage &image)
{
try {
// create EXR header
qint32 width = image.width();
qint32 height = image.height();
// limiting the maximum image size on a reasonable size (as done in other plugins)
if (width > EXR_MAX_IMAGE_WIDTH || height > EXR_MAX_IMAGE_HEIGHT) {
qWarning() << "The maximum image size is limited to" << EXR_MAX_IMAGE_WIDTH << "x" << EXR_MAX_IMAGE_HEIGHT << "px";
return false;
}
Imf::Header header(width, height);
// set compression scheme (forcing PIZ as default)
header.compression() = Imf::Compression::PIZ_COMPRESSION;
if (m_compressionRatio >= qint32(Imf::Compression::NO_COMPRESSION) && m_compressionRatio < qint32(Imf::Compression::NUM_COMPRESSION_METHODS)) {
header.compression() = Imf::Compression(m_compressionRatio);
}
// set the DCT quality (used by DCT compressions only)
if (m_quality > -1 && m_quality <= 100) {
header.dwaCompressionLevel() = float(m_quality);
}
// make ZIP compression fast (used by ZIP compressions)
header.zipCompressionLevel() = 1;
// set preview (don't set it for small images)
if (width > 1024 || height > 1024) {
Imf::Array2D<Imf::PreviewRgba> previewPixels;
if (makePreview(image, previewPixels)) {
header.setPreviewImage(Imf::PreviewImage(previewPixels.width(), previewPixels.height(), &previewPixels[0][0]));
}
}
// set metadata (EXR attributes)
setMetadata(image, header);
// write the EXR
K_OStream ostr(device(), QByteArray());
Imf::RgbaOutputFile file(ostr, header, image.hasAlphaChannel() ? Imf::RgbaChannels::WRITE_RGBA : Imf::RgbaChannels::WRITE_RGB);
Imf::Array2D<Imf::Rgba> pixels;
pixels.resizeErase(EXR_LINES_PER_BLOCK, width);
// convert the image and write into the stream
#if defined(EXR_USE_QT6_FLOAT_IMAGE)
auto convFormat = image.hasAlphaChannel() ? QImage::Format_RGBA16FPx4 : QImage::Format_RGBX16FPx4;
#else
auto convFormat = image.hasAlphaChannel() ? QImage::Format_RGBA64 : QImage::Format_RGBX64;
#endif
ScanLineConverter slc(convFormat);
slc.setDefaultSourceColorSpace(QColorSpace(QColorSpace::SRgb));
slc.setTargetColorSpace(QColorSpace(QColorSpace::SRgbLinear));
for (int y = 0, n = 0; y < height; y += n) {
for (n = 0; n < std::min(EXR_LINES_PER_BLOCK, height - y); ++n) {
#if defined(EXR_USE_QT6_FLOAT_IMAGE)
auto scanLine = reinterpret_cast<const qfloat16 *>(slc.convertedScanLine(image, y + n));
if (scanLine == nullptr) {
return false;
}
for (int x = 0; x < width; ++x) {
auto xcs = x * 4;
pixels[n][x].r = float(*(scanLine + xcs));
pixels[n][x].g = float(*(scanLine + xcs + 1));
pixels[n][x].b = float(*(scanLine + xcs + 2));
pixels[n][x].a = float(*(scanLine + xcs + 3));
}
#else
auto scanLine = reinterpret_cast<const QRgba64 *>(slc.convertedScanLine(image, y + n));
if (scanLine == nullptr) {
return false;
}
for (int x = 0; x < width; ++x) {
pixels[n][x].r = float((scanLine + x)->red() / 65535.f);
pixels[n][x].g = float((scanLine + x)->green() / 65535.f);
pixels[n][x].b = float((scanLine + x)->blue() / 65535.f);
pixels[n][x].a = float((scanLine + x)->alpha() / 65535.f);
}
#endif
}
file.setFrameBuffer(&pixels[0][0] - qint64(y) * width, 1, width);
file.writePixels(n);
}
} catch (const std::exception &) {
return false;
}
return true;
}
void EXRHandler::setOption(ImageOption option, const QVariant &value)
{
if (option == QImageIOHandler::CompressionRatio) {
auto ok = false;
auto cr = value.toInt(&ok);
if (ok) {
m_compressionRatio = cr;
}
}
if (option == QImageIOHandler::Quality) {
auto ok = false;
auto q = value.toInt(&ok);
if (ok) {
m_quality = q;
}
}
}
bool EXRHandler::supportsOption(ImageOption option) const
{
if (option == QImageIOHandler::Size) {
return true;
}
if (option == QImageIOHandler::ImageFormat) {
return true;
}
if (option == QImageIOHandler::CompressionRatio) {
return true;
}
if (option == QImageIOHandler::Quality) {
return true;
}
return false;
}
QVariant EXRHandler::option(ImageOption option) const
{
QVariant v;
if (option == QImageIOHandler::Size) {
if (auto d = device()) {
// transactions works on both random and sequential devices
d->startTransaction();
try {
K_IStream istr(d, QByteArray());
Imf::RgbaInputFile file(istr);
if (m_imageNumber > -1) { // set the image to read
auto views = viewList(file.header());
if (m_imageNumber < views.count()) {
file.setLayerName(views.at(m_imageNumber).toStdString());
}
}
Imath::Box2i dw = file.dataWindow();
v = QVariant(QSize(dw.max.x - dw.min.x + 1, dw.max.y - dw.min.y + 1));
} catch (const std::exception &) {
// broken file or unsupported version
}
d->rollbackTransaction();
}
}
if (option == QImageIOHandler::ImageFormat) {
if (auto d = device()) {
// transactions works on both random and sequential devices
d->startTransaction();
try {
K_IStream istr(d, QByteArray());
Imf::RgbaInputFile file(istr);
v = QVariant::fromValue(imageFormat(file));
} catch (const std::exception &) {
// broken file or unsupported version
}
d->rollbackTransaction();
}
}
if (option == QImageIOHandler::CompressionRatio) {
v = QVariant(m_compressionRatio);
}
if (option == QImageIOHandler::Quality) {
v = QVariant(m_quality);
}
return v;
}
bool EXRHandler::jumpToNextImage()
{
return jumpToImage(m_imageNumber + 1);
}
bool EXRHandler::jumpToImage(int imageNumber)
{
if (imageNumber < 0 || imageNumber >= imageCount()) {
return false;
}
m_imageNumber = imageNumber;
return true;
}
int EXRHandler::imageCount() const
{
// NOTE: image count is cached for performance reason
auto &&count = m_imageCount;
if (count > 0) {
return count;
}
count = QImageIOHandler::imageCount();
auto d = device();
d->startTransaction();
try {
K_IStream istr(d, QByteArray());
Imf::RgbaInputFile file(istr);
auto views = viewList(file.header());
if (!views.isEmpty()) {
count = views.size();
}
} catch (const std::exception &) {
// do nothing
}
d->rollbackTransaction();
return count;
}
int EXRHandler::currentImageNumber() const
{
return m_imageNumber;
}
bool EXRHandler::canRead(QIODevice *device)
{
if (!device) {
@ -236,7 +821,7 @@ bool EXRHandler::canRead(QIODevice *device)
QImageIOPlugin::Capabilities EXRPlugin::capabilities(QIODevice *device, const QByteArray &format) const
{
if (format == "exr") {
return Capabilities(CanRead);
return Capabilities(CanRead | CanWrite);
}
if (!format.isEmpty()) {
return {};
@ -249,6 +834,9 @@ QImageIOPlugin::Capabilities EXRPlugin::capabilities(QIODevice *device, const QB
if (device->isReadable() && EXRHandler::canRead(device)) {
cap |= CanRead;
}
if (device->isWritable()) {
cap |= CanWrite;
}
return cap;
}
@ -259,3 +847,5 @@ QImageIOHandler *EXRPlugin::create(QIODevice *device, const QByteArray &format)
handler->setFormat(format);
return handler;
}
#include "moc_exr_p.cpp"

View File

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

View File

@ -1,8 +1,8 @@
/*
QImageIO Routines to read (and perhaps in the future, write) images
in the high definition EXR format.
The high dynamic range EXR format support for QImage.
SPDX-FileCopyrightText: 2003 Brad Hards <bradh@frogmouth.net>
SPDX-FileCopyrightText: 2023 Mirco Miranda <mircomir@outlook.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
@ -12,6 +12,44 @@
#include <QImageIOPlugin>
/*!
* \brief The EXRHandler class
* The handler uses the OpenEXR reference implementation of the EXR file format.
*
* The purpose of EXR format is to accurately and efficiently represent high-dynamic-range scene-linear
* image data and associated metadata.
*
* Both reading and writing of EXR files is supported. When saving, the image is converted to 16-bit
* and sRGB Linear color space (if not already so). If no color space is set in the image, sRGB is assumed.
* When the handler is compiled with the default compile options, the loaded image is a 16-bit image
* with linear color space.
* Multiview images are also supported (read only) via imageCount(), jumpToImage(), etc....
*
* The following QImageIOHandler options are supported:
* - ImageFormat: The image's data format returned by the handler.
* - Size: The size of the image.
* - CompressionRatio: The compression ratio of the image data (see OpenEXR compression schemes).
* - Quality: The quality level of the image (see OpenEXR compression level of lossy codecs).
*
* The following metadata are set/get via QImage::setText()/QImage::text() in both read/write (if any):
* - Latitude, Longitude, Altitude: Geographic coordinates (Float converted to string).
* - CreationDate: Date the image was captured/created (QDateTime converted to string using Qt::ISODate).
* - Comment: Additional image information in human-readable form, for example a verbal description of the image (QString).
* - Owner: Name of the owner of the image (QString).
*
* In addition, information about image resolution is preserved and the preview is written for images larger
* than 1024px.
*
* The following compile options are supported (defines):
* - EXR_MAX_IMAGE_WIDTH: Maximum image width supported (read/write, default: 300000 px).
* - EXR_MAX_IMAGE_HEIGHT: Maximum image height supported (read/write, default: 300000 px).
* - EXR_LINES_PER_BLOCK: The number of scanlines buffered on both read and write operations.\n
* The higher the value, the greater the parallelization but the RAM consumption increases (default: 128)
* - EXR_USE_LEGACY_CONVERSIONS: The result image is an 8-bit RGB(A) converted without icc profiles (read, default: undefined).
* - EXR_CONVERT_TO_SRGB: The resulting image is convertef in the sRGB color space (read, default: undefined).
* - EXR_DISABLE_XMP_ATTRIBUTE: Disable the stores of XMP values in a non-standard attribute named "xmp".\n
* The QImage metadata used is "XML:com.adobe.xmp" (write, default: undefined).
*/
class EXRHandler : public QImageIOHandler
{
public:
@ -19,8 +57,65 @@ public:
bool canRead() const override;
bool read(QImage *outImage) override;
bool write(const QImage &image) override;
void setOption(ImageOption option, const QVariant &value) override;
bool supportsOption(QImageIOHandler::ImageOption option) const override;
QVariant option(QImageIOHandler::ImageOption option) const override;
bool jumpToNextImage() override;
bool jumpToImage(int imageNumber) override;
int imageCount() const override;
int currentImageNumber() const override;
static bool canRead(QIODevice *device);
private:
/*!
* \brief m_compressionRatio
* Value set by QImageWriter::setCompression().
*
* The compression scheme is the same as defined by OpenEXR library:
* - 0: no compression
* - 1: run length encoding
* - 2: zlib compression, one scan line at a time
* - 3: zlib compression, in blocks of 16 scan lines
* - 4: piz-based wavelet compression (default)
* - 5: lossy 24-bit float compression
* - 6: lossy 4-by-4 pixel block compression, fixed compression rate
* - 7: lossy 4-by-4 pixel block compression, fields are compressed more
* - 8: lossy DCT based compression, in blocks of 32 scanlines. More efficient for partial buffer access.
* - 9: lossy DCT based compression, in blocks of 256 scanlines. More efficient space wise and faster to decode full frames than DWAA_COMPRESSION.
*/
qint32 m_compressionRatio;
/*!
* \brief m_quality
* Value set by QImageWriter::setQuality().
*
* The quality is used on DCT compression schemes only with a
* supposed value between 0 and 100 (default: 45).
*/
qint32 m_quality;
/*!
* \brief m_imageNumber
* Value set by QImageReader::jumpToImage() or QImageReader::jumpToNextImage().
* The number of view selected in a multiview image.
*/
qint32 m_imageNumber;
/*!
* \brief m_imageCount
* The total number of views (cache value)
*/
mutable qint32 m_imageCount;
/*!
* \brief m_startPos
* The initial device position to allow multi image load (cache value).
*/
qint64 m_startPos;
};
class EXRPlugin : public QImageIOPlugin

View File

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

View File

@ -9,13 +9,20 @@
#include "hdr_p.h"
#include "util_p.h"
#include <QColorSpace>
#include <QDataStream>
#include <QFloat16>
#include <QImage>
#include <QLoggingCategory>
#include <QRegularExpressionMatch>
#include <QDebug>
/* *** HDR_HALF_QUALITY ***
* If defined, a 16-bits float image is created, otherwise a 32-bits float ones (default).
*/
//#define HDR_HALF_QUALITY // default commented -> you should define it in your cmake file
typedef unsigned char uchar;
Q_LOGGING_CATEGORY(HDRPLUGIN, "kf.imageformats.plugins.hdr", QtWarningMsg)
@ -26,15 +33,6 @@ namespace // Private.
#define MINELEN 8 // minimum scanline length for encoding
#define MAXELEN 0x7fff // maximum scanline length for encoding
static inline uchar ClipToByte(float value)
{
if (value > 255.0f) {
return 255;
}
// else if (value < 0.0f) return 0; // we know value is positive.
return uchar(value);
}
// read an old style line from the hdr image file
// if 'first' is true the first byte is already read
static bool Read_Old_Line(uchar *image, int width, QDataStream &s)
@ -42,6 +40,7 @@ static bool Read_Old_Line(uchar *image, int width, QDataStream &s)
int rshift = 0;
int i;
uchar *start = image;
while (width > 0) {
s >> image[0];
s >> image[1];
@ -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)) {
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);
(uint &)image[0] = (uint &)image[0 - 4];
image += 4;
@ -69,24 +75,38 @@ static bool Read_Old_Line(uchar *image, int width, QDataStream &s)
return true;
}
static void RGBE_To_QRgbLine(uchar *image, QRgb *scanline, int width)
template<class float_T>
void RGBE_To_QRgbLine(uchar *image, float_T *scanline, int width)
{
for (int j = 0; j < width; j++) {
// v = ldexp(1.0, int(image[3]) - 128);
float v;
int e = int(image[3]) - 128;
int e = qBound(-31, int(image[3]) - 128, 31);
if (e > 0) {
v = float(1 << e);
} else {
v = 1.0f / float(1 << -e);
}
scanline[j] = qRgb(ClipToByte(float(image[0]) * v), ClipToByte(float(image[1]) * v), ClipToByte(float(image[2]) * v));
auto j4 = j * 4;
auto vn = v / 255.0f;
scanline[j4] = float_T(std::min(float(image[0]) * vn, 1.0f));
scanline[j4 + 1] = float_T(std::min(float(image[1]) * vn, 1.0f));
scanline[j4 + 2] = float_T(std::min(float(image[2]) * vn, 1.0f));
scanline[j4 + 3] = float_T(1.0f);
image += 4;
}
}
QImage::Format imageFormat()
{
#ifdef HDR_HALF_QUALITY
return QImage::Format_RGBX16FPx4;
#else
return QImage::Format_RGBX32FPx4;
#endif
}
// Load the HDR image.
static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &img)
{
@ -94,7 +114,7 @@ static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &i
uchar code;
// Create dst image.
img = imageAlloc(width, height, QImage::Format_RGB32);
img = imageAlloc(width, height, imageFormat());
if (img.isNull()) {
qCDebug(HDRPLUGIN) << "Couldn't create image with size" << width << height << "and format RGB32";
return false;
@ -102,10 +122,14 @@ static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &i
QByteArray lineArray;
lineArray.resize(4 * width);
uchar *image = (uchar *)lineArray.data();
uchar *image = reinterpret_cast<uchar *>(lineArray.data());
for (int cline = 0; cline < height; cline++) {
QRgb *scanline = (QRgb *)img.scanLine(cline);
#ifdef HDR_HALF_QUALITY
auto scanline = reinterpret_cast<qfloat16 *>(img.scanLine(cline));
#else
auto scanline = reinterpret_cast<float *>(img.scanLine(cline));
#endif
// determine scanline type
if ((width < MINELEN) || (MAXELEN < width)) {
@ -148,7 +172,7 @@ static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &i
}
// 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;) {
s >> code;
if (s.atEnd()) {
@ -160,14 +184,20 @@ static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &i
code &= 127;
s >> val;
while (code != 0) {
image[i + j * 4] = val;
auto idx = i + j * 4;
if (idx < len) {
image[idx] = val;
}
j++;
code--;
}
} else {
// non-run
while (code != 0) {
s >> image[i + j * 4];
auto idx = i + j * 4;
if (idx < len) {
s >> image[idx];
}
j++;
code--;
}
@ -181,9 +211,7 @@ static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &i
return true;
}
} // namespace
bool HDRHandler::read(QImage *outImage)
static QSize readHeaderSize(QIODevice *device)
{
int len;
QByteArray line(MAXLINE + 1, Qt::Uninitialized);
@ -191,7 +219,7 @@ bool HDRHandler::read(QImage *outImage)
// Parse header
do {
len = device()->readLine(line.data(), MAXLINE);
len = device->readLine(line.data(), MAXLINE);
if (line.startsWith("FORMAT=")) {
format = line.mid(7, len - 7 - 1 /*\n*/);
@ -201,10 +229,10 @@ bool HDRHandler::read(QImage *outImage)
if (format != "32-bit_rle_rgbe") {
qCDebug(HDRPLUGIN) << "Unknown HDR format:" << format;
return false;
return QSize();
}
len = device()->readLine(line.data(), MAXLINE);
len = device->readLine(line.data(), MAXLINE);
line.resize(len);
/*
@ -224,29 +252,75 @@ bool HDRHandler::read(QImage *outImage)
QRegularExpressionMatch match = resolutionRegExp.match(QString::fromLatin1(line));
if (!match.hasMatch()) {
qCDebug(HDRPLUGIN) << "Invalid HDR file, the first line after the header didn't have the expected format:" << line;
return false;
return QSize();
}
if ((match.captured(1).at(1) != u'Y') || (match.captured(3).at(1) != u'X')) {
qCDebug(HDRPLUGIN) << "Unsupported image orientation in HDR file.";
return QSize();
}
return QSize(match.captured(4).toInt(), match.captured(2).toInt());
}
} // namespace
bool HDRHandler::read(QImage *outImage)
{
QDataStream s(device());
QSize size = readHeaderSize(s.device());
if (!size.isValid()) {
return false;
}
const int width = match.captured(4).toInt();
const int height = match.captured(2).toInt();
QDataStream s(device());
QImage img;
if (!LoadHDR(s, width, height, img)) {
if (!LoadHDR(s, size.width(), size.height(), img)) {
// qDebug() << "Error loading HDR file.";
return false;
}
// The images read by Gimp and Photoshop (including those of the tests) are interpreted with linear color space.
// By setting the linear color space, programs that support profiles display HDR files as in GIMP and Photoshop.
img.setColorSpace(QColorSpace(QColorSpace::SRgbLinear));
*outImage = img;
return true;
}
bool HDRHandler::supportsOption(ImageOption option) const
{
if (option == QImageIOHandler::Size) {
return true;
}
if (option == QImageIOHandler::ImageFormat) {
return true;
}
return false;
}
QVariant HDRHandler::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 size = readHeaderSize(d);
d->rollbackTransaction();
if (size.isValid()) {
v = QVariant::fromValue(size);
}
}
}
if (option == QImageIOHandler::ImageFormat) {
v = QVariant::fromValue(imageFormat());
}
return v;
}
HDRHandler::HDRHandler()
{
}
@ -267,7 +341,20 @@ bool HDRHandler::canRead(QIODevice *device)
return false;
}
return device->peek(11) == "#?RADIANCE\n" || device->peek(7) == "#?RGBE\n";
// the .pic taken from official test cases does not start with this string but can be loaded.
if(device->peek(11) == "#?RADIANCE\n" || device->peek(7) == "#?RGBE\n") {
return true;
}
// allow to load offical test cases: https://radsite.lbl.gov/radiance/framed.html
device->startTransaction();
QSize size = readHeaderSize(device);
device->rollbackTransaction();
if (size.isValid()) {
return true;
}
return false;
}
QImageIOPlugin::Capabilities HDRPlugin::capabilities(QIODevice *device, const QByteArray &format) const
@ -296,3 +383,5 @@ QImageIOHandler *HDRPlugin::create(QIODevice *device, const QByteArray &format)
handler->setFormat(format);
return handler;
}
#include "moc_hdr_p.cpp"

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