Compare commits
49 Commits
work/fuf/r
...
work/aacid
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f728b87ae8 | ||
|
|
bb17f7bf84 | ||
|
|
b849e48ef4 | ||
|
|
81b7263d73 | ||
|
|
63e21ee5f3 | ||
|
|
06f097046c | ||
|
|
950ed43623 | ||
|
|
863c424390 | ||
|
|
bd083ff354 | ||
|
|
99663607b2 | ||
|
|
7499e3b8d4 | ||
|
|
cb5ca7fc48 | ||
|
|
4f61e3912c | ||
|
|
b8a9c75c80 | ||
|
|
4995c9cd15 | ||
|
|
a54c5e876c | ||
|
|
6c1a7ad339 | ||
|
|
c721fa481b | ||
|
|
ea15fed399 | ||
|
|
c2fabef501 | ||
|
|
5b2c190823 | ||
|
|
1b94554323 | ||
|
|
c2c12b1d7e | ||
|
|
c169296fbf | ||
|
|
29aec82e67 | ||
|
|
95ee381195 | ||
|
|
8e5951471d | ||
|
|
0710bc65f6 | ||
|
|
4be09ba419 | ||
|
|
6cbdf9cf54 | ||
|
|
7d6de20e8c | ||
|
|
b37c991e39 | ||
|
|
249046f25d | ||
|
|
9f7b1b8dee | ||
|
|
f065104b72 | ||
|
|
f34185197a | ||
|
|
9f24023ca7 | ||
|
|
8d1ef536be | ||
|
|
da8ed31aec | ||
|
|
ce8383e5fd | ||
|
|
db0adee62f | ||
|
|
91a342e90d | ||
|
|
7864ad4bc6 | ||
|
|
524711f633 | ||
|
|
b8b980f400 | ||
|
|
d932e0d16b | ||
|
|
18ea0492bc | ||
|
|
7899c27a80 | ||
|
|
6a51407556 |
12
.gitattributes
vendored
@@ -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
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
include:
|
||||
- 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/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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
project(KImageFormats)
|
||||
set(KF_VERSION "6.4.0") # handled by release scripts
|
||||
set(KF_DEP_VERSION "6.4.0") # handled by release scripts
|
||||
project(KImageFormats VERSION ${KF_VERSION})
|
||||
|
||||
include(FeatureSummary)
|
||||
find_package(ECM 5.240.0 NO_MODULE)
|
||||
find_package(ECM 6.4.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,15 +75,20 @@ 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.4
|
||||
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)
|
||||
|
||||
9
LICENSES/BSD-2-Clause.txt
Normal 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
@@ -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.
|
||||
11
README.md
@@ -17,18 +17,21 @@ The following image formats have read-only support:
|
||||
- Camera RAW images (arw, cr2, cr3, dcs, dng, ...)
|
||||
- Gimp (xcf)
|
||||
- Krita (kra)
|
||||
- OpenEXR (exr)
|
||||
- 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)
|
||||
- SGI images (rgb, rgba, sgi, bw)
|
||||
@@ -45,10 +48,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;
|
||||
|
||||
@@ -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
|
||||
@@ -138,6 +155,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)
|
||||
|
||||
BIN
autotests/read/exr/gray.exr
Normal file
BIN
autotests/read/exr/gray.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.6 KiB |
47
autotests/read/hdr/orientation1.hdr
Normal file
5
autotests/read/hdr/orientation1.hdr.json
Normal file
@@ -0,0 +1,5 @@
|
||||
[
|
||||
{
|
||||
"fileName" : "orientation_all.png"
|
||||
}
|
||||
]
|
||||
47
autotests/read/hdr/orientation2.hdr
Normal file
5
autotests/read/hdr/orientation2.hdr.json
Normal file
@@ -0,0 +1,5 @@
|
||||
[
|
||||
{
|
||||
"fileName" : "orientation_all.png"
|
||||
}
|
||||
]
|
||||
47
autotests/read/hdr/orientation3.hdr
Normal file
5
autotests/read/hdr/orientation3.hdr.json
Normal file
@@ -0,0 +1,5 @@
|
||||
[
|
||||
{
|
||||
"fileName" : "orientation_all.png"
|
||||
}
|
||||
]
|
||||
47
autotests/read/hdr/orientation4.hdr
Normal file
5
autotests/read/hdr/orientation4.hdr.json
Normal file
@@ -0,0 +1,5 @@
|
||||
[
|
||||
{
|
||||
"fileName" : "orientation_all.png"
|
||||
}
|
||||
]
|
||||
47
autotests/read/hdr/orientation5.hdr
Normal file
5
autotests/read/hdr/orientation5.hdr.json
Normal file
@@ -0,0 +1,5 @@
|
||||
[
|
||||
{
|
||||
"fileName" : "orientation_all.png"
|
||||
}
|
||||
]
|
||||
47
autotests/read/hdr/orientation6.hdr
Normal file
5
autotests/read/hdr/orientation6.hdr.json
Normal file
@@ -0,0 +1,5 @@
|
||||
[
|
||||
{
|
||||
"fileName" : "orientation_all.png"
|
||||
}
|
||||
]
|
||||
47
autotests/read/hdr/orientation7.hdr
Normal file
5
autotests/read/hdr/orientation7.hdr.json
Normal file
@@ -0,0 +1,5 @@
|
||||
[
|
||||
{
|
||||
"fileName" : "orientation_all.png"
|
||||
}
|
||||
]
|
||||
47
autotests/read/hdr/orientation8.hdr
Normal file
5
autotests/read/hdr/orientation8.hdr.json
Normal file
@@ -0,0 +1,5 @@
|
||||
[
|
||||
{
|
||||
"fileName" : "orientation_all.png"
|
||||
}
|
||||
]
|
||||
BIN
autotests/read/hdr/orientation_all.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
autotests/read/hej2/rgb_kcolorchooser.hej2
Normal file
BIN
autotests/read/hej2/rgb_kcolorchooser.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
autotests/read/hej2/rgba_kolourpaint.hej2
Normal file
BIN
autotests/read/hej2/rgba_kolourpaint.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
autotests/read/jxr/abydos_bgra32.jxr
Normal file
BIN
autotests/read/jxr/abydos_bgra32.png
Normal file
|
After Width: | Height: | Size: 206 KiB |
BIN
autotests/read/jxr/testcard_bgra8.jxr
Normal file
BIN
autotests/read/jxr/testcard_bgra8.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
autotests/read/jxr/testcard_cmyk8.jxr
Normal file
11
autotests/read/jxr/testcard_cmyk8.jxr.json
Normal 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."
|
||||
}
|
||||
]
|
||||
BIN
autotests/read/jxr/testcard_cmyk8.tif
Normal file
BIN
autotests/read/jxr/testcard_gray16.jxr
Normal file
BIN
autotests/read/jxr/testcard_gray16.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
autotests/read/jxr/testcard_gray8.jxr
Normal file
BIN
autotests/read/jxr/testcard_gray8.png
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
BIN
autotests/read/jxr/testcard_mono.jxr
Normal file
BIN
autotests/read/jxr/testcard_mono.png
Normal file
|
After Width: | Height: | Size: 8.2 KiB |
BIN
autotests/read/jxr/testcard_rgb16.jxr
Normal file
BIN
autotests/read/jxr/testcard_rgb16.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
autotests/read/jxr/testcard_rgb8.jxr
Normal file
BIN
autotests/read/jxr/testcard_rgb8.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
autotests/read/jxr/testcard_rgba16.jxr
Normal file
BIN
autotests/read/jxr/testcard_rgba16.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
autotests/read/jxr/testcard_rgba8.jxr
Normal file
BIN
autotests/read/jxr/testcard_rgba8.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
autotests/read/pfm/testcard_gray.pfm
Normal file
BIN
autotests/read/pfm/testcard_gray.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
autotests/read/pfm/testcard_rgb.pfm
Normal file
BIN
autotests/read/pfm/testcard_rgb.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
autotests/read/pfm/testcard_rgb_ps.pfm
Normal file
BIN
autotests/read/pfm/testcard_rgb_ps.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
autotests/read/psd/cmyk16_testcard.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
autotests/read/psd/cmyk16_testcard.psd
Normal file
11
autotests/read/psd/cmyk16_testcard.psd.json
Normal 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"
|
||||
}
|
||||
]
|
||||
BIN
autotests/read/psd/cmyk16_testcard_qt6_8.tif
Normal file
BIN
autotests/read/psd/cmyk8_testcard.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
autotests/read/psd/cmyk8_testcard.psd
Normal file
11
autotests/read/psd/cmyk8_testcard.psd.json
Normal 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"
|
||||
}
|
||||
]
|
||||
BIN
autotests/read/psd/cmyk8_testcard_qt6_8.tif
Normal file
11
autotests/read/psd/cmyka-16bits.psd.json
Normal 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"
|
||||
}
|
||||
]
|
||||
BIN
autotests/read/psd/cmyka-16bits_qt6_8.png
Normal file
|
After Width: | Height: | Size: 111 KiB |
11
autotests/read/psd/cmyka-8bits.psd.json
Normal 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"
|
||||
}
|
||||
]
|
||||
BIN
autotests/read/psd/cmyka-8bits_qt6_8.png
Normal file
|
After Width: | Height: | Size: 77 KiB |
11
autotests/read/psd/mch-16bits.psd.json
Normal 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"
|
||||
}
|
||||
]
|
||||
BIN
autotests/read/psd/mch-16bits_qt_6_8.tif
Normal file
11
autotests/read/psd/mch-8bits.psd.json
Normal 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"
|
||||
}
|
||||
]
|
||||
BIN
autotests/read/psd/mch-8bits_qt_6.8.tif
Normal file
BIN
autotests/read/pxr/testcard_gray.png
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
BIN
autotests/read/pxr/testcard_gray.pxr
Normal file
BIN
autotests/read/pxr/testcard_rgb.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
autotests/read/pxr/testcard_rgb.pxr
Normal file
|
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 93 KiB |
32
autotests/read/xcf/birthday16.xcf.json
Normal 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"
|
||||
}
|
||||
]
|
||||
BIN
autotests/read/xcf/birthday16_alphabug.png
Normal file
|
After Width: | Height: | Size: 91 KiB |
|
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 93 KiB |
32
autotests/read/xcf/birthday32.xcf.json
Normal 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"
|
||||
}
|
||||
]
|
||||
BIN
autotests/read/xcf/birthday32_alphabug.png
Normal file
|
After Width: | Height: | Size: 91 KiB |
BIN
autotests/read/xcf/bug_476755_gray_layers.png
Normal file
|
After Width: | Height: | Size: 234 KiB |
BIN
autotests/read/xcf/bug_476755_gray_layers.xcf
Normal file
BIN
autotests/read/xcf/bug_476755_rgb_layers.png
Normal file
|
After Width: | Height: | Size: 234 KiB |
BIN
autotests/read/xcf/bug_476755_rgb_layers.xcf
Normal 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,22 +262,30 @@ 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));
|
||||
bool skipTest = false;
|
||||
QFileInfo expFileInfo = timg.compareImage(skipTest);
|
||||
if (skipTest) {
|
||||
QTextStream(stdout) << "SKIP : " << fi.fileName() << ": image format not supported by current Qt version!\n";
|
||||
++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;
|
||||
@@ -199,11 +310,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";
|
||||
|
||||
106
autotests/templateimage.cpp
Normal file
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
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(bool &skipTest) const
|
||||
{
|
||||
auto fi = jsonImage(skipTest);
|
||||
if (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(bool &skipTest) const
|
||||
{
|
||||
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();
|
||||
|
||||
// filter
|
||||
if (name.isEmpty() && !unsupportedFormat)
|
||||
continue;
|
||||
if (!minQt.isNull() && currentQt < minQt)
|
||||
continue;
|
||||
if (!maxQt.isNull() && currentQt > maxQt)
|
||||
continue;
|
||||
if (unsupportedFormat) {
|
||||
skipTest = true;
|
||||
break;
|
||||
}
|
||||
return QFileInfo(QStringLiteral("%1/%2").arg(fi.path(), name));
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
74
autotests/templateimage.h
Normal file
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
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:
|
||||
/*!
|
||||
* \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 skipTest True if the test should be skipped (e.g. image format not supported by current Qt version).
|
||||
* \return The template image to use for the comparison.
|
||||
*/
|
||||
QFileInfo compareImage(bool &skipTest) 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 skipTest True if the test should be skipped (not supported).
|
||||
* \return The template image read from the corresponding JSON.
|
||||
*/
|
||||
QFileInfo jsonImage(bool &skipTest) const;
|
||||
|
||||
private:
|
||||
QFileInfo m_fi;
|
||||
};
|
||||
|
||||
#endif // TEMPLATEIMAGE_H
|
||||
@@ -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."));
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
24
cmake/find-modules/FindLibJXR.cmake
Normal 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)
|
||||
@@ -1,4 +1,3 @@
|
||||
maintainer: alexmerry
|
||||
description: Image format plugins for Qt
|
||||
tier: 2
|
||||
type: functional
|
||||
|
||||
@@ -41,7 +41,7 @@ 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 PRIVATE OpenEXR::OpenEXR)
|
||||
else()
|
||||
@@ -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)
|
||||
|
||||
@@ -383,7 +383,7 @@ bool ANIHandler::ensureScanned() const
|
||||
|
||||
// TODO should we check that the number of rate entries matches nSteps?
|
||||
auto *dataPtr = data.data();
|
||||
QVector<int> list;
|
||||
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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
@@ -330,6 +355,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;
|
||||
@@ -515,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
|
||||
@@ -563,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) {
|
||||
@@ -642,8 +681,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
|
||||
@@ -803,6 +842,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;
|
||||
|
||||
@@ -810,6 +851,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;
|
||||
|
||||
@@ -866,7 +918,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:
|
||||
@@ -1039,6 +1091,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;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/*
|
||||
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>
|
||||
@@ -8,23 +7,51 @@
|
||||
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.
|
||||
/* *** 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.
|
||||
*/
|
||||
//#define EXR_USE_LEGACY_CONVERSIONS // default commented -> you should define it in your cmake file
|
||||
//#define EXR_CONVERT_TO_SRGB // default: commented -> you should define it in your cmake file
|
||||
|
||||
/* *** EXR_ALLOW_LINEAR_COLORSPACE ***
|
||||
* If defined, the linear data is kept and it is the display program that
|
||||
* must convert to the monitor profile. Otherwise the data is converted to sRGB
|
||||
* to accommodate programs that do not support color profiles.
|
||||
* NOTE: If EXR_USE_LEGACY_CONVERSIONS is active, this is ignored.
|
||||
/* *** 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_ALLOW_LINEAR_COLORSPACE // default: commented -> you should define it in your cmake file
|
||||
//#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>
|
||||
@@ -39,10 +66,9 @@
|
||||
#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>
|
||||
@@ -53,16 +79,10 @@
|
||||
#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
|
||||
{
|
||||
public:
|
||||
@@ -122,24 +142,66 @@ void K_IStream::clear()
|
||||
// TODO
|
||||
}
|
||||
|
||||
#ifdef EXR_USE_LEGACY_CONVERSIONS
|
||||
// source: https://openexr.com/en/latest/ReadingAndWritingImageFiles.html
|
||||
inline unsigned char gamma(float x)
|
||||
class K_OStream : public Imf::OStream
|
||||
{
|
||||
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));
|
||||
}
|
||||
public:
|
||||
K_OStream(QIODevice *dev, const QByteArray &fileName)
|
||||
: OStream(fileName.data())
|
||||
, m_dev(dev)
|
||||
{
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
EXRHandler::EXRHandler()
|
||||
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;
|
||||
}
|
||||
|
||||
#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);
|
||||
}
|
||||
|
||||
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
|
||||
@@ -151,113 +213,538 @@ bool EXRHandler::canRead() const
|
||||
return false;
|
||||
}
|
||||
|
||||
static QImage::Format imageFormat(const Imf::RgbaInputFile &file)
|
||||
{
|
||||
auto isRgba = file.channels() & Imf::RgbaChannels::WRITE_A;
|
||||
return (isRgba ? QImage::Format_RGBA16FPx4 : QImage::Format_RGBX16FPx4);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \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(META_KEY_COMMENT), QString::fromStdString(comments->value()));
|
||||
}
|
||||
|
||||
if (auto owner = header.findTypedAttribute<Imf::StringAttribute>("owner")) {
|
||||
image.setText(QStringLiteral(META_KEY_OWNER), QString::fromStdString(owner->value()));
|
||||
}
|
||||
|
||||
if (auto lat = header.findTypedAttribute<Imf::FloatAttribute>("latitude")) {
|
||||
image.setText(QStringLiteral(META_KEY_LATITUDE), QLocale::c().toString(lat->value()));
|
||||
}
|
||||
|
||||
if (auto lon = header.findTypedAttribute<Imf::FloatAttribute>("longitude")) {
|
||||
image.setText(QStringLiteral(META_KEY_LONGITUDE), QLocale::c().toString(lon->value()));
|
||||
}
|
||||
|
||||
if (auto alt = header.findTypedAttribute<Imf::FloatAttribute>("altitude")) {
|
||||
image.setText(QStringLiteral(META_KEY_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(META_KEY_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(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')
|
||||
*/
|
||||
}
|
||||
|
||||
/*!
|
||||
* \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
|
||||
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
|
||||
}
|
||||
|
||||
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();
|
||||
bool isRgba = file.channels() & Imf::RgbaChannels::WRITE_A;
|
||||
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;
|
||||
}
|
||||
|
||||
#if defined(EXR_USE_LEGACY_CONVERSIONS)
|
||||
QImage image = imageAlloc(width, height, isRgba ? QImage::Format_ARGB32 : QImage::Format_RGB32);
|
||||
#elif defined(EXR_USE_QT6_FLOAT_IMAGE)
|
||||
QImage image = imageAlloc(width, height, isRgba ? QImage::Format_RGBA16FPx4 : QImage::Format_RGBX16FPx4);
|
||||
#else
|
||||
QImage image = imageAlloc(width, height, isRgba ? QImage::Format_RGBA64 : QImage::Format_RGBX64);
|
||||
#endif
|
||||
// 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;
|
||||
}
|
||||
|
||||
// set some useful metadata
|
||||
auto &&h = file.header();
|
||||
if (auto comments = h.findTypedAttribute<Imf::StringAttribute>("comments")) {
|
||||
image.setText(QStringLiteral("Comment"), QString::fromStdString(comments->value()));
|
||||
}
|
||||
if (auto owner = h.findTypedAttribute<Imf::StringAttribute>("owner")) {
|
||||
image.setText(QStringLiteral("Owner"), QString::fromStdString(owner->value()));
|
||||
}
|
||||
if (auto capDate = h.findTypedAttribute<Imf::StringAttribute>("capDate")) {
|
||||
float off = 0;
|
||||
if (auto utcOffset = h.findTypedAttribute<Imf::FloatAttribute>("utcOffset")) {
|
||||
off = utcOffset->value();
|
||||
}
|
||||
auto dateTime = QDateTime::fromString(QString::fromStdString(capDate->value()), QStringLiteral("yyyy:MM:dd HH:mm:ss"));
|
||||
if (dateTime.isValid()) {
|
||||
dateTime.setTimeZone(QTimeZone::fromSecondsAheadOfUtc(off));
|
||||
image.setText(QStringLiteral("Date"), dateTime.toString(Qt::ISODate));
|
||||
}
|
||||
}
|
||||
if (auto xDensity = h.findTypedAttribute<Imf::FloatAttribute>("xDensity")) {
|
||||
float par = 1;
|
||||
if (auto pixelAspectRatio = h.findTypedAttribute<Imf::FloatAttribute>("pixelAspectRatio")) {
|
||||
par = pixelAspectRatio->value();
|
||||
}
|
||||
image.setDotsPerMeterX(qRound(xDensity->value() * 100.0 / 2.54));
|
||||
image.setDotsPerMeterY(qRound(xDensity->value() * par * 100.0 / 2.54));
|
||||
}
|
||||
Imf::Array2D<Imf::Rgba> pixels;
|
||||
pixels.resizeErase(EXR_LINES_PER_BLOCK, width);
|
||||
bool isRgba = image.hasAlphaChannel();
|
||||
|
||||
Imf::Array<Imf::Rgba> pixels;
|
||||
pixels.resizeErase(width);
|
||||
|
||||
// somehow copy pixels into image
|
||||
for (int y = 0; y < height; ++y) {
|
||||
for (int y = 0, n = 0; y < height; y += n) {
|
||||
auto my = dw.min.y + y;
|
||||
if (my <= dw.max.y) { // paranoia check
|
||||
file.setFrameBuffer(&pixels[0] - dw.min.x - qint64(my) * width, 1, width);
|
||||
file.readPixels(my, my);
|
||||
if (my > dw.max.y) { // paranoia check
|
||||
break;
|
||||
}
|
||||
|
||||
#if defined(EXR_USE_LEGACY_CONVERSIONS)
|
||||
auto scanLine = reinterpret_cast<QRgb *>(image.scanLine(y));
|
||||
for (int x = 0; x < width; ++x) {
|
||||
*(scanLine + x) = RgbaToQrgba(pixels[x]);
|
||||
}
|
||||
#elif defined(EXR_USE_QT6_FLOAT_IMAGE)
|
||||
auto scanLine = reinterpret_cast<qfloat16 *>(image.scanLine(y));
|
||||
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) {
|
||||
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[x].r), 1.f));
|
||||
*(scanLine + xcs + 1) = qfloat16(qBound(0.f, float(pixels[x].g), 1.f));
|
||||
*(scanLine + xcs + 2) = qfloat16(qBound(0.f, float(pixels[x].b), 1.f));
|
||||
*(scanLine + xcs + 3) = qfloat16(isRgba ? qBound(0.f, float(pixels[x].a), 1.f) : 1.f);
|
||||
*(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));
|
||||
for (int x = 0; x < width; ++x) {
|
||||
*(scanLine + x) = QRgba64::fromRgba64(quint16(qBound(0.f, float(pixels[x].r) * 65535.f + 0.5f, 65535.f)),
|
||||
quint16(qBound(0.f, float(pixels[x].g) * 65535.f + 0.5f, 65535.f)),
|
||||
quint16(qBound(0.f, float(pixels[x].b) * 65535.f + 0.5f, 65535.f)),
|
||||
isRgba ? quint16(qBound(0.f, float(pixels[x].a) * 65535.f + 0.5f, 65535.f)) : quint16(65535));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// set some useful metadata
|
||||
readMetadata(header, image);
|
||||
// final color operations
|
||||
#ifndef EXR_USE_LEGACY_CONVERSIONS
|
||||
image.setColorSpace(QColorSpace(QColorSpace::SRgbLinear));
|
||||
#ifndef EXR_ALLOW_LINEAR_COLORSPACE
|
||||
image.convertToColorSpace(QColorSpace(QColorSpace::SRgb));
|
||||
#endif // !EXR_ALLOW_LINEAR_COLORSPACE
|
||||
#endif // !EXR_USE_LEGACY_CONVERSIONS
|
||||
readColorSpace(header, image);
|
||||
|
||||
*outImage = std::move(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(META_KEY_COMMENT), Qt::CaseInsensitive)) {
|
||||
header.insert("comments", Imf::StringAttribute(text.toStdString()));
|
||||
}
|
||||
|
||||
if (!key.compare(QStringLiteral(META_KEY_OWNER), Qt::CaseInsensitive)) {
|
||||
header.insert("owner", Imf::StringAttribute(text.toStdString()));
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
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);
|
||||
if (ok) {
|
||||
header.insert(qPrintable(key.toLower()), Imf::FloatAttribute(value));
|
||||
}
|
||||
}
|
||||
|
||||
if (!key.compare(QStringLiteral(META_KEY_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(META_KEY_XMP_ADOBE), 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 doesn’t have a chromaticities attribute, display software should assume that the
|
||||
// file’s 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());
|
||||
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
|
||||
auto convFormat = image.hasAlphaChannel() ? QImage::Format_RGBA16FPx4 : QImage::Format_RGBX16FPx4;
|
||||
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) {
|
||||
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));
|
||||
}
|
||||
}
|
||||
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) {
|
||||
if (auto d = device())
|
||||
return !d->isSequential();
|
||||
}
|
||||
if (option == QImageIOHandler::ImageFormat) {
|
||||
if (auto d = device())
|
||||
return !d->isSequential();
|
||||
}
|
||||
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();
|
||||
if (m_startPos > -1) {
|
||||
d->seek(m_startPos);
|
||||
}
|
||||
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();
|
||||
if (m_startPos > -1) {
|
||||
d->seek(m_startPos);
|
||||
}
|
||||
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) {
|
||||
@@ -273,7 +760,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 {};
|
||||
@@ -286,6 +773,9 @@ QImageIOPlugin::Capabilities EXRPlugin::capabilities(QIODevice *device, const QB
|
||||
if (device->isReadable() && EXRHandler::canRead(device)) {
|
||||
cap |= CanRead;
|
||||
}
|
||||
if (device->isWritable()) {
|
||||
cap |= CanWrite;
|
||||
}
|
||||
return cap;
|
||||
}
|
||||
|
||||
|
||||