Compare commits

..

53 Commits

Author SHA1 Message Date
8c23e74ef6 Update dependency version to 6.5.0 2024-08-02 12:56:31 +02:00
219d9cb2c2 JXL: added ImageTransformation option
Let Qt rotate the image when the ImageAutotransform option is set to true.

In tests it also solves the image size control with the value returned by the options with certain rotations.
2024-07-30 22:46:52 +00:00
51921e8ee5 xcf: Fix crash on malformed files 2024-07-29 20:53:13 +02:00
23e9fec869 pcx: Fix crash in broken files 2024-07-26 16:53:32 +02:00
4478bc8d2b xcf: Fix crash on broken files 2024-07-25 00:04:38 +02:00
acd6b3970c pcx: fix crash on invalid files 2024-07-23 00:22:08 +02:00
638fdfcbdd pcx: fix crash on invalid files
Added new PCX testfile for readtest.
2024-07-22 22:13:14 +00:00
a497ab789b exr: added some usefull attributes 2024-07-21 04:52:18 +00:00
3590a43fc5 pcx: Read 16 color images that are 4bpp and 1 plane
We had code for 1bpp and 4 planes but gimp is generating this format
2024-07-19 21:30:59 +00:00
f5a6de7280 Full range HDR support
EXR, HDR, JXR and PFM formats support High Dynamic Range images (FP values grater than 1).

In summary, here is the list of changes:

    EXR, HDR, JXR and PFM: When working with FP formats, the clamp between 0 and 1 ​​is no longer done.
    EXR: Removed old SDR code and conversions. Due to the lack of a QImage Gray FP format, Gray images are output as RGB FP (recently added code for Qt 6.8 has been removed).
    PFM: Due to the lack of a QImage Gray FP format, Gray images are output as RGB FP.
    HDR: Added rotation and exposure support.

With this patch, EXR, JXR, HDR, PFM behave like Qt's TIFF plugin when working with FP images.
2024-07-17 22:24:57 +02:00
4c0f49295b Prepare gitlab for files that are coming int the next commit 2024-07-17 22:24:33 +02:00
e9da5edb9a avif: check return values
Some libavif calls did not return values in the older versions.
Situation changed meanwhile;
we can check the return values for error conditions now.
2024-07-15 17:46:21 +02:00
e10f5aa9a5 raw: Fix compiler warning with macro redefinition in Windows
This fixes the following compiler warning with mingw-w64 toolchain in Windows.

raw.cpp:436:9: warning: "DEFAULT_QUALITY" redefined
  436 | #define DEFAULT_QUALITY (C_IQ(3) | C_OC(1) | C_CW(1) | C_AW(1) | C_BT(1) | C_HS(0) | C_FLAGS(1))
      |         ^~~~~~~~~~~~~~~
wingdi.h:1142:9: note: this is the location of the previous definition
 1142 | #define DEFAULT_QUALITY 0
      |         ^~~~~~~~~~~~~~~

DEFAULT_QUALITY macro is used with CreateFontW API.
https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-createfontw
2024-07-14 04:14:18 +00:00
14020a23d5 Update version to 6.5.0 2024-07-12 13:27:10 +02:00
bb17f7bf84 update version for new release 2024-07-05 13:19:28 +02:00
b849e48ef4 Fixed wrong plugin options behaviour
While working on MR !230 I noticed that the options read I entered into several plugins could not be read after reading the image.

**The patch fixes problems reading options in plugins and adds option checking in the readtest.cpp.**

In particular, the reading test does the following additional actions:
- reads options before reading the image;
- compare the options read with the options returned by the reader after reading the image;
- compares the format and size of the returned image with the format and size returned by the reader.
2024-06-19 22:18:45 +00:00
81b7263d73 EXR: Full support for gray image/colorspace
Starting with Qt 6.8, QColorSpace supports Gray and CMYK color profiles.

- On saving, grayscale images are converted to linear gray profile;
- On loading, a Grayscale image is stored in a QImage::Format_Grayscale16 instead a RGB one;
- ScanlineConverter class was updated to gray conversions.
2024-06-17 15:17:51 +00:00
63e21ee5f3 Disable JXR plugin due to crashes in JXRLIB
I ran a simple fuzzer on all the plugins in the repo and the JXR one crashes every few seconds. I attach some files (I have many more) that cause the crashes. For the moment I think it's best to keep it deactivated.

The strange thing is that for the same plugin I had created the PR on oss-fuzz which ran locally for over an hour without problems. I'm a bit confused.
2024-06-13 22:24:19 +00:00
06f097046c PFM: Portable FloatMap read only support
Simple HDR format supported by GIMP (read/write) and Photoshop (read/write).
2024-06-13 22:20:40 +00:00
950ed43623 PXR: Pixar raster read only support
Limited read only support to Pixar raster as supported by Photoshop (RGB and Gray 8-bit only).
2024-06-12 22:42:08 +00:00
863c424390 Sanity checks (fuzzer)
I ran a stupid fuzzer on all the plugins in the repo and some plugins needs more sanity checks.

- RAS: fixed palette reading on corrupted files
- RGB: improved error detection on datastream

This patch improves the reading speed of some corrupted files and limit the maximum memory allocation of RAS palette.
2024-06-11 22:15:27 +00:00
bd083ff354 XCF: fixed wrong composite on Grayscale images
CCBUG: 476755
2024-06-10 20:16:31 +00:00
99663607b2 Fix compilation 2024-06-07 15:08:53 +00:00
7499e3b8d4 Use of metadata macro definitions
Replaced the metadata string with the common macro definition
2024-06-07 15:08:37 +00:00
cb5ca7fc48 JXR: added CMYK image to read test
Improved read test to skip unsupported image format: the CMYK image test only works if you compile with Qt 6.8 or higher.
2024-06-07 14:09:34 +00:00
4f61e3912c XCF: Increased maximum property size
CCBUG: 426222

The problem was caused by a check on the maximum size of properties (specifically it failed on PROP_PARASITES).
2024-06-07 11:43:31 +00:00
b8a9c75c80 JXR support
CCBUG: 451584

An implementation of the JXR format.
2024-06-07 10:35:25 +00:00
4995c9cd15 PSD: support native CMYK introduced by Qt 6.8
Qt 6.8 will introduce native support for the CMYK (8-bit) format.
With this patch you will finally be able to correctly see the colors of CMYK images with ICC profile.
The testing part has been updated with the addition of an (optional) json file for each image to test. Inside you enter which image to use depending on the Qt version.

In short:
- Added native CMYK suport to PSD reader
- CMYK with alpha is converted using QColorSpace in a RGBA image
- Read tests changed to use the correct comparison image based on the Qt version
- Fixed also XCF tests: now works with all Qt version (see also [QTBUG-120614](https://bugreports.qt.io/browse/QTBUG-120614))
- Work around for CCBUG: 468288
2024-06-07 10:16:58 +00:00
a54c5e876c update version for new release 2024-05-31 17:41:40 +02:00
6c1a7ad339 update version for new release 2024-05-31 15:15:35 +02:00
c721fa481b Remove explicit maintainer from metainfo
All frameworks are maintained by the KDE community
2024-05-12 21:27:40 +02:00
ea15fed399 update version for new release 2024-05-12 14:06:57 +02:00
c2fabef501 Ensure dependencies are provided on Android 2024-05-06 00:02:54 +12:00
5b2c190823 update version for new release 2024-05-03 11:49:56 +02:00
1b94554323 update version for new release 2024-04-12 16:12:29 +01:00
c2c12b1d7e Fix build with Qt 6.7 on 32 bits
Qt changed the argument type again in 0ed34d1992
2024-04-11 08:30:45 +02:00
c169296fbf update version for new release 2024-04-05 11:45:58 +01:00
29aec82e67 Add KF_VERSION & KF_DEP_VERSION variables
For consistency with the other KF modules, but also to have some version
info with working copies/checkouts, as well as min required KF deps.
2024-03-18 16:24:41 +01:00
95ee381195 XCF: testcase update for fixed Qt
Updated testcases to work with Qt without alpha [bug](https://bugreports.qt.io/browse/QTBUG-120614.).

Needs Qt >= 6.6.2 for tests to pass
2024-03-14 21:49:09 +00:00
8e5951471d TGA: added options support
- Added Size and Format options support
- Fixed a double image allocation when reading RGBA images (RGB was always allocated and then replaced by RGBA one)
- Fixed the code for sequential devices

The Size option is used by the KIO 5 thumbnailer to avoid to use too memory. A backport to KF5 would serve CCBUG: 413801 and CCBUG: 479612
2024-03-04 23:47:59 +00:00
0710bc65f6 More header checks (CCBUG: 479612) 2024-02-29 15:55:57 +01:00
4be09ba419 update version for new release 2024-02-21 11:15:16 +00:00
6cbdf9cf54 Workaround QTBUG-120614 2024-02-09 07:55:28 +00:00
7d6de20e8c update version for new release 2024-02-01 09:26:20 +00:00
b37c991e39 Port to ECMFeatureSummary
This avoids a feature summary in the middle of the cmake configuration
when this module is used as a git submodule.

GIT_SILENT
2024-01-26 14:17:50 +01:00
249046f25d update version for new release 2024-01-10 11:27:02 +00:00
9f7b1b8dee ScanlineConverter: fix indexed conversion and support for source depth less than 24 bits 2024-01-06 09:04:06 +01:00
f065104b72 Less space used when saving a grayscale image 2024-01-06 08:20:14 +01:00
f34185197a Fix build with Qt 6.7 2023-12-20 18:32:28 +01:00
9f24023ca7 The maximum number of channels explained better
Just a clarification on why the maximum value of channels present in the specifications is not used.
2023-12-20 16:43:12 +00:00
8d1ef536be Code is qt6 only now. Remove unused check 2023-12-16 11:16:53 +00:00
da8ed31aec avif: new quality settings
Prior libavif 1.0, quality was set using two numbers
(minQuantizer and maxQuantizer).
New versions of libavif recommend using
new/single quality parameter instead.
Compression using the new settings is different
compared to the old.
For example, new quality 68 gives similar compression
like old default 52.
2023-12-13 17:40:21 +01:00
ce8383e5fd HEIF plug-in extended to support HEJ2 format
HEJ2 is JPEG 2000 codec encapsulated in HEIF container.
Only HEJ2 reading is implemented.
2023-12-07 13:26:43 +01:00
131 changed files with 3952 additions and 383 deletions

12
.gitattributes vendored
View File

@ -1 +1,13 @@
autotests/read/raw/RAW_KODAK_C330_FORMAT_NONE_YRGB.raw binary
autotests/read/hdr/orientation1.hdr binary
autotests/read/hdr/orientation2.hdr binary
autotests/read/hdr/orientation3.hdr binary
autotests/read/hdr/orientation4.hdr binary
autotests/read/hdr/orientation5.hdr binary
autotests/read/hdr/orientation6.hdr binary
autotests/read/hdr/orientation7.hdr binary
autotests/read/hdr/orientation8.hdr binary
autotests/read/hdr/fake_earth.hdr binary
autotests/read/hdr/rgb.hdr binary
autotests/read/hdr/rgb-landscape.hdr binary
autotests/read/hdr/rgb-portrait.hdr binary

View File

@ -1,5 +1,5 @@
Dependencies:
- 'on': ['Linux', 'FreeBSD', 'macOS', 'Windows']
- 'on': ['Linux', 'FreeBSD', 'macOS', 'Windows', 'Android']
'require':
'frameworks/extra-cmake-modules': '@same'
'frameworks/karchive' : '@same'

View File

@ -1,9 +1,11 @@
cmake_minimum_required(VERSION 3.16)
project(KImageFormats)
set(KF_VERSION "6.5.0") # handled by release scripts
set(KF_DEP_VERSION "6.5.0") # handled by release scripts
project(KImageFormats VERSION ${KF_VERSION})
include(FeatureSummary)
find_package(ECM 5.240.0 NO_MODULE)
find_package(ECM 6.5.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)
@ -22,7 +24,7 @@ include(FindPkgConfig)
set(REQUIRED_QT_VERSION 6.5.0)
find_package(Qt6Gui ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE)
find_package(KF6Archive)
find_package(KF6Archive ${KF_DEP_VERSION})
set_package_properties(KF6Archive PROPERTIES
TYPE OPTIONAL
PURPOSE "Required for the QImage plugin for Krita and OpenRaster images"
@ -73,13 +75,18 @@ if(KIMAGEFORMATS_JXL)
endif()
add_feature_info(LibJXL LibJXL_FOUND "required for the QImage plugin for JPEG XL images")
# note: module FindLibRaw missing from https://invent.kde.org/frameworks/extra-cmake-modules
find_package(LibRaw 0.20.2)
set_package_properties(LibRaw PROPERTIES
TYPE OPTIONAL
PURPOSE "Required for the QImage plugin for RAW images"
)
option(KIMAGEFORMATS_JXR "Enable plugin for JPEG XR format" OFF)
if(KIMAGEFORMATS_JXR)
find_package(LibJXR)
endif()
add_feature_info(LibJXR LibJXR_FOUND "required for the QImage plugin for JPEG XR images")
ecm_set_disabled_deprecation_versions(
QT 6.5
KF 5.102
@ -91,6 +98,7 @@ if (BUILD_TESTING)
add_subdirectory(tests)
endif()
feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES)
include(ECMFeatureSummary)
ecm_feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES)
kde_configure_git_pre_commit_hook(CHECKS CLANG_FORMAT)

View File

@ -18,16 +18,19 @@ The following image formats have read-only support:
- Gimp (xcf)
- Krita (kra)
- OpenRaster (ora)
- Pixar raster (pxr)
- Portable FloatMap (pfm)
- 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)
- 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)
- JPEG XR (jxr). Can be enabled with the KIMAGEFORMATS_JXR build option.
- OpenEXR (exr)
- Personal Computer Exchange (pcx)
- Quite OK Image format (qoi)

View File

@ -11,7 +11,7 @@ macro(kimageformats_read_tests)
endif()
if (NOT TARGET readtest)
add_executable(readtest readtest.cpp)
add_executable(readtest readtest.cpp templateimage.cpp)
target_link_libraries(readtest Qt6::Gui)
target_compile_definitions(readtest
PRIVATE IMAGEDIR="${CMAKE_CURRENT_SOURCE_DIR}/read")
@ -66,7 +66,9 @@ endmacro()
kimageformats_read_tests(
hdr
pcx
pfm
psd
pxr
qoi
ras
rgb
@ -97,6 +99,12 @@ if (LibHeif_FOUND)
kimageformats_write_tests(FUZZ 1
heif-nodatacheck-lossless
)
if (LibHeif_VERSION VERSION_GREATER_EQUAL "1.17.0")
kimageformats_read_tests(FUZZ 1
hej2
)
endif()
endif()
if (LibJXL_FOUND AND LibJXLThreads_FOUND)
@ -108,6 +116,15 @@ if (LibJXL_FOUND AND LibJXLThreads_FOUND)
)
endif()
if (LibJXR_FOUND)
kimageformats_read_tests(
jxr
)
kimageformats_write_tests(
jxr-nodatacheck
)
endif()
# Allow some fuzziness when reading this formats, to allow for
# rounding errors (eg: in alpha blending).
kimageformats_read_tests(FUZZ 1

BIN
autotests/read/exr/gray.exr Normal file

Binary file not shown.

BIN
autotests/read/exr/gray.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,5 @@
[
{
"fileName" : "orientation_all.png"
}
]

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,5 @@
[
{
"fileName" : "orientation_all.png"
}
]

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,5 @@
[
{
"fileName" : "orientation_all.png"
}
]

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,5 @@
[
{
"fileName" : "orientation_all.png"
}
]

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,5 @@
[
{
"fileName" : "orientation_all.png"
}
]

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,5 @@
[
{
"fileName" : "orientation_all.png"
}
]

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,5 @@
[
{
"fileName" : "orientation_all.png"
}
]

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,5 @@
[
{
"fileName" : "orientation_all.png"
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 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: 16 KiB

Binary file not shown.

View File

@ -0,0 +1,19 @@
[
{
"minQtVersion" : "6.5.7",
"maxQtVersion" : "6.5.99",
"disableAutoTransform": true,
"fileName" : "orientation6_notranfs.png",
"comment" : "Test with automatic transformation disabled."
},
{
"minQtVersion" : "6.7.3",
"disableAutoTransform": true,
"fileName" : "orientation6_notranfs.png",
"comment" : "Test with automatic transformation disabled."
},
{
"unsupportedFormat" : true,
"comment" : "It is not possible to disable the transformation with the current version of the plugin."
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

View File

@ -0,0 +1,11 @@
[
{
"minQtVersion" : "6.8.0",
"fileName" : "testcard_cmyk8.tif"
},
{
"maxQtVersion" : "6.7.99",
"unsupportedFormat" : true,
"comment" : "Qt versions lower than 6.8 do not support CMYK format so this test should be skipped."
}
]

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 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: 13 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 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: 4.3 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 593 B

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

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

View File

@ -0,0 +1,11 @@
[
{
"minQtVersion" : "6.8.0",
"fileName" : "cmyk16_testcard_qt6_8.tif"
},
{
"minQtVersion" : "6.0.0",
"maxQtVersion" : "6.7.99",
"fileName" : "cmyk16_testcard.png"
}
]

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

View File

@ -0,0 +1,11 @@
[
{
"minQtVersion" : "6.8.0",
"fileName" : "cmyk8_testcard_qt6_8.tif"
},
{
"minQtVersion" : "6.0.0",
"maxQtVersion" : "6.7.99",
"fileName" : "cmyk8_testcard.png"
}
]

Binary file not shown.

View File

@ -0,0 +1,11 @@
[
{
"minQtVersion" : "6.8.0",
"fileName" : "cmyka-16bits_qt6_8.png"
},
{
"minQtVersion" : "6.0.0",
"maxQtVersion" : "6.7.99",
"fileName" : "cmyka-16bits.png"
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

View File

@ -0,0 +1,11 @@
[
{
"minQtVersion" : "6.8.0",
"fileName" : "cmyka-8bits_qt6_8.png"
},
{
"minQtVersion" : "6.0.0",
"maxQtVersion" : "6.7.99",
"fileName" : "cmyka-8bits.png"
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

View File

@ -0,0 +1,11 @@
[
{
"minQtVersion" : "6.8.0",
"fileName" : "mch-16bits_qt_6_8.tif"
},
{
"minQtVersion" : "6.0.0",
"maxQtVersion" : "6.7.99",
"fileName" : "mch-16bits.png"
}
]

Binary file not shown.

View File

@ -0,0 +1,11 @@
[
{
"minQtVersion" : "6.8.0",
"fileName" : "mch-8bits_qt_6.8.tif"
},
{
"minQtVersion" : "6.0.0",
"maxQtVersion" : "6.7.99",
"fileName" : "mch-8bits.png"
}
]

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

After

Width:  |  Height:  |  Size: 93 KiB

View File

@ -0,0 +1,32 @@
[
{
"minQtVersion" : "6.7.0",
"fileName" : "birthday16.png",
"seeAlso" : "https://bugreports.qt.io/browse/QTBUG-120614"
},
{
"minQtVersion" : "6.0.0",
"maxQtVersion" : "6.2.10",
"fileName" : "birthday16_alphabug.png"
},
{
"minQtVersion" : "6.3.0",
"maxQtVersion" : "6.3.2",
"fileName" : "birthday32_alphabug.png"
},
{
"minQtVersion" : "6.4.0",
"maxQtVersion" : "6.4.3",
"fileName" : "birthday32_alphabug.png"
},
{
"minQtVersion" : "6.5.0",
"maxQtVersion" : "6.5.4",
"fileName" : "birthday16_alphabug.png"
},
{
"minQtVersion" : "6.6.0",
"maxQtVersion" : "6.6.1",
"fileName" : "birthday16_alphabug.png"
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

After

Width:  |  Height:  |  Size: 93 KiB

View File

@ -0,0 +1,32 @@
[
{
"minQtVersion" : "6.7.0",
"fileName" : "birthday32.png",
"seeAlso" : "https://bugreports.qt.io/browse/QTBUG-120614"
},
{
"minQtVersion" : "6.0.0",
"maxQtVersion" : "6.2.10",
"fileName" : "birthday32_alphabug.png"
},
{
"minQtVersion" : "6.3.0",
"maxQtVersion" : "6.3.2",
"fileName" : "birthday32_alphabug.png"
},
{
"minQtVersion" : "6.4.0",
"maxQtVersion" : "6.4.3",
"fileName" : "birthday32_alphabug.png"
},
{
"minQtVersion" : "6.5.0",
"maxQtVersion" : "6.5.4",
"fileName" : "birthday32_alphabug.png"
},
{
"minQtVersion" : "6.6.0",
"maxQtVersion" : "6.6.1",
"fileName" : "birthday32_alphabug.png"
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

View File

@ -16,6 +16,7 @@
#include <QTextStream>
#include "../tests/format-enum.h"
#include "templateimage.h"
#include "fuzzyeq.cpp"
@ -89,13 +90,115 @@ static QImage::Format preferredFormat(QImage::Format fmt)
}
}
/*!
* \brief The OptionTest class
* Class for testing image options.
* Supports the most common options:
* - Size
* - ImageFormat
* - ImageTransformation (rotations)
* \todo Add missing options if needed.
*/
class OptionTest
{
public:
OptionTest()
: m_size(QSize())
, m_format(QImage::Format_Invalid)
, m_transformations(QImageIOHandler::TransformationNone)
{
}
OptionTest(const OptionTest&) = default;
OptionTest& operator =(const OptionTest&) = default;
/*!
* \brief store
* Stores the supported options of the reader.
* \param reader
* \return True on success, otherwise false.
*/
bool store(const QImageReader *reader = nullptr)
{
if (reader == nullptr) {
return false;
}
bool ok = true;
if (reader->supportsOption(QImageIOHandler::Size)) {
m_size = reader->size();
if (m_size.isEmpty())
ok = false;
}
if (reader->supportsOption(QImageIOHandler::ImageFormat)) {
m_format = reader->imageFormat();
if (m_format == QImage::Format_Invalid)
ok = false;
}
if (reader->supportsOption(QImageIOHandler::ImageTransformation)) {
m_transformations = reader->transformation();
if (m_transformations < 0 || m_transformations > 7)
ok = false;
}
return ok;
}
/*!
* \brief compare
* Compare the stored values with the ones read from the image reader.
* \param reader
* \return True on success, otherwise false.
*/
bool compare(const QImageReader *reader)
{
if (reader == nullptr) {
return false;
}
bool ok = true;
if (reader->supportsOption(QImageIOHandler::Size)) {
ok = ok && (m_size == reader->size());
}
if (reader->supportsOption(QImageIOHandler::ImageFormat)) {
ok = ok && (m_format == reader->imageFormat());
}
if (reader->supportsOption(QImageIOHandler::ImageTransformation)) {
ok = ok && (m_transformations == reader->transformation());
}
return ok;
}
/*!
* \brief compare
* Compare the image properties with the ones stored.
* \param image
* \return True on success, otherwise false.
*/
bool compare(const QImage& image)
{
bool ok = true;
if (!m_size.isEmpty()) {
// Size option return the size without transformation (tested with Qt TIFF plugin).
ok = ok && (m_size == image.size() || m_size == image.size().transposed());
}
if (m_format != QImage::Format_Invalid) {
ok = ok && (m_format == image.format());
}
return ok;
}
private:
QSize m_size;
QImage::Format m_format;
QImageIOHandler::Transformations m_transformations;
};
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.1.0"));
QCoreApplication::setApplicationVersion(QStringLiteral("1.2.0"));
QCommandLineParser parser;
parser.setApplicationDescription(QStringLiteral("Performs basic image conversion checking."));
@ -159,28 +262,39 @@ int main(int argc, char **argv)
QTextStream(stdout) << "* Run on RANDOM ACCESS device\n";
}
for (const QFileInfo &fi : lstImgDir) {
if (!fi.suffix().compare("png", Qt::CaseInsensitive) || !fi.suffix().compare("tif", Qt::CaseInsensitive)) {
TemplateImage timg(fi);
if (timg.isTemplate()) {
continue;
}
int suffixPos = fi.filePath().size() - suffix.size();
QString inputfile = fi.filePath();
QString fmt = QStringLiteral("png");
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.size(), fmt);
}
QString expfilename = QFileInfo(expfile).fileName();
std::unique_ptr<QIODevice> inputDevice(seq ? new SequentialFile(inputfile) : new QFile(inputfile));
TemplateImage::TestFlags flags = TemplateImage::None;
QString comment;
QFileInfo expFileInfo = timg.compareImage(flags, comment);
if ((flags & TemplateImage::SkipTest) == TemplateImage::SkipTest) {
if(comment.isEmpty())
comment = QStringLiteral("image format not supported by current Qt version!");
QTextStream(stdout) << "SKIP : " << fi.fileName() << QStringLiteral(": %1\n").arg(comment);
++skipped;
continue;
}
if (!formatStrings.contains(expFileInfo.suffix(), Qt::CaseInsensitive)) {
// Work Around for CCBUG: 468288
QTextStream(stdout) << "SKIP : " << fi.fileName() << ": comparison image " << expFileInfo.fileName() << " cannot be loaded due to the lack of "
<< expFileInfo.suffix().toUpper() << " plugin!\n";
++skipped;
continue;
}
QString expfilename = expFileInfo.fileName();
std::unique_ptr<QIODevice> inputDevice(seq ? new SequentialFile(fi.filePath()) : new QFile(fi.filePath()));
QImageReader inputReader(inputDevice.get(), format);
QImageReader expReader(expfile, fmt.toLatin1());
QImageReader expReader(expFileInfo.filePath());
QImage inputImage;
QImage expImage;
// inputImage is auto-rotated to final orientation
inputReader.setAutoTransform(true);
inputReader.setAutoTransform((flags & TemplateImage::DisableAutotransform) != TemplateImage::DisableAutotransform);
if (!expReader.read(&expImage)) {
QTextStream(stdout) << "ERROR: " << fi.fileName() << ": could not load " << expfilename << ": " << expReader.errorString() << "\n";
@ -199,11 +313,32 @@ int main(int argc, char **argv)
}
continue;
}
OptionTest optionTest;
if (!optionTest.store(&inputReader)) {
QTextStream(stdout) << "FAIL : " << fi.fileName() << ": error while reading options\n";
++failed;
continue;
}
if (!inputReader.read(&inputImage)) {
QTextStream(stdout) << "FAIL : " << fi.fileName() << ": failed to load: " << inputReader.errorString() << "\n";
++failed;
continue;
}
if (!optionTest.compare(&inputReader)) {
QTextStream(stdout) << "FAIL : " << fi.fileName() << ": error while comparing options\n";
++failed;
continue;
}
if (!optionTest.compare(inputImage)) {
QTextStream(stdout) << "FAIL : " << fi.fileName() << ": error while comparing the image properties with options\n";
++failed;
continue;
}
if (expImage.width() != inputImage.width()) {
QTextStream(stdout) << "FAIL : " << fi.fileName() << ": width was " << inputImage.width() << " but " << expfilename << " width was "
<< expImage.width() << "\n";

111
autotests/templateimage.cpp Normal file
View File

@ -0,0 +1,111 @@
/*
SPDX-FileCopyrightText: 2024 Mirco Miranda <mircomir@outlook.com>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "templateimage.h"
#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QVersionNumber>
TemplateImage::TemplateImage(const QFileInfo &fi) :
m_fi(fi)
{
}
bool TemplateImage::isTemplate() const
{
auto list = suffixes();
for (auto&& suffix : list) {
if (!m_fi.suffix().compare(suffix, Qt::CaseInsensitive))
return true;
}
return false;
}
QFileInfo TemplateImage::compareImage(TestFlags &flags, QString& comment) const
{
auto fi = jsonImage(flags, comment);
if ((flags & TestFlag::SkipTest) == TestFlag::SkipTest) {
return {};
}
if (fi.exists()) {
return fi;
}
return legacyImage();
}
QStringList TemplateImage::suffixes()
{
return QStringList({"png", "tif", "tiff", "json"});
}
QFileInfo TemplateImage::legacyImage() const
{
auto list = suffixes();
for (auto&& suffix : list) {
auto fi = QFileInfo(QStringLiteral("%1/%2.%3").arg(m_fi.path(), m_fi.completeBaseName(), suffix));
if (fi.exists()) {
return fi;
}
}
return {};
}
QFileInfo TemplateImage::jsonImage(TestFlags &flags, QString& comment) const
{
flags = TestFlag::None;
auto fi = QFileInfo(QStringLiteral("%1.json").arg(m_fi.filePath()));
if (!fi.exists()) {
return {};
}
QFile f(fi.filePath());
if (!f.open(QFile::ReadOnly)) {
return {};
}
QJsonParseError err;
auto doc = QJsonDocument::fromJson(f.readAll(), &err);
if (err.error != QJsonParseError::NoError || !doc.isArray()) {
return {};
}
auto currentQt = QVersionNumber::fromString(qVersion());
auto arr = doc.array();
for (auto val : arr) {
if (!val.isObject())
continue;
auto obj = val.toObject();
auto minQt = QVersionNumber::fromString(obj.value("minQtVersion").toString());
auto maxQt = QVersionNumber::fromString(obj.value("maxQtVersion").toString());
auto name = obj.value("fileName").toString();
auto unsupportedFormat = obj.value("unsupportedFormat").toBool();
comment = obj.value("comment").toString();
if(obj.value("disableAutoTransform").toBool())
flags |= TestFlag::DisableAutotransform;
// filter
if (name.isEmpty() && !unsupportedFormat)
continue;
if (!minQt.isNull() && currentQt < minQt)
continue;
if (!maxQt.isNull() && currentQt > maxQt)
continue;
if (unsupportedFormat) {
flags |= TestFlag::SkipTest;
break;
}
return QFileInfo(QStringLiteral("%1/%2").arg(fi.path(), name));
}
return {};
}

83
autotests/templateimage.h Normal file
View File

@ -0,0 +1,83 @@
/*
SPDX-FileCopyrightText: 2024 Mirco Miranda <mircomir@outlook.com>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#ifndef TEMPLATEIMAGE_H
#define TEMPLATEIMAGE_H
#include <QFileInfo>
/*!
* \brief The TemplateImage class
* Given an image name, it decides the template image to compare it with.
*/
class TemplateImage
{
public:
enum TestFlag {
None = 0x0,
SkipTest = 0x1,
DisableAutotransform = 0x2
};
Q_DECLARE_FLAGS(TestFlags, TestFlag)
/*!
* \brief TemplateImage
* \param fi The image to test.
*/
TemplateImage(const QFileInfo& fi);
/*!
* \brief TemplateImage
* Default copy constructor.
*/
TemplateImage(const TemplateImage& other) = default;
/*!
* \brief operator =
* Default copy operator
*/
TemplateImage& operator=(const TemplateImage& other) = default;
/*!
* \brief isTemplate
* \return True if the image is a template, false otherwise.
* \sa suffixes
*/
bool isTemplate() const;
/*!
* \brief compareImage
* \param flags Flags for modifying test behavior (e.g. image format not supported by current Qt version).
* \return The template image to use for the comparison.
*/
QFileInfo compareImage(TestFlags &flags, QString& comment) const;
/*!
* \brief suffixes
* \return The list of suffixes considered templates.
*/
static QStringList suffixes();
private:
/*!
* \brief legacyImage
* \return The template image calculated from the source image name.
*/
QFileInfo legacyImage() const;
/*!
* \brief jsonImage
* \param flags Flags for modifying test behavior.
* \return The template image read from the corresponding JSON.
*/
QFileInfo jsonImage(TestFlags &flags, QString& comment) const;
private:
QFileInfo m_fi;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(TemplateImage::TestFlags)
#endif // TEMPLATEIMAGE_H

View File

@ -0,0 +1,24 @@
# - Find LibJXR
# Find the JXR library
# This module defines
# LIBJXR_INCLUDE_DIRS, where to find jxrlib/JXRGlue.h
# LIBJXR_LIBRARIES, the libraries needed to use JXR
#
# Based on cmake code found at https://github.com/microsoft/vcpkg/blob/master/ports/jxrlib/FindJXR.cmake
find_path(LIBJXR_INCLUDE_DIRS
NAMES JXRGlue.h
PATH_SUFFIXES jxrlib
)
mark_as_advanced(LIBJXR_INCLUDE_DIRS)
include(SelectLibraryConfigurations)
find_library(LIBJPEGXR_LIBRARY NAMES jpegxr)
find_library(LIBJXRGLUE_LIBRARY NAMES jxrglue)
set(LIBJXR_LIBRARIES ${LIBJPEGXR_LIBRARY} ${LIBJXRGLUE_LIBRARY})
mark_as_advanced(LIBJXR_LIBRARIES)
include(FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(LibJXR DEFAULT_MSG LIBJXR_INCLUDE_DIRS LIBJXR_LIBRARIES)

View File

@ -1,4 +1,3 @@
maintainer: alexmerry
description: Image format plugins for Qt
tier: 2
type: functional

View File

@ -83,7 +83,15 @@ kimageformats_add_plugin(kimg_pic SOURCES pic.cpp)
##################################
kimageformats_add_plugin(kimg_psd SOURCES psd.cpp)
kimageformats_add_plugin(kimg_pfm SOURCES pfm.cpp)
##################################
kimageformats_add_plugin(kimg_psd SOURCES psd.cpp scanlineconverter.cpp)
##################################
kimageformats_add_plugin(kimg_pxr SOURCES pxr.cpp)
##################################
@ -115,6 +123,20 @@ endif()
##################################
if (LibJXR_FOUND)
kimageformats_add_plugin(kimg_jxr SOURCES jxr.cpp)
kde_enable_exceptions()
target_include_directories(kimg_jxr PRIVATE ${LIBJXR_INCLUDE_DIRS})
target_link_libraries(kimg_jxr PRIVATE jpegxr jxrglue)
target_compile_definitions(kimg_jxr PRIVATE INITGUID)
if (NOT CMAKE_SYSTEM_NAME STREQUAL "Windows")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-error=undef")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-error=undef")
endif()
endif()
##################################
if (KF6Archive_FOUND)
kimageformats_add_plugin(kimg_kra SOURCES kra.cpp)

View File

@ -16,9 +16,34 @@
#include <cfloat>
/*
Quality range - compression/subsampling
100 - lossless RGB compression
< KIMG_AVIF_QUALITY_BEST, 100 ) - YUV444 color subsampling
< KIMG_AVIF_QUALITY_HIGH, KIMG_AVIF_QUALITY_BEST ) - YUV422 color subsampling
< 0, KIMG_AVIF_QUALITY_HIGH ) - YUV420 color subsampling
< 0, KIMG_AVIF_QUALITY_LOW ) - lossy compression of alpha channel
*/
#ifndef KIMG_AVIF_DEFAULT_QUALITY
#define KIMG_AVIF_DEFAULT_QUALITY 68
#endif
#ifndef KIMG_AVIF_QUALITY_BEST
#define KIMG_AVIF_QUALITY_BEST 90
#endif
#ifndef KIMG_AVIF_QUALITY_HIGH
#define KIMG_AVIF_QUALITY_HIGH 80
#endif
#ifndef KIMG_AVIF_QUALITY_LOW
#define KIMG_AVIF_QUALITY_LOW 51
#endif
QAVIFHandler::QAVIFHandler()
: m_parseState(ParseAvifNotParsed)
, m_quality(52)
, m_quality(KIMG_AVIF_DEFAULT_QUALITY)
, m_container_width(0)
, m_container_height(0)
, m_rawAvifData(AVIF_DATA_EMPTY)
@ -519,9 +544,17 @@ bool QAVIFHandler::write(const QImage &image)
}
}
if (m_quality > 100) {
m_quality = 100;
} else if (m_quality < 0) {
m_quality = KIMG_AVIF_DEFAULT_QUALITY;
}
#if AVIF_VERSION < 1000000
int maxQuantizer = AVIF_QUANTIZER_WORST_QUALITY * (100 - qBound(0, m_quality, 100)) / 100;
int minQuantizer = 0;
int maxQuantizerAlpha = 0;
#endif
avifResult res;
bool save_grayscale; // true - monochrome, false - colors
@ -567,13 +600,15 @@ bool QAVIFHandler::write(const QImage &image)
break;
}
// quality settings
#if AVIF_VERSION < 1000000
// deprecated quality settings
if (maxQuantizer > 20) {
minQuantizer = maxQuantizer - 20;
if (maxQuantizer > 40) { // we decrease quality of alpha channel here
maxQuantizerAlpha = maxQuantizer - 40;
}
}
#endif
if (save_grayscale && !image.hasAlphaChannel()) { // we are going to save grayscale image without alpha channel
if (save_depth > 8) {
@ -584,7 +619,15 @@ bool QAVIFHandler::write(const QImage &image)
QImage tmpgrayimage = image.convertToFormat(tmpformat);
avif = avifImageCreate(tmpgrayimage.width(), tmpgrayimage.height(), save_depth, AVIF_PIXEL_FORMAT_YUV400);
#if AVIF_VERSION >= 110000
res = avifImageAllocatePlanes(avif, AVIF_PLANES_YUV);
if (res != AVIF_RESULT_OK) {
qWarning("ERROR in avifImageAllocatePlanes: %s", avifResultToString(res));
return false;
}
#else
avifImageAllocatePlanes(avif, AVIF_PLANES_YUV);
#endif
if (tmpgrayimage.colorSpace().isValid()) {
avif->colorPrimaries = (avifColorPrimaries)1;
@ -646,8 +689,8 @@ bool QAVIFHandler::write(const QImage &image)
QImage tmpcolorimage = image.convertToFormat(tmpformat);
avifPixelFormat pixel_format = AVIF_PIXEL_FORMAT_YUV420;
if (maxQuantizer < 20) {
if (maxQuantizer < 10) {
if (m_quality >= KIMG_AVIF_QUALITY_HIGH) {
if (m_quality >= KIMG_AVIF_QUALITY_BEST) {
pixel_format = AVIF_PIXEL_FORMAT_YUV444; // best quality
} else {
pixel_format = AVIF_PIXEL_FORMAT_YUV422; // high quality
@ -771,7 +814,15 @@ bool QAVIFHandler::write(const QImage &image)
avif->transferCharacteristics = transfer_to_save;
if (iccprofile.size() > 0) {
#if AVIF_VERSION >= 1000000
res = avifImageSetProfileICC(avif, reinterpret_cast<const uint8_t *>(iccprofile.constData()), iccprofile.size());
if (res != AVIF_RESULT_OK) {
qWarning("ERROR in avifImageSetProfileICC: %s", avifResultToString(res));
return false;
}
#else
avifImageSetProfileICC(avif, reinterpret_cast<const uint8_t *>(iccprofile.constData()), iccprofile.size());
#endif
}
avifRGBImage rgb;
@ -807,6 +858,8 @@ bool QAVIFHandler::write(const QImage &image)
avifRWData raw = AVIF_DATA_EMPTY;
avifEncoder *encoder = avifEncoderCreate();
encoder->maxThreads = qBound(1, QThread::idealThreadCount(), 64);
#if AVIF_VERSION < 1000000
encoder->minQuantizer = minQuantizer;
encoder->maxQuantizer = maxQuantizer;
@ -814,6 +867,17 @@ bool QAVIFHandler::write(const QImage &image)
encoder->minQuantizerAlpha = AVIF_QUANTIZER_LOSSLESS;
encoder->maxQuantizerAlpha = maxQuantizerAlpha;
}
#else
encoder->quality = m_quality;
if (image.hasAlphaChannel()) {
if (m_quality >= KIMG_AVIF_QUALITY_LOW) {
encoder->qualityAlpha = 100;
} else {
encoder->qualityAlpha = 100 - (KIMG_AVIF_QUALITY_LOW - m_quality) / 2;
}
}
#endif
encoder->speed = 6;
@ -870,7 +934,7 @@ void QAVIFHandler::setOption(ImageOption option, const QVariant &value)
if (m_quality > 100) {
m_quality = 100;
} else if (m_quality < 0) {
m_quality = 52;
m_quality = KIMG_AVIF_DEFAULT_QUALITY;
}
return;
default:
@ -923,6 +987,8 @@ bool QAVIFHandler::jumpToNextImage()
return false;
}
avifResult decodeResult;
if (m_decoder->imageIndex >= 0) {
if (m_decoder->imageCount < 2) {
m_parseState = ParseAvifSuccess;
@ -930,11 +996,16 @@ bool QAVIFHandler::jumpToNextImage()
}
if (m_decoder->imageIndex >= m_decoder->imageCount - 1) { // start from beginning
avifDecoderReset(m_decoder);
decodeResult = avifDecoderReset(m_decoder);
if (decodeResult != AVIF_RESULT_OK) {
qWarning("ERROR in avifDecoderReset: %s", avifResultToString(decodeResult));
m_parseState = ParseAvifError;
return false;
}
}
}
avifResult decodeResult = avifDecoderNextImage(m_decoder);
decodeResult = avifDecoderNextImage(m_decoder);
if (decodeResult != AVIF_RESULT_OK) {
qWarning("ERROR: Failed to decode Next image in sequence: %s", avifResultToString(decodeResult));

View File

@ -7,20 +7,11 @@
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
@ -92,13 +83,6 @@
#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
{
public:
@ -209,22 +193,6 @@ void K_OStream::seekg(Imf::Int64 pos)
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)
@ -248,13 +216,7 @@ bool EXRHandler::canRead() const
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
}
/*!
@ -290,23 +252,23 @@ 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()));
image.setText(QStringLiteral(META_KEY_COMMENT), QString::fromStdString(comments->value()));
}
if (auto owner = header.findTypedAttribute<Imf::StringAttribute>("owner")) {
image.setText(QStringLiteral("Owner"), QString::fromStdString(owner->value()));
image.setText(QStringLiteral(META_KEY_OWNER), QString::fromStdString(owner->value()));
}
if (auto lat = header.findTypedAttribute<Imf::FloatAttribute>("latitude")) {
image.setText(QStringLiteral("Latitude"), QLocale::c().toString(lat->value()));
image.setText(QStringLiteral(META_KEY_LATITUDE), QLocale::c().toString(lat->value()));
}
if (auto lon = header.findTypedAttribute<Imf::FloatAttribute>("longitude")) {
image.setText(QStringLiteral("Longitude"), QLocale::c().toString(lon->value()));
image.setText(QStringLiteral(META_KEY_LONGITUDE), QLocale::c().toString(lon->value()));
}
if (auto alt = header.findTypedAttribute<Imf::FloatAttribute>("altitude")) {
image.setText(QStringLiteral("Altitude"), QLocale::c().toString(alt->value()));
image.setText(QStringLiteral(META_KEY_ALTITUDE), QLocale::c().toString(alt->value()));
}
if (auto capDate = header.findTypedAttribute<Imf::StringAttribute>("capDate")) {
@ -317,7 +279,7 @@ static void readMetadata(const Imf::Header &header, QImage &image)
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));
image.setText(QStringLiteral(META_KEY_CREATIONDATE), dateTime.toString(Qt::ISODate));
}
}
@ -332,26 +294,30 @@ static void readMetadata(const Imf::Header &header, QImage &image)
// Non-standard attribute
if (auto xmp = header.findTypedAttribute<Imf::StringAttribute>("xmp")) {
image.setText(QStringLiteral("XML:com.adobe.xmp"), QString::fromStdString(xmp->value()));
image.setText(QStringLiteral(META_KEY_XMP_ADOBE), 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')
*/
// camera metadata
if (auto manufacturer = header.findTypedAttribute<Imf::StringAttribute>("cameraMake")) {
image.setText(QStringLiteral(META_KEY_MANUFACTURER), QString::fromStdString(manufacturer->value()));
}
if (auto model = header.findTypedAttribute<Imf::StringAttribute>("cameraModel")) {
image.setText(QStringLiteral(META_KEY_MODEL), QString::fromStdString(model->value()));
}
if (auto serial = header.findTypedAttribute<Imf::StringAttribute>("cameraSerialNumber")) {
image.setText(QStringLiteral(META_KEY_SERIALNUMBER), QString::fromStdString(serial->value()));
}
// lens metadata
if (auto manufacturer = header.findTypedAttribute<Imf::StringAttribute>("lensMake")) {
image.setText(QStringLiteral(META_KEY_LENS_MANUFACTURER), QString::fromStdString(manufacturer->value()));
}
if (auto model = header.findTypedAttribute<Imf::StringAttribute>("lensModel")) {
image.setText(QStringLiteral(META_KEY_LENS_MODEL), QString::fromStdString(model->value()));
}
if (auto serial = header.findTypedAttribute<Imf::StringAttribute>("lensSerialNumber")) {
image.setText(QStringLiteral(META_KEY_LENS_SERIALNUMBER), QString::fromStdString(serial->value()));
}
}
/*!
@ -361,8 +327,6 @@ static void readMetadata(const Imf::Header &header, QImage &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();
@ -380,8 +344,6 @@ static void readColorSpace(const Imf::Header &header, QImage &image)
#ifdef EXR_CONVERT_TO_SRGB
image.convertToColorSpace(QColorSpace(QColorSpace::SRgb));
#endif
#endif // !EXR_USE_LEGACY_CONVERSIONS
}
bool EXRHandler::read(QImage *outImage)
@ -432,7 +394,6 @@ bool EXRHandler::read(QImage *outImage)
pixels.resizeErase(EXR_LINES_PER_BLOCK, width);
bool isRgba = image.hasAlphaChannel();
// somehow copy pixels into image
for (int y = 0, n = 0; y < height; y += n) {
auto my = dw.min.y + y;
if (my > dw.max.y) { // paranoia check
@ -443,30 +404,14 @@ bool EXRHandler::read(QImage *outImage)
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);
*(scanLine + xcs) = qfloat16(float(pixels[n][x].r));
*(scanLine + xcs + 1) = qfloat16(float(pixels[n][x].g));
*(scanLine + xcs + 2) = qfloat16(float(pixels[n][x].b));
*(scanLine + xcs + 3) = qfloat16(isRgba ? std::clamp(float(pixels[n][x].a), 0.f, 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
}
}
@ -530,18 +475,18 @@ 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)) {
if (!key.compare(QStringLiteral(META_KEY_COMMENT), Qt::CaseInsensitive)) {
header.insert("comments", Imf::StringAttribute(text.toStdString()));
}
if (!key.compare(QStringLiteral("Owner"), Qt::CaseInsensitive)) {
if (!key.compare(QStringLiteral(META_KEY_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)) {
if (!key.compare(QStringLiteral(META_KEY_LATITUDE), Qt::CaseInsensitive) ||
!key.compare(QStringLiteral(META_KEY_LONGITUDE), Qt::CaseInsensitive) ||
!key.compare(QStringLiteral(META_KEY_ALTITUDE), Qt::CaseInsensitive)) {
// clang-format on
auto ok = false;
auto value = QLocale::c().toFloat(text, &ok);
@ -550,7 +495,7 @@ static void setMetadata(const QImage &image, Imf::Header &header)
}
}
if (!key.compare(QStringLiteral("CreationDate"), Qt::CaseInsensitive)) {
if (!key.compare(QStringLiteral(META_KEY_CREATIONDATE), Qt::CaseInsensitive)) {
auto dt = QDateTime::fromString(text, Qt::ISODate);
if (dt.isValid()) {
dateTime = dt;
@ -558,10 +503,30 @@ static void setMetadata(const QImage &image, Imf::Header &header)
}
#ifndef EXR_DISABLE_XMP_ATTRIBUTE // warning: Non-standard attribute!
if (!key.compare(QStringLiteral("XML:com.adobe.xmp"), Qt::CaseInsensitive)) {
if (!key.compare(QStringLiteral(META_KEY_XMP_ADOBE), Qt::CaseInsensitive)) {
header.insert("xmp", Imf::StringAttribute(text.toStdString()));
}
#endif
if (!key.compare(QStringLiteral(META_KEY_MANUFACTURER), Qt::CaseInsensitive)) {
header.insert("cameraMake", Imf::StringAttribute(text.toStdString()));
}
if (!key.compare(QStringLiteral(META_KEY_MODEL), Qt::CaseInsensitive)) {
header.insert("cameraModel", Imf::StringAttribute(text.toStdString()));
}
if (!key.compare(QStringLiteral(META_KEY_SERIALNUMBER), Qt::CaseInsensitive)) {
header.insert("cameraSerialNumber", Imf::StringAttribute(text.toStdString()));
}
if (!key.compare(QStringLiteral(META_KEY_LENS_MANUFACTURER), Qt::CaseInsensitive)) {
header.insert("lensMake", Imf::StringAttribute(text.toStdString()));
}
if (!key.compare(QStringLiteral(META_KEY_LENS_MODEL), Qt::CaseInsensitive)) {
header.insert("lensModel", Imf::StringAttribute(text.toStdString()));
}
if (!key.compare(QStringLiteral(META_KEY_LENS_SERIALNUMBER), Qt::CaseInsensitive)) {
header.insert("lensSerialNumber", Imf::StringAttribute(text.toStdString()));
}
}
if (dateTime.isValid()) {
header.insert("capDate", Imf::StringAttribute(dateTime.toString(QStringLiteral("yyyy:MM:dd HH:mm:ss")).toStdString()));
@ -578,8 +543,6 @@ static void setMetadata(const QImage &image, Imf::Header &header)
// 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)
@ -621,22 +584,24 @@ bool EXRHandler::write(const QImage &image)
// write the EXR
K_OStream ostr(device(), QByteArray());
Imf::RgbaOutputFile file(ostr, header, image.hasAlphaChannel() ? Imf::RgbaChannels::WRITE_RGBA : Imf::RgbaChannels::WRITE_RGB);
auto channelsType = image.hasAlphaChannel() ? Imf::RgbaChannels::WRITE_RGBA : Imf::RgbaChannels::WRITE_RGB;
if (image.format() == QImage::Format_Mono ||
image.format() == QImage::Format_MonoLSB ||
image.format() == QImage::Format_Grayscale16 ||
image.format() == QImage::Format_Grayscale8) {
channelsType = Imf::RgbaChannels::WRITE_Y;
}
Imf::RgbaOutputFile file(ostr, header, channelsType);
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;
@ -648,18 +613,6 @@ bool EXRHandler::write(const QImage &image)
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);
@ -692,10 +645,12 @@ void EXRHandler::setOption(ImageOption option, const QVariant &value)
bool EXRHandler::supportsOption(ImageOption option) const
{
if (option == QImageIOHandler::Size) {
return true;
if (auto d = device())
return !d->isSequential();
}
if (option == QImageIOHandler::ImageFormat) {
return true;
if (auto d = device())
return !d->isSequential();
}
if (option == QImageIOHandler::CompressionRatio) {
return true;
@ -714,6 +669,9 @@ QVariant EXRHandler::option(ImageOption option) const
if (auto d = device()) {
// transactions works on both random and sequential devices
d->startTransaction();
if (m_startPos > -1) {
d->seek(m_startPos);
}
try {
K_IStream istr(d, QByteArray());
Imf::RgbaInputFile file(istr);
@ -736,6 +694,9 @@ QVariant EXRHandler::option(ImageOption option) const
if (auto d = device()) {
// transactions works on both random and sequential devices
d->startTransaction();
if (m_startPos > -1) {
d->seek(m_startPos);
}
try {
K_IStream istr(d, QByteArray());
Imf::RgbaInputFile file(istr);

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