Compare commits
60 Commits
Author | SHA1 | Date | |
---|---|---|---|
db0adee62f | |||
91a342e90d | |||
7864ad4bc6 | |||
524711f633 | |||
b8b980f400 | |||
d932e0d16b | |||
18ea0492bc | |||
7899c27a80 | |||
6a51407556 | |||
75e1280073 | |||
0a6fbd88e9 | |||
fbf60f8bbb | |||
2799382c21 | |||
adc7da4f41 | |||
ac808679cd | |||
2aea982e9e | |||
9173f02ea3 | |||
4badb3088e | |||
8dc685df26 | |||
4bd9d5baec | |||
79e8e183eb | |||
7d63a1d8fa | |||
c11c5eff4f | |||
c02bf3dbcc | |||
6254529d2d | |||
35ff3efbbc | |||
7a0d95af92 | |||
4c3ade04dd | |||
b209e54b6f | |||
4dc2099fa4 | |||
491b223c15 | |||
6559bf8994 | |||
34ed3bad27 | |||
9c579fc1f8 | |||
93adb22632 | |||
d57ff91f8b | |||
edd6adcbac | |||
d787c12727 | |||
c9fec5e408 | |||
e60dfd4968 | |||
f5a9dd46d2 | |||
41f0411b62 | |||
e1a3751936 | |||
402dfb5de3 | |||
a3f7c03b61 | |||
20cec27ae8 | |||
d34c1668aa | |||
560d0483ae | |||
085c9c4841 | |||
b654f20ece | |||
21211cd63b | |||
5cc7a2b45c | |||
4451737d2f | |||
01ab0876f1 | |||
a67dcac7d1 | |||
cef8d08ad4 | |||
a4b9dd9400 | |||
6e6c1ab5f4 | |||
f205adf2e0 | |||
14742cb502 |
@ -2,13 +2,9 @@
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
include:
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux-static.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/android.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/freebsd.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux-qt6.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/android-qt6.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/windows.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/windows-static.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/freebsd-qt6.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/windows-qt6.yml
|
||||
- project: sysadmin/ci-utilities
|
||||
file:
|
||||
- /gitlab-templates/linux-qt6.yml
|
||||
- /gitlab-templates/android-qt6.yml
|
||||
- /gitlab-templates/freebsd-qt6.yml
|
||||
- /gitlab-templates/windows-qt6.yml
|
||||
|
@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.16)
|
||||
project(KImageFormats)
|
||||
|
||||
include(FeatureSummary)
|
||||
find_package(ECM 5.107.0 NO_MODULE)
|
||||
find_package(ECM 5.240.0 NO_MODULE)
|
||||
set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://commits.kde.org/extra-cmake-modules")
|
||||
feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES)
|
||||
|
||||
@ -19,11 +19,11 @@ include(ECMDeprecationSettings)
|
||||
include(CheckIncludeFiles)
|
||||
include(FindPkgConfig)
|
||||
|
||||
set(REQUIRED_QT_VERSION 5.15.2)
|
||||
find_package(Qt${QT_MAJOR_VERSION}Gui ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE)
|
||||
set(REQUIRED_QT_VERSION 6.5.0)
|
||||
find_package(Qt6Gui ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE)
|
||||
|
||||
find_package(KF5Archive)
|
||||
set_package_properties(KF5Archive PROPERTIES
|
||||
find_package(KF6Archive)
|
||||
set_package_properties(KF6Archive PROPERTIES
|
||||
TYPE OPTIONAL
|
||||
PURPOSE "Required for the QImage plugin for Krita and OpenRaster images"
|
||||
)
|
||||
@ -32,12 +32,12 @@ set_package_properties(KF5Archive PROPERTIES
|
||||
# this available in PATH
|
||||
set(BUILD_EPS_PLUGIN FALSE)
|
||||
if (UNIX)
|
||||
find_package(Qt${QT_MAJOR_VERSION}PrintSupport ${REQUIRED_QT_VERSION} NO_MODULE)
|
||||
set_package_properties(Qt${QT_MAJOR_VERSION}PrintSupport PROPERTIES
|
||||
find_package(Qt6PrintSupport ${REQUIRED_QT_VERSION} NO_MODULE)
|
||||
set_package_properties(Qt6PrintSupport PROPERTIES
|
||||
PURPOSE "Required for the QImage plugin for EPS images"
|
||||
TYPE OPTIONAL
|
||||
)
|
||||
if (TARGET Qt${QT_MAJOR_VERSION}::PrintSupport)
|
||||
if (TARGET Qt6::PrintSupport)
|
||||
set(BUILD_EPS_PLUGIN TRUE)
|
||||
endif()
|
||||
endif()
|
||||
@ -51,7 +51,10 @@ set_package_properties(OpenEXR PROPERTIES
|
||||
PURPOSE "Required for the QImage plugin for OpenEXR images"
|
||||
)
|
||||
|
||||
find_package(libavif 0.8.2 CONFIG)
|
||||
find_package(libavif 0.8.2 CONFIG QUIET)
|
||||
if(NOT libavif_FOUND)
|
||||
find_package(libavif 1 CONFIG)
|
||||
endif()
|
||||
set_package_properties(libavif PROPERTIES
|
||||
TYPE OPTIONAL
|
||||
PURPOSE "Required for the QImage plugin for AVIF images"
|
||||
@ -65,8 +68,8 @@ add_feature_info(LibHeif LibHeif_FOUND "required for the QImage plugin for HEIF/
|
||||
|
||||
option(KIMAGEFORMATS_JXL "Enable plugin for JPEG XL format" ON)
|
||||
if(KIMAGEFORMATS_JXL)
|
||||
pkg_check_modules(LibJXL IMPORTED_TARGET libjxl>=0.6.1)
|
||||
pkg_check_modules(LibJXLThreads IMPORTED_TARGET libjxl_threads>=0.6.1)
|
||||
pkg_check_modules(LibJXL IMPORTED_TARGET libjxl>=0.7.0)
|
||||
pkg_check_modules(LibJXLThreads IMPORTED_TARGET libjxl_threads>=0.7.0)
|
||||
endif()
|
||||
add_feature_info(LibJXL LibJXL_FOUND "required for the QImage plugin for JPEG XL images")
|
||||
|
||||
@ -78,8 +81,8 @@ set_package_properties(LibRaw PROPERTIES
|
||||
)
|
||||
|
||||
ecm_set_disabled_deprecation_versions(
|
||||
QT 5.15.2
|
||||
KF 5.95
|
||||
QT 6.5
|
||||
KF 5.102
|
||||
)
|
||||
|
||||
add_subdirectory(src)
|
||||
|
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.
|
18
README.md
@ -14,22 +14,26 @@ image formats.
|
||||
The following image formats have read-only support:
|
||||
|
||||
- Animated Windows cursors (ani)
|
||||
- Gimp (xcf)
|
||||
- OpenEXR (exr)
|
||||
- Photoshop documents (psd, psb, pdd, psdt)
|
||||
- Sun Raster (ras)
|
||||
- Camera RAW images (arw, cr2, cr3, dcs, dng, ...)
|
||||
- Gimp (xcf)
|
||||
- Krita (kra)
|
||||
- OpenRaster (ora)
|
||||
- Photoshop documents (psd, psb, pdd, psdt)
|
||||
- Radiance HDR (hdr)
|
||||
- Sun Raster (im1, im8, im24, im32, ras, sun)
|
||||
|
||||
The following image formats have read and write support:
|
||||
|
||||
- AV1 Image File Format (AVIF)
|
||||
- Encapsulated PostScript (eps)
|
||||
- High Efficiency Image File Format (heif). Can be enabled with the KIMAGEFORMATS_HEIF build option.
|
||||
- JPEG XL (jxl)
|
||||
- OpenEXR (exr)
|
||||
- Personal Computer Exchange (pcx)
|
||||
- Quite OK Image format (qoi)
|
||||
- SGI images (rgb, rgba, sgi, bw)
|
||||
- Softimage PIC (pic)
|
||||
- Targa (tga): supports more formats than Qt's version
|
||||
- XView (xv)
|
||||
|
||||
## Contributing
|
||||
|
||||
@ -41,10 +45,6 @@ of Qt is the license. As such, if you write an imageformat plugin and
|
||||
you are willing to sign the Qt Project contributor agreement, it may be
|
||||
better to submit the plugin directly to the Qt Project.
|
||||
|
||||
Note that the imageformat plugins provided by this module also provide a
|
||||
desktop file. This is for the benefit of KImageIO in the KDE4 Support
|
||||
framework.
|
||||
|
||||
## Duplicated Plugins
|
||||
|
||||
The TGA plugin supports more formats than Qt's own TGA plugin;
|
||||
|
@ -1,7 +1,4 @@
|
||||
#find_package(Qt5Test ${REQUIRED_QT_VERSION} NO_MODULE)
|
||||
|
||||
include(ECMMarkAsTest)
|
||||
include(CMakeParseArguments)
|
||||
|
||||
add_definitions(-DPLUGIN_DIR="${CMAKE_CURRENT_BINARY_DIR}/../bin")
|
||||
remove_definitions(-DQT_NO_CAST_FROM_ASCII)
|
||||
@ -15,7 +12,7 @@ macro(kimageformats_read_tests)
|
||||
|
||||
if (NOT TARGET readtest)
|
||||
add_executable(readtest readtest.cpp)
|
||||
target_link_libraries(readtest Qt${QT_MAJOR_VERSION}::Gui)
|
||||
target_link_libraries(readtest Qt6::Gui)
|
||||
target_compile_definitions(readtest
|
||||
PRIVATE IMAGEDIR="${CMAKE_CURRENT_SOURCE_DIR}/read")
|
||||
ecm_mark_as_test(readtest)
|
||||
@ -38,7 +35,7 @@ macro(kimageformats_write_tests)
|
||||
|
||||
if (NOT TARGET writetest)
|
||||
add_executable(writetest writetest.cpp)
|
||||
target_link_libraries(writetest Qt${QT_MAJOR_VERSION}::Gui)
|
||||
target_link_libraries(writetest Qt6::Gui)
|
||||
target_compile_definitions(writetest
|
||||
PRIVATE IMAGEDIR="${CMAKE_CURRENT_SOURCE_DIR}/write")
|
||||
ecm_mark_as_test(writetest)
|
||||
@ -70,12 +67,13 @@ kimageformats_read_tests(
|
||||
hdr
|
||||
pcx
|
||||
psd
|
||||
qoi
|
||||
ras
|
||||
rgb
|
||||
tga
|
||||
)
|
||||
|
||||
if (KF5Archive_FOUND)
|
||||
if (KF6Archive_FOUND)
|
||||
kimageformats_read_tests(
|
||||
kra
|
||||
ora
|
||||
@ -125,6 +123,7 @@ kimageformats_read_tests(FUZZ 1
|
||||
kimageformats_write_tests(
|
||||
pcx-lossless
|
||||
pic-lossless
|
||||
qoi-lossless
|
||||
rgb-lossless
|
||||
tga # fixme: the alpha images appear not to be written properly
|
||||
)
|
||||
@ -139,6 +138,11 @@ if (OpenEXR_FOUND)
|
||||
kimageformats_read_tests(
|
||||
exr
|
||||
)
|
||||
# Color space conversions from sRGB to linear on saving and
|
||||
# from linear to sRGB on loading result in some rounding errors.
|
||||
kimageformats_write_tests(FUZZ 5
|
||||
exr-nodatacheck-lossless
|
||||
)
|
||||
endif()
|
||||
|
||||
if (LibRaw_FOUND)
|
||||
@ -147,19 +151,19 @@ if (LibRaw_FOUND)
|
||||
)
|
||||
endif()
|
||||
|
||||
find_package(Qt${QT_MAJOR_VERSION}Test ${REQUIRED_QT_VERSION} CONFIG QUIET)
|
||||
find_package(Qt6Test ${REQUIRED_QT_VERSION} CONFIG QUIET)
|
||||
|
||||
if(NOT TARGET Qt${QT_MAJOR_VERSION}::Test)
|
||||
message(STATUS "Qt${QT_MAJOR_VERSION}Test not found, some autotests will not be built.")
|
||||
if(NOT TARGET Qt6::Test)
|
||||
message(STATUS "Qt6Test not found, some autotests will not be built.")
|
||||
return()
|
||||
endif()
|
||||
|
||||
add_executable(pictest pictest.cpp)
|
||||
target_link_libraries(pictest Qt${QT_MAJOR_VERSION}::Gui Qt${QT_MAJOR_VERSION}::Test)
|
||||
target_link_libraries(pictest Qt6::Gui Qt6::Test)
|
||||
ecm_mark_as_test(pictest)
|
||||
add_test(NAME kimageformats-pic COMMAND pictest)
|
||||
|
||||
add_executable(anitest anitest.cpp)
|
||||
target_link_libraries(anitest Qt${QT_MAJOR_VERSION}::Gui Qt${QT_MAJOR_VERSION}::Test)
|
||||
target_link_libraries(anitest Qt6::Gui Qt6::Test)
|
||||
ecm_mark_as_test(anitest)
|
||||
add_test(NAME kimageformats-ani COMMAND anitest)
|
||||
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.6 KiB |
BIN
autotests/read/hdr/fake_earth.hdr
Normal file
BIN
autotests/read/hdr/fake_earth.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 189 KiB After Width: | Height: | Size: 189 KiB |
BIN
autotests/read/psd/mch3-16bits.png
Normal file
After Width: | Height: | Size: 71 KiB |
BIN
autotests/read/psd/mch3-16bits.psb
Normal file
BIN
autotests/read/psd/mch3-8bits.png
Normal file
After Width: | Height: | Size: 39 KiB |
BIN
autotests/read/psd/mch3-8bits.psb
Normal file
BIN
autotests/read/qoi/1px.png
Normal file
After Width: | Height: | Size: 551 B |
BIN
autotests/read/qoi/1px.qoi
Normal file
BIN
autotests/read/qoi/2px.png
Normal file
After Width: | Height: | Size: 561 B |
BIN
autotests/read/qoi/2px.qoi
Normal file
BIN
autotests/read/qoi/bnm_rgb.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
autotests/read/qoi/bnm_rgb.qoi
Normal file
BIN
autotests/read/qoi/bnm_rgba.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
autotests/read/qoi/bnm_rgba.qoi
Normal file
BIN
autotests/read/qoi/testcard.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
autotests/read/qoi/testcard.qoi
Normal file
BIN
autotests/read/qoi/testcard_rgba.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
autotests/read/qoi/testcard_rgba.qoi
Normal file
BIN
autotests/read/ras/rgb-gimp-2.10-1bit.png
Normal file
After Width: | Height: | Size: 192 B |
BIN
autotests/read/ras/rgb-gimp-2.10-1bit.ras
Normal file
BIN
autotests/read/ras/rgb-gimp-2.10-gray.png
Normal file
After Width: | Height: | Size: 470 B |
BIN
autotests/read/ras/rgb-gimp-2.10-gray.ras
Normal file
BIN
autotests/read/ras/rgb-gimp-2.10-pal16.png
Normal file
After Width: | Height: | Size: 338 B |
BIN
autotests/read/ras/rgb-gimp-2.10-pal16.ras
Normal file
BIN
autotests/read/ras/rgb24.png
Normal file
After Width: | Height: | Size: 7.1 KiB |
BIN
autotests/read/ras/rgb24.ras
Normal file
BIN
autotests/read/ras/rle_1bit.png
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
autotests/read/ras/rle_1bit.ras
Normal file
BIN
autotests/read/ras/rle_24bit.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
autotests/read/ras/rle_24bit.ras
Normal file
BIN
autotests/read/ras/rle_pal8bit.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
autotests/read/ras/rle_pal8bit.ras
Normal file
BIN
autotests/read/xcf/birthday.png
Normal file
After Width: | Height: | Size: 96 KiB |
BIN
autotests/read/xcf/birthday.xcf
Normal file
BIN
autotests/read/xcf/birthday16.png
Normal file
After Width: | Height: | Size: 91 KiB |
BIN
autotests/read/xcf/birthday16.xcf
Normal file
BIN
autotests/read/xcf/birthday16_gray.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
autotests/read/xcf/birthday16_gray.xcf
Normal file
BIN
autotests/read/xcf/birthday16_grayA.png
Normal file
After Width: | Height: | Size: 84 KiB |
BIN
autotests/read/xcf/birthday16_grayA.xcf
Normal file
BIN
autotests/read/xcf/birthday16fp.png
Normal file
After Width: | Height: | Size: 82 KiB |
BIN
autotests/read/xcf/birthday16fp.xcf
Normal file
BIN
autotests/read/xcf/birthday16fp_gray.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
autotests/read/xcf/birthday16fp_gray.xcf
Normal file
BIN
autotests/read/xcf/birthday16fp_grayA.png
Normal file
After Width: | Height: | Size: 72 KiB |
BIN
autotests/read/xcf/birthday16fp_grayA.xcf
Normal file
BIN
autotests/read/xcf/birthday32.png
Normal file
After Width: | Height: | Size: 91 KiB |
BIN
autotests/read/xcf/birthday32.xcf
Normal file
BIN
autotests/read/xcf/birthday32_gray.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
autotests/read/xcf/birthday32_gray.xcf
Normal file
BIN
autotests/read/xcf/birthday32_grayA.png
Normal file
After Width: | Height: | Size: 84 KiB |
BIN
autotests/read/xcf/birthday32_grayA.xcf
Normal file
BIN
autotests/read/xcf/birthday32fp.png
Normal file
After Width: | Height: | Size: 82 KiB |
BIN
autotests/read/xcf/birthday32fp.xcf
Normal file
BIN
autotests/read/xcf/birthday32fp_gray.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
autotests/read/xcf/birthday32fp_gray.xcf
Normal file
BIN
autotests/read/xcf/birthday32fp_grayA.png
Normal file
After Width: | Height: | Size: 72 KiB |
BIN
autotests/read/xcf/birthday32fp_grayA.xcf
Normal file
BIN
autotests/read/xcf/birthday_grayA.png
Normal file
After Width: | Height: | Size: 80 KiB |
BIN
autotests/read/xcf/birthday_grayA.xcf
Normal file
Before Width: | Height: | Size: 114 KiB After Width: | Height: | Size: 115 KiB |
BIN
autotests/read/xcf/fruktpilot16_icc.png
Normal file
After Width: | Height: | Size: 115 KiB |
BIN
autotests/read/xcf/fruktpilot16_icc.xcf
Normal file
BIN
autotests/read/xcf/fruktpilot16fplin_icc.png
Normal file
After Width: | Height: | Size: 112 KiB |
BIN
autotests/read/xcf/fruktpilot16fplin_icc.xcf
Normal file
BIN
autotests/read/xcf/fruktpilot32_icc.png
Normal file
After Width: | Height: | Size: 115 KiB |
BIN
autotests/read/xcf/fruktpilot32_icc.xcf
Normal file
BIN
autotests/read/xcf/fruktpilot32fplin_icc.png
Normal file
After Width: | Height: | Size: 112 KiB |
BIN
autotests/read/xcf/fruktpilot32fplin_icc.xcf
Normal file
Before Width: | Height: | Size: 115 KiB After Width: | Height: | Size: 115 KiB |
@ -162,13 +162,13 @@ int main(int argc, char **argv)
|
||||
if (!fi.suffix().compare("png", Qt::CaseInsensitive) || !fi.suffix().compare("tif", Qt::CaseInsensitive)) {
|
||||
continue;
|
||||
}
|
||||
int suffixPos = fi.filePath().count() - suffix.count();
|
||||
int suffixPos = fi.filePath().size() - suffix.size();
|
||||
QString inputfile = fi.filePath();
|
||||
QString fmt = QStringLiteral("png");
|
||||
QString expfile = fi.filePath().replace(suffixPos, suffix.count(), fmt);
|
||||
QString expfile = fi.filePath().replace(suffixPos, suffix.size(), fmt);
|
||||
if (!QFile::exists(expfile)) { // try with tiff
|
||||
fmt = QStringLiteral("tif");
|
||||
expfile = fi.filePath().replace(suffixPos, suffix.count(), fmt);
|
||||
expfile = fi.filePath().replace(suffixPos, suffix.size(), fmt);
|
||||
}
|
||||
QString expfilename = QFileInfo(expfile).fileName();
|
||||
|
||||
|
BIN
autotests/write/rgb.qoi
Normal file
BIN
autotests/write/rgba.qoi
Normal file
@ -7,6 +7,7 @@
|
||||
#include <stdio.h>
|
||||
|
||||
#include <QBuffer>
|
||||
#include <QColorSpace>
|
||||
#include <QCommandLineParser>
|
||||
#include <QCoreApplication>
|
||||
#include <QDir>
|
||||
@ -23,8 +24,8 @@ int main(int argc, char **argv)
|
||||
QCoreApplication app(argc, argv);
|
||||
QCoreApplication::removeLibraryPath(QStringLiteral(PLUGIN_DIR));
|
||||
QCoreApplication::addLibraryPath(QStringLiteral(PLUGIN_DIR));
|
||||
QCoreApplication::setApplicationName(QStringLiteral("readtest"));
|
||||
QCoreApplication::setApplicationVersion(QStringLiteral("1.0.0"));
|
||||
QCoreApplication::setApplicationName(QStringLiteral("writetest"));
|
||||
QCoreApplication::setApplicationVersion(QStringLiteral("1.1.0"));
|
||||
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription(QStringLiteral("Performs basic image conversion checking."));
|
||||
@ -85,8 +86,8 @@ int main(int argc, char **argv)
|
||||
if (parser.isSet(ignoreDataCheck)) {
|
||||
pngfile = fi.filePath();
|
||||
} else {
|
||||
int suffixPos = fi.filePath().count() - suffix.count();
|
||||
pngfile = fi.filePath().replace(suffixPos, suffix.count(), QStringLiteral("png"));
|
||||
int suffixPos = fi.filePath().size() - suffix.size();
|
||||
pngfile = fi.filePath().replace(suffixPos, suffix.size(), QStringLiteral("png"));
|
||||
}
|
||||
QString pngfilename = QFileInfo(pngfile).fileName();
|
||||
|
||||
@ -148,6 +149,14 @@ int main(int argc, char **argv)
|
||||
++failed;
|
||||
continue;
|
||||
}
|
||||
if (reReadImage.colorSpace().isValid()) {
|
||||
QColorSpace toColorSpace;
|
||||
if (pngImage.colorSpace().isValid()) {
|
||||
reReadImage.convertToColorSpace(pngImage.colorSpace());
|
||||
} else {
|
||||
reReadImage.convertToColorSpace(QColorSpace(QColorSpace::SRgb));
|
||||
}
|
||||
}
|
||||
reReadImage = reReadImage.convertToFormat(pngImage.format());
|
||||
}
|
||||
|
||||
|
@ -12,3 +12,4 @@ Ignacio Castaño <castano@ludicon.com> -- DDS and PDS format reader.
|
||||
Christoph Hormann <chris_hormann@gmx.de> -- HDR format read support.
|
||||
Michael Ritzert <kde@ritzert.de> -- JPEG 2000 format read/write support
|
||||
Troy Unrau <troy@kde.org> -- Sun RASter read support
|
||||
Ernest Gupik <ernestgupik@wp.pl> -- QOI format read support
|
||||
|
@ -11,9 +11,10 @@ function(kimageformats_add_plugin plugin)
|
||||
message(FATAL_ERROR "kimageformats_add_plugin called without SOURCES parameter")
|
||||
endif()
|
||||
|
||||
add_library(${plugin} MODULE ${KIF_ADD_PLUGIN_SOURCES})
|
||||
set_target_properties(${plugin} PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/imageformats")
|
||||
target_link_libraries(${plugin} Qt${QT_MAJOR_VERSION}::Gui)
|
||||
qt_add_plugin(${plugin} PLUGIN_TYPE imageformats)
|
||||
target_sources(${plugin} PRIVATE ${KIF_ADD_PLUGIN_SOURCES})
|
||||
set_target_properties(${plugin} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/imageformats)
|
||||
target_link_libraries(${plugin} PRIVATE Qt6::Gui)
|
||||
install(TARGETS ${plugin} DESTINATION ${KDE_INSTALL_QTPLUGINDIR}/imageformats)
|
||||
endfunction()
|
||||
|
||||
@ -21,172 +22,105 @@ endfunction()
|
||||
|
||||
kimageformats_add_plugin(kimg_ani SOURCES ani.cpp)
|
||||
|
||||
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||
install(FILES ani.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||
endif()
|
||||
|
||||
##################################
|
||||
|
||||
if (TARGET avif)
|
||||
kimageformats_add_plugin(kimg_avif SOURCES "avif.cpp")
|
||||
target_link_libraries(kimg_avif "avif")
|
||||
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||
install(FILES avif.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
##################################
|
||||
|
||||
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||
install(FILES dds-qt.desktop RENAME dds.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||
target_link_libraries(kimg_avif PRIVATE "avif")
|
||||
endif()
|
||||
|
||||
##################################
|
||||
|
||||
if (BUILD_EPS_PLUGIN)
|
||||
if (TARGET Qt${QT_MAJOR_VERSION}::PrintSupport)
|
||||
if (TARGET Qt6::PrintSupport)
|
||||
kimageformats_add_plugin(kimg_eps SOURCES eps.cpp)
|
||||
target_link_libraries(kimg_eps Qt${QT_MAJOR_VERSION}::PrintSupport)
|
||||
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||
install(FILES eps.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||
endif()
|
||||
target_link_libraries(kimg_eps PRIVATE Qt6::PrintSupport)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
##################################
|
||||
|
||||
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||
# need this for Qt's version of the plugin
|
||||
install(FILES jp2.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||
endif()
|
||||
|
||||
##################################
|
||||
|
||||
if(OpenEXR_FOUND)
|
||||
kimageformats_add_plugin(kimg_exr SOURCES exr.cpp)
|
||||
kimageformats_add_plugin(kimg_exr SOURCES exr.cpp scanlineconverter.cpp)
|
||||
if(TARGET OpenEXR::OpenEXR)
|
||||
target_link_libraries(kimg_exr OpenEXR::OpenEXR)
|
||||
target_link_libraries(kimg_exr PRIVATE OpenEXR::OpenEXR)
|
||||
else()
|
||||
if(OpenEXR_VERSION_STRING VERSION_LESS 2.3.0)
|
||||
# Older OpenEXR versions use dynamic exception specifications, so
|
||||
# cannot use C++17 with them
|
||||
set_target_properties(kimg_exr PROPERTIES CXX_STANDARD 14)
|
||||
endif()
|
||||
target_link_libraries(kimg_exr OpenEXR::IlmImf)
|
||||
target_link_libraries(kimg_exr PRIVATE OpenEXR::IlmImf)
|
||||
endif()
|
||||
kde_target_enable_exceptions(kimg_exr PRIVATE)
|
||||
|
||||
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||
install(FILES exr.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
##################################
|
||||
|
||||
kimageformats_add_plugin(kimg_hdr SOURCES hdr.cpp)
|
||||
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||
install(FILES hdr.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||
endif()
|
||||
|
||||
##################################
|
||||
|
||||
if (LibHeif_FOUND)
|
||||
kimageformats_add_plugin(kimg_heif SOURCES heif.cpp)
|
||||
target_link_libraries(kimg_heif PkgConfig::LibHeif)
|
||||
|
||||
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||
install(FILES heif.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||
endif()
|
||||
target_link_libraries(kimg_heif PRIVATE PkgConfig::LibHeif)
|
||||
endif()
|
||||
|
||||
##################################
|
||||
|
||||
if (LibJXL_FOUND AND LibJXLThreads_FOUND)
|
||||
kimageformats_add_plugin(kimg_jxl SOURCES jxl.cpp)
|
||||
target_link_libraries(kimg_jxl PkgConfig::LibJXL PkgConfig::LibJXLThreads)
|
||||
if (LibJXL_VERSION VERSION_GREATER_EQUAL "0.7.0")
|
||||
target_compile_definitions(kimg_jxl PRIVATE KIMG_JXL_API_VERSION=70)
|
||||
endif()
|
||||
|
||||
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||
install(FILES jxl.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||
endif()
|
||||
target_link_libraries(kimg_jxl PRIVATE PkgConfig::LibJXL PkgConfig::LibJXLThreads)
|
||||
endif()
|
||||
|
||||
##################################
|
||||
|
||||
kimageformats_add_plugin(kimg_pcx SOURCES pcx.cpp)
|
||||
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||
install(FILES pcx.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||
endif()
|
||||
|
||||
##################################
|
||||
|
||||
kimageformats_add_plugin(kimg_pic SOURCES pic.cpp)
|
||||
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||
install(FILES pic.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||
endif()
|
||||
|
||||
##################################
|
||||
|
||||
kimageformats_add_plugin(kimg_psd SOURCES psd.cpp)
|
||||
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||
install(FILES psd.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||
endif()
|
||||
|
||||
##################################
|
||||
|
||||
kimageformats_add_plugin(kimg_qoi SOURCES qoi.cpp scanlineconverter.cpp)
|
||||
|
||||
##################################
|
||||
|
||||
kimageformats_add_plugin(kimg_ras SOURCES ras.cpp)
|
||||
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||
install(FILES ras.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||
endif()
|
||||
|
||||
##################################
|
||||
|
||||
kimageformats_add_plugin(kimg_rgb SOURCES rgb.cpp)
|
||||
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||
install(FILES rgb.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||
endif()
|
||||
|
||||
##################################
|
||||
|
||||
kimageformats_add_plugin(kimg_tga SOURCES tga.cpp)
|
||||
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||
install(FILES tga.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||
endif()
|
||||
|
||||
##################################
|
||||
|
||||
kimageformats_add_plugin(kimg_xcf SOURCES xcf.cpp)
|
||||
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||
install(FILES xcf.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||
endif()
|
||||
|
||||
##################################
|
||||
|
||||
if (LibRaw_FOUND)
|
||||
kimageformats_add_plugin(kimg_raw SOURCES raw.cpp)
|
||||
kde_enable_exceptions()
|
||||
target_link_libraries(kimg_raw LibRaw::LibRaw)
|
||||
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||
install(FILES raw.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||
endif()
|
||||
target_link_libraries(kimg_raw PRIVATE LibRaw::LibRaw)
|
||||
endif()
|
||||
|
||||
##################################
|
||||
|
||||
if (KF5Archive_FOUND)
|
||||
if (KF6Archive_FOUND)
|
||||
|
||||
kimageformats_add_plugin(kimg_kra SOURCES kra.cpp)
|
||||
target_link_libraries(kimg_kra KF5::Archive)
|
||||
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||
install(FILES kra.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||
endif()
|
||||
target_link_libraries(kimg_kra PRIVATE KF6::Archive)
|
||||
|
||||
kimageformats_add_plugin(kimg_ora SOURCES ora.cpp)
|
||||
target_link_libraries(kimg_ora KF5::Archive)
|
||||
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||
install(FILES ora.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||
endif()
|
||||
target_link_libraries(kimg_ora PRIVATE KF6::Archive)
|
||||
|
||||
endif()
|
||||
|
@ -90,7 +90,7 @@ bool ANIHandler::read(QImage *outImage)
|
||||
}
|
||||
|
||||
const QByteArray frameSizeData = device()->read(sizeof(quint32_le));
|
||||
if (frameSizeData.count() != sizeof(quint32_le)) {
|
||||
if (frameSizeData.size() != sizeof(quint32_le)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -383,8 +383,8 @@ bool ANIHandler::ensureScanned() const
|
||||
|
||||
// TODO should we check that the number of rate entries matches nSteps?
|
||||
auto *dataPtr = data.data();
|
||||
QVector<int> list;
|
||||
for (int i = 0; i < data.count(); i += sizeof(quint32_le)) {
|
||||
QList<int> list;
|
||||
for (int i = 0; i < data.size(); i += sizeof(quint32_le)) {
|
||||
const auto entry = *(reinterpret_cast<const quint32_le *>(dataPtr + i));
|
||||
list.append(entry);
|
||||
}
|
||||
@ -570,3 +570,5 @@ QImageIOHandler *ANIPlugin::create(QIODevice *device, const QByteArray &format)
|
||||
handler->setFormat(format);
|
||||
return handler;
|
||||
}
|
||||
|
||||
#include "moc_ani_p.cpp"
|
||||
|
@ -1,7 +0,0 @@
|
||||
[Desktop Entry]
|
||||
Type=Service
|
||||
X-KDE-ServiceTypes=QImageIOPlugins
|
||||
X-KDE-ImageFormat=ani
|
||||
X-KDE-MimeType=application/x-navi-animation
|
||||
X-KDE-Read=true
|
||||
X-KDE-Write=false
|
@ -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;
|
||||
|
@ -330,6 +330,10 @@ bool QAVIFHandler::decode_one_frame()
|
||||
avifRGBImage rgb;
|
||||
avifRGBImageSetDefaults(&rgb, m_decoder->image);
|
||||
|
||||
#if AVIF_VERSION >= 1000000
|
||||
rgb.maxThreads = m_decoder->maxThreads;
|
||||
#endif
|
||||
|
||||
if (m_decoder->image->depth > 8) {
|
||||
rgb.depth = 16;
|
||||
rgb.format = AVIF_RGB_FORMAT_RGBA;
|
||||
@ -424,7 +428,7 @@ bool QAVIFHandler::decode_one_frame()
|
||||
}
|
||||
|
||||
if (m_decoder->image->transformFlags & AVIF_TRANSFORM_IMIR) {
|
||||
#if AVIF_VERSION > 90100
|
||||
#if AVIF_VERSION > 90100 && AVIF_VERSION < 1000000
|
||||
switch (m_decoder->image->imir.mode) {
|
||||
#else
|
||||
switch (m_decoder->image->imir.axis) {
|
||||
@ -714,9 +718,9 @@ bool QAVIFHandler::write(const QImage &image)
|
||||
if (save_depth == 8) {
|
||||
save_depth = 10;
|
||||
if (tmpcolorimage.hasAlphaChannel()) {
|
||||
tmpcolorimage = tmpcolorimage.convertToFormat(QImage::Format_RGBA64);
|
||||
tmpcolorimage.convertTo(QImage::Format_RGBA64);
|
||||
} else {
|
||||
tmpcolorimage = tmpcolorimage.convertToFormat(QImage::Format_RGBX64);
|
||||
tmpcolorimage.convertTo(QImage::Format_RGBX64);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1039,6 +1043,11 @@ int QAVIFHandler::loopCount() const
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if AVIF_VERSION >= 1000000
|
||||
if (m_decoder->repetitionCount >= 0) {
|
||||
return m_decoder->repetitionCount;
|
||||
}
|
||||
#endif
|
||||
// Endless loop to work around https://github.com/AOMediaCodec/libavif/issues/347
|
||||
return -1;
|
||||
}
|
||||
@ -1103,3 +1112,5 @@ QImageIOHandler *QAVIFPlugin::create(QIODevice *device, const QByteArray &format
|
||||
handler->setFormat(format);
|
||||
return handler;
|
||||
}
|
||||
|
||||
#include "moc_avif_p.cpp"
|
||||
|
@ -1,7 +0,0 @@
|
||||
[Desktop Entry]
|
||||
Type=Service
|
||||
X-KDE-ServiceTypes=QImageIOPlugins
|
||||
X-KDE-ImageFormat=avif
|
||||
X-KDE-MimeType=image/avif
|
||||
X-KDE-Read=true
|
||||
X-KDE-Write=true
|
@ -1,7 +0,0 @@
|
||||
[Desktop Entry]
|
||||
Type=Service
|
||||
X-KDE-ServiceTypes=QImageIOPlugins
|
||||
X-KDE-ImageFormat=dds
|
||||
X-KDE-MimeType=image/x-dds
|
||||
X-KDE-Read=true
|
||||
X-KDE-Write=true
|
@ -365,3 +365,5 @@ QImageIOHandler *EPSPlugin::create(QIODevice *device, const QByteArray &format)
|
||||
handler->setFormat(format);
|
||||
return handler;
|
||||
}
|
||||
|
||||
#include "moc_eps_p.cpp"
|
||||
|
@ -1,7 +0,0 @@
|
||||
[Desktop Entry]
|
||||
Type=Service
|
||||
X-KDE-ServiceTypes=QImageIOPlugins
|
||||
X-KDE-ImageFormat=eps,epsi,epsf
|
||||
X-KDE-MimeType=image/x-eps
|
||||
X-KDE-Read=true
|
||||
X-KDE-Write=true
|
@ -1,13 +1,66 @@
|
||||
/*
|
||||
KImageIO Routines to read (and perhaps in the future, write) images
|
||||
in the high dynamic range EXR format.
|
||||
The high dynamic range EXR format support for QImage.
|
||||
|
||||
SPDX-FileCopyrightText: 2003 Brad Hards <bradh@frogmouth.net>
|
||||
SPDX-FileCopyrightText: 2023 Mirco Miranda <mircomir@outlook.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
/* *** EXR_USE_LEGACY_CONVERSIONS ***
|
||||
* If defined, the result image is an 8-bit RGB(A) converted
|
||||
* without icc profiles. Otherwise, a 16-bit images is generated.
|
||||
* NOTE: The use of legacy conversions are discouraged due to
|
||||
* imprecise image result.
|
||||
*/
|
||||
//#define EXR_USE_LEGACY_CONVERSIONS // default commented -> you should define it in your cmake file
|
||||
|
||||
/* *** EXR_CONVERT_TO_SRGB ***
|
||||
* If defined, the linear data is converted to sRGB on read to accommodate
|
||||
* programs that do not support color profiles.
|
||||
* Otherwise the data are kept as is and it is the display program that
|
||||
* must convert to the monitor profile.
|
||||
* NOTE: If EXR_USE_LEGACY_CONVERSIONS is active, this is ignored.
|
||||
*/
|
||||
//#define EXR_CONVERT_TO_SRGB // default: commented -> you should define it in your cmake file
|
||||
|
||||
/* *** EXR_STORE_XMP_ATTRIBUTE ***
|
||||
* If defined, disables the stores XMP values in a non-standard attribute named "xmp".
|
||||
* The QImage metadata used is "XML:com.adobe.xmp".
|
||||
* NOTE: The use of non-standard attributes is possible but discouraged by the specification. However,
|
||||
* metadata is essential for good image management and programs like darktable also set this
|
||||
* attribute. Gimp reads the "xmp" attribute and Darktable writes it as well.
|
||||
*/
|
||||
//#define EXR_DISABLE_XMP_ATTRIBUTE // default: commented -> you should define it in your cmake file
|
||||
|
||||
/* *** EXR_MAX_IMAGE_WIDTH and EXR_MAX_IMAGE_HEIGHT ***
|
||||
* The maximum size in pixel allowed by the plugin.
|
||||
*/
|
||||
#ifndef EXR_MAX_IMAGE_WIDTH
|
||||
#define EXR_MAX_IMAGE_WIDTH 300000
|
||||
#endif
|
||||
#ifndef EXR_MAX_IMAGE_HEIGHT
|
||||
#define EXR_MAX_IMAGE_HEIGHT EXR_MAX_IMAGE_WIDTH
|
||||
#endif
|
||||
|
||||
/* *** EXR_LINES_PER_BLOCK ***
|
||||
* Allows certain compression schemes to work in multithreading
|
||||
* Requires up to "LINES_PER_BLOCK * MAX_IMAGE_WIDTH * 8"
|
||||
* additional RAM (e.g. if 128, up to 307MiB of RAM).
|
||||
* There is a performance gain with the following parameters (based on empirical tests):
|
||||
* - PIZ compression needs 64+ lines
|
||||
* - ZIPS compression needs 8+ lines
|
||||
* - ZIP compression needs 32+ lines
|
||||
* - Others not tested
|
||||
*
|
||||
* NOTE: The OpenEXR documentation states that the higher the better :)
|
||||
*/
|
||||
#ifndef EXR_LINES_PER_BLOCK
|
||||
#define EXR_LINES_PER_BLOCK 128
|
||||
#endif
|
||||
|
||||
#include "exr_p.h"
|
||||
#include "scanlineconverter_p.h"
|
||||
#include "util_p.h"
|
||||
|
||||
#include <IexThrowErrnoExc.h>
|
||||
@ -22,18 +75,29 @@
|
||||
#include <ImfInt64.h>
|
||||
#include <ImfIntAttribute.h>
|
||||
#include <ImfLineOrderAttribute.h>
|
||||
#include <ImfPreviewImage.h>
|
||||
#include <ImfRgbaFile.h>
|
||||
#include <ImfStandardAttributes.h>
|
||||
#include <ImfStringAttribute.h>
|
||||
#include <ImfVecAttribute.h>
|
||||
#include <ImfVersion.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <QColorSpace>
|
||||
#include <QDataStream>
|
||||
#include <QDebug>
|
||||
#include <QFloat16>
|
||||
#include <QImage>
|
||||
#include <QImageIOPlugin>
|
||||
#include <QLocale>
|
||||
#include <QThread>
|
||||
#include <QTimeZone>
|
||||
|
||||
// Allow the code to works on all QT versions supported by KDE
|
||||
// project (Qt 5.15 and Qt 6.x) to easy backports fixes.
|
||||
#if (QT_VERSION_MAJOR >= 6) && !defined(EXR_USE_LEGACY_CONVERSIONS)
|
||||
// If uncommented, the image is rendered in a float16 format, the result is very precise
|
||||
#define EXR_USE_QT6_FLOAT_IMAGE // default uncommented
|
||||
#endif
|
||||
|
||||
class K_IStream : public Imf::IStream
|
||||
{
|
||||
@ -94,80 +158,82 @@ void K_IStream::clear()
|
||||
// TODO
|
||||
}
|
||||
|
||||
/* this does a conversion from the ILM Half (equal to Nvidia Half)
|
||||
* format into the normal 32 bit pixel format. Process is from the
|
||||
* ILM code.
|
||||
*/
|
||||
QRgb RgbaToQrgba(struct Imf::Rgba &imagePixel)
|
||||
class K_OStream : public Imf::OStream
|
||||
{
|
||||
float r;
|
||||
float g;
|
||||
float b;
|
||||
float a;
|
||||
|
||||
// 1) Compensate for fogging by subtracting defog
|
||||
// from the raw pixel values.
|
||||
// Response: We work with defog of 0.0, so this is a no-op
|
||||
|
||||
// 2) Multiply the defogged pixel values by
|
||||
// 2^(exposure + 2.47393).
|
||||
// Response: We work with exposure of 0.0.
|
||||
// (2^2.47393) is 5.55555
|
||||
r = imagePixel.r * 5.55555;
|
||||
g = imagePixel.g * 5.55555;
|
||||
b = imagePixel.b * 5.55555;
|
||||
a = imagePixel.a * 5.55555;
|
||||
|
||||
// 3) Values, which are now 1.0, are called "middle gray".
|
||||
// If defog and exposure are both set to 0.0, then
|
||||
// middle gray corresponds to a raw pixel value of 0.18.
|
||||
// In step 6, middle gray values will be mapped to an
|
||||
// intensity 3.5 f-stops below the display's maximum
|
||||
// intensity.
|
||||
// Response: no apparent content.
|
||||
|
||||
// 4) Apply a knee function. The knee function has two
|
||||
// parameters, kneeLow and kneeHigh. Pixel values
|
||||
// below 2^kneeLow are not changed by the knee
|
||||
// function. Pixel values above kneeLow are lowered
|
||||
// according to a logarithmic curve, such that the
|
||||
// value 2^kneeHigh is mapped to 2^3.5 (in step 6,
|
||||
// this value will be mapped to the display's
|
||||
// maximum intensity).
|
||||
// Response: kneeLow = 0.0 (2^0.0 => 1); kneeHigh = 5.0 (2^5 =>32)
|
||||
if (r > 1.0) {
|
||||
r = 1.0 + std::log((r - 1.0) * 0.184874 + 1) / 0.184874;
|
||||
public:
|
||||
K_OStream(QIODevice *dev, const QByteArray &fileName)
|
||||
: OStream(fileName.data())
|
||||
, m_dev(dev)
|
||||
{
|
||||
}
|
||||
if (g > 1.0) {
|
||||
g = 1.0 + std::log((g - 1.0) * 0.184874 + 1) / 0.184874;
|
||||
}
|
||||
if (b > 1.0) {
|
||||
b = 1.0 + std::log((b - 1.0) * 0.184874 + 1) / 0.184874;
|
||||
}
|
||||
if (a > 1.0) {
|
||||
a = 1.0 + std::log((a - 1.0) * 0.184874 + 1) / 0.184874;
|
||||
}
|
||||
//
|
||||
// 5) Gamma-correct the pixel values, assuming that the
|
||||
// screen's gamma is 0.4545 (or 1/2.2).
|
||||
r = std::pow(r, 0.4545);
|
||||
g = std::pow(g, 0.4545);
|
||||
b = std::pow(b, 0.4545);
|
||||
a = std::pow(a, 0.4545);
|
||||
|
||||
// 6) Scale the values such that pixels middle gray
|
||||
// pixels are mapped to 84.66 (or 3.5 f-stops below
|
||||
// the display's maximum intensity).
|
||||
//
|
||||
// 7) Clamp the values to [0, 255].
|
||||
return qRgba((unsigned char)(Imath::clamp(r * 84.66f, 0.f, 255.f)),
|
||||
(unsigned char)(Imath::clamp(g * 84.66f, 0.f, 255.f)),
|
||||
(unsigned char)(Imath::clamp(b * 84.66f, 0.f, 255.f)),
|
||||
(unsigned char)(Imath::clamp(a * 84.66f, 0.f, 255.f)));
|
||||
void write(const char c[/*n*/], int n) override;
|
||||
#if OPENEXR_VERSION_MAJOR > 2
|
||||
uint64_t tellp() override;
|
||||
void seekp(uint64_t pos) override;
|
||||
#else
|
||||
Imf::Int64 tellp() override;
|
||||
void seekp(Imf::Int64 pos) override;
|
||||
#endif
|
||||
|
||||
private:
|
||||
QIODevice *m_dev;
|
||||
};
|
||||
|
||||
void K_OStream::write(const char c[], int n)
|
||||
{
|
||||
qint64 result = m_dev->write(c, n);
|
||||
if (result > 0) {
|
||||
return;
|
||||
} else { // negative value {
|
||||
Iex::throwErrnoExc("Error in write", result);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
EXRHandler::EXRHandler()
|
||||
#if OPENEXR_VERSION_MAJOR > 2
|
||||
uint64_t K_OStream::tellp()
|
||||
#else
|
||||
Imf::Int64 K_OStream::tellg()
|
||||
#endif
|
||||
{
|
||||
return m_dev->pos();
|
||||
}
|
||||
|
||||
#if OPENEXR_VERSION_MAJOR > 2
|
||||
void K_OStream::seekp(uint64_t pos)
|
||||
#else
|
||||
void K_OStream::seekg(Imf::Int64 pos)
|
||||
#endif
|
||||
{
|
||||
m_dev->seek(pos);
|
||||
}
|
||||
|
||||
#ifdef EXR_USE_LEGACY_CONVERSIONS
|
||||
// source: https://openexr.com/en/latest/ReadingAndWritingImageFiles.html
|
||||
inline unsigned char gamma(float x)
|
||||
{
|
||||
x = std::pow(5.5555f * std::max(0.f, x), 0.4545f) * 84.66f;
|
||||
return (unsigned char)qBound(0.f, x, 255.f);
|
||||
}
|
||||
inline QRgb RgbaToQrgba(struct Imf::Rgba &imagePixel)
|
||||
{
|
||||
return qRgba(gamma(float(imagePixel.r)),
|
||||
gamma(float(imagePixel.g)),
|
||||
gamma(float(imagePixel.b)),
|
||||
(unsigned char)(qBound(0.f, imagePixel.a * 255.f, 255.f) + 0.5f));
|
||||
}
|
||||
#endif
|
||||
|
||||
EXRHandler::EXRHandler()
|
||||
: m_compressionRatio(-1)
|
||||
, m_quality(-1)
|
||||
, m_imageNumber(0)
|
||||
, m_imageCount(0)
|
||||
, m_startPos(-1)
|
||||
{
|
||||
// Set the number of threads to use (0 is allowed)
|
||||
Imf::setGlobalThreadCount(QThread::idealThreadCount() / 2);
|
||||
}
|
||||
|
||||
bool EXRHandler::canRead() const
|
||||
@ -179,48 +245,567 @@ bool EXRHandler::canRead() const
|
||||
return false;
|
||||
}
|
||||
|
||||
static QImage::Format imageFormat(const Imf::RgbaInputFile &file)
|
||||
{
|
||||
auto isRgba = file.channels() & Imf::RgbaChannels::WRITE_A;
|
||||
#if defined(EXR_USE_LEGACY_CONVERSIONS)
|
||||
return (isRgba ? QImage::Format_ARGB32 : QImage::Format_RGB32);
|
||||
#elif defined(EXR_USE_QT6_FLOAT_IMAGE)
|
||||
return (isRgba ? QImage::Format_RGBA16FPx4 : QImage::Format_RGBX16FPx4);
|
||||
#else
|
||||
return (isRgba ? QImage::Format_RGBA64 : QImage::Format_RGBX64);
|
||||
#endif
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief viewList
|
||||
* \param header
|
||||
* \return The list of available views.
|
||||
*/
|
||||
static QStringList viewList(const Imf::Header &h)
|
||||
{
|
||||
QStringList l;
|
||||
if (auto views = h.findTypedAttribute<Imf::StringVectorAttribute>("multiView")) {
|
||||
for (auto &&v : views->value()) {
|
||||
l << QString::fromStdString(v);
|
||||
}
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
#ifdef QT_DEBUG
|
||||
void printAttributes(const Imf::Header &h)
|
||||
{
|
||||
for (auto i = h.begin(); i != h.end(); ++i) {
|
||||
qDebug() << i.name();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/*!
|
||||
* \brief readMetadata
|
||||
* Reads EXR attributes from the \a header and set its as metadata in the \a image.
|
||||
*/
|
||||
static void readMetadata(const Imf::Header &header, QImage &image)
|
||||
{
|
||||
// set some useful metadata
|
||||
if (auto comments = header.findTypedAttribute<Imf::StringAttribute>("comments")) {
|
||||
image.setText(QStringLiteral("Comment"), QString::fromStdString(comments->value()));
|
||||
}
|
||||
|
||||
if (auto owner = header.findTypedAttribute<Imf::StringAttribute>("owner")) {
|
||||
image.setText(QStringLiteral("Owner"), QString::fromStdString(owner->value()));
|
||||
}
|
||||
|
||||
if (auto lat = header.findTypedAttribute<Imf::FloatAttribute>("latitude")) {
|
||||
image.setText(QStringLiteral("Latitude"), QLocale::c().toString(lat->value()));
|
||||
}
|
||||
|
||||
if (auto lon = header.findTypedAttribute<Imf::FloatAttribute>("longitude")) {
|
||||
image.setText(QStringLiteral("Longitude"), QLocale::c().toString(lon->value()));
|
||||
}
|
||||
|
||||
if (auto alt = header.findTypedAttribute<Imf::FloatAttribute>("altitude")) {
|
||||
image.setText(QStringLiteral("Altitude"), QLocale::c().toString(alt->value()));
|
||||
}
|
||||
|
||||
if (auto capDate = header.findTypedAttribute<Imf::StringAttribute>("capDate")) {
|
||||
float off = 0;
|
||||
if (auto utcOffset = header.findTypedAttribute<Imf::FloatAttribute>("utcOffset")) {
|
||||
off = utcOffset->value();
|
||||
}
|
||||
auto dateTime = QDateTime::fromString(QString::fromStdString(capDate->value()), QStringLiteral("yyyy:MM:dd HH:mm:ss"));
|
||||
if (dateTime.isValid()) {
|
||||
dateTime.setTimeZone(QTimeZone::fromSecondsAheadOfUtc(off));
|
||||
image.setText(QStringLiteral("CreationDate"), dateTime.toString(Qt::ISODate));
|
||||
}
|
||||
}
|
||||
|
||||
if (auto xDensity = header.findTypedAttribute<Imf::FloatAttribute>("xDensity")) {
|
||||
float par = 1;
|
||||
if (auto pixelAspectRatio = header.findTypedAttribute<Imf::FloatAttribute>("pixelAspectRatio")) {
|
||||
par = pixelAspectRatio->value();
|
||||
}
|
||||
image.setDotsPerMeterX(qRound(xDensity->value() * 100.0 / 2.54));
|
||||
image.setDotsPerMeterY(qRound(xDensity->value() * par * 100.0 / 2.54));
|
||||
}
|
||||
|
||||
// Non-standard attribute
|
||||
if (auto xmp = header.findTypedAttribute<Imf::StringAttribute>("xmp")) {
|
||||
image.setText(QStringLiteral("XML:com.adobe.xmp"), QString::fromStdString(xmp->value()));
|
||||
}
|
||||
|
||||
/* TODO: OpenEXR 3.2 metadata
|
||||
*
|
||||
* New Optional Standard Attributes:
|
||||
* - Support automated editorial workflow:
|
||||
* reelName, imageCounter, ascFramingDecisionList
|
||||
*
|
||||
* - Support forensics (“which other shots used that camera and lens before the camera firmware was updated?”):
|
||||
* cameraMake, cameraModel, cameraSerialNumber, cameraFirmware, cameraUuid, cameraLabel, lensMake, lensModel,
|
||||
* lensSerialNumber, lensFirmware, cameraColorBalance
|
||||
*
|
||||
* -Support pickup shots (reproduce critical camera settings):
|
||||
* shutterAngle, cameraCCTSetting, cameraTintSetting
|
||||
*
|
||||
* - Support metadata-driven match move:
|
||||
* sensorCenterOffset, sensorOverallDimensions, sensorPhotositePitch, sensorAcquisitionRectanglenominalFocalLength,
|
||||
* effectiveFocalLength, pinholeFocalLength, entrancePupilOffset, tStop(complementing existing 'aperture')
|
||||
*/
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief readColorSpace
|
||||
* Reads EXR chromaticities from the \a header and set its as color profile in the \a image.
|
||||
*/
|
||||
static void readColorSpace(const Imf::Header &header, QImage &image)
|
||||
{
|
||||
// final color operations
|
||||
#ifndef EXR_USE_LEGACY_CONVERSIONS
|
||||
|
||||
QColorSpace cs;
|
||||
if (auto chroma = header.findTypedAttribute<Imf::ChromaticitiesAttribute>("chromaticities")) {
|
||||
auto &&v = chroma->value();
|
||||
cs = QColorSpace(QPointF(v.white.x, v.white.y),
|
||||
QPointF(v.red.x, v.red.y),
|
||||
QPointF(v.green.x, v.green.y),
|
||||
QPointF(v.blue.x, v.blue.y),
|
||||
QColorSpace::TransferFunction::Linear);
|
||||
}
|
||||
if (!cs.isValid()) {
|
||||
cs = QColorSpace(QColorSpace::SRgbLinear);
|
||||
}
|
||||
image.setColorSpace(cs);
|
||||
|
||||
#ifdef EXR_CONVERT_TO_SRGB
|
||||
image.convertToColorSpace(QColorSpace(QColorSpace::SRgb));
|
||||
#endif
|
||||
|
||||
#endif // !EXR_USE_LEGACY_CONVERSIONS
|
||||
}
|
||||
|
||||
bool EXRHandler::read(QImage *outImage)
|
||||
{
|
||||
try {
|
||||
int width;
|
||||
int height;
|
||||
auto d = device();
|
||||
|
||||
K_IStream istr(device(), QByteArray());
|
||||
// set the image position after the first run.
|
||||
if (!d->isSequential()) {
|
||||
if (m_startPos < 0) {
|
||||
m_startPos = d->pos();
|
||||
} else {
|
||||
d->seek(m_startPos);
|
||||
}
|
||||
}
|
||||
|
||||
K_IStream istr(d, QByteArray());
|
||||
Imf::RgbaInputFile file(istr);
|
||||
auto &&header = file.header();
|
||||
|
||||
// set the image to load
|
||||
if (m_imageNumber > -1) {
|
||||
auto views = viewList(header);
|
||||
if (m_imageNumber < views.count()) {
|
||||
file.setLayerName(views.at(m_imageNumber).toStdString());
|
||||
}
|
||||
}
|
||||
|
||||
// get image info
|
||||
Imath::Box2i dw = file.dataWindow();
|
||||
qint32 width = dw.max.x - dw.min.x + 1;
|
||||
qint32 height = dw.max.y - dw.min.y + 1;
|
||||
|
||||
width = dw.max.x - dw.min.x + 1;
|
||||
height = dw.max.y - dw.min.y + 1;
|
||||
// limiting the maximum image size on a reasonable size (as done in other plugins)
|
||||
if (width > EXR_MAX_IMAGE_WIDTH || height > EXR_MAX_IMAGE_HEIGHT) {
|
||||
qWarning() << "The maximum image size is limited to" << EXR_MAX_IMAGE_WIDTH << "x" << EXR_MAX_IMAGE_HEIGHT << "px";
|
||||
return false;
|
||||
}
|
||||
|
||||
QImage image = imageAlloc(width, height, QImage::Format_RGB32);
|
||||
// creating the image
|
||||
QImage image = imageAlloc(width, height, imageFormat(file));
|
||||
if (image.isNull()) {
|
||||
qWarning() << "Failed to allocate image, invalid size?" << QSize(width, height);
|
||||
return false;
|
||||
}
|
||||
|
||||
Imf::Array2D<Imf::Rgba> pixels;
|
||||
pixels.resizeErase(height, width);
|
||||
|
||||
file.setFrameBuffer(&pixels[0][0] - dw.min.x - dw.min.y * width, 1, width);
|
||||
file.readPixels(dw.min.y, dw.max.y);
|
||||
pixels.resizeErase(EXR_LINES_PER_BLOCK, width);
|
||||
bool isRgba = image.hasAlphaChannel();
|
||||
|
||||
// somehow copy pixels into image
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
// copy pixels(x,y) into image(x,y)
|
||||
image.setPixel(x, y, RgbaToQrgba(pixels[y][x]));
|
||||
for (int y = 0, n = 0; y < height; y += n) {
|
||||
auto my = dw.min.y + y;
|
||||
if (my > dw.max.y) { // paranoia check
|
||||
break;
|
||||
}
|
||||
|
||||
file.setFrameBuffer(&pixels[0][0] - dw.min.x - qint64(my) * width, 1, width);
|
||||
file.readPixels(my, std::min(my + EXR_LINES_PER_BLOCK - 1, dw.max.y));
|
||||
|
||||
for (n = 0; n < std::min(EXR_LINES_PER_BLOCK, height - y); ++n) {
|
||||
#if defined(EXR_USE_LEGACY_CONVERSIONS)
|
||||
Q_UNUSED(isRgba)
|
||||
auto scanLine = reinterpret_cast<QRgb *>(image.scanLine(y + n));
|
||||
for (int x = 0; x < width; ++x) {
|
||||
*(scanLine + x) = RgbaToQrgba(pixels[n][x]);
|
||||
}
|
||||
#elif defined(EXR_USE_QT6_FLOAT_IMAGE)
|
||||
auto scanLine = reinterpret_cast<qfloat16 *>(image.scanLine(y + n));
|
||||
for (int x = 0; x < width; ++x) {
|
||||
auto xcs = x * 4;
|
||||
*(scanLine + xcs) = qfloat16(qBound(0.f, float(pixels[n][x].r), 1.f));
|
||||
*(scanLine + xcs + 1) = qfloat16(qBound(0.f, float(pixels[n][x].g), 1.f));
|
||||
*(scanLine + xcs + 2) = qfloat16(qBound(0.f, float(pixels[n][x].b), 1.f));
|
||||
*(scanLine + xcs + 3) = qfloat16(isRgba ? qBound(0.f, float(pixels[n][x].a), 1.f) : 1.f);
|
||||
}
|
||||
#else
|
||||
auto scanLine = reinterpret_cast<QRgba64 *>(image.scanLine(y + n));
|
||||
for (int x = 0; x < width; ++x) {
|
||||
*(scanLine + x) = QRgba64::fromRgba64(quint16(qBound(0.f, float(pixels[n][x].r) * 65535.f + 0.5f, 65535.f)),
|
||||
quint16(qBound(0.f, float(pixels[n][x].g) * 65535.f + 0.5f, 65535.f)),
|
||||
quint16(qBound(0.f, float(pixels[n][x].b) * 65535.f + 0.5f, 65535.f)),
|
||||
isRgba ? quint16(qBound(0.f, float(pixels[n][x].a) * 65535.f + 0.5f, 65535.f)) : quint16(65535));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// set some useful metadata
|
||||
readMetadata(header, image);
|
||||
// final color operations
|
||||
readColorSpace(header, image);
|
||||
|
||||
*outImage = image;
|
||||
|
||||
return true;
|
||||
} catch (const std::exception &exc) {
|
||||
// qDebug() << exc.what();
|
||||
} catch (const std::exception &) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief makePreview
|
||||
* Creates a preview of maximum 256 x 256 pixels from the \a image.
|
||||
*/
|
||||
bool makePreview(const QImage &image, Imf::Array2D<Imf::PreviewRgba> &pixels)
|
||||
{
|
||||
auto w = image.width();
|
||||
auto h = image.height();
|
||||
|
||||
QImage preview;
|
||||
if (w > h) {
|
||||
preview = image.scaledToWidth(256).convertToFormat(QImage::Format_ARGB32);
|
||||
} else {
|
||||
preview = image.scaledToHeight(256).convertToFormat(QImage::Format_ARGB32);
|
||||
}
|
||||
if (preview.isNull()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
w = preview.width();
|
||||
h = preview.height();
|
||||
pixels.resizeErase(h, w);
|
||||
preview.convertToColorSpace(QColorSpace(QColorSpace::SRgb));
|
||||
|
||||
for (int y = 0; y < h; ++y) {
|
||||
auto scanLine = reinterpret_cast<const QRgb *>(preview.constScanLine(y));
|
||||
for (int x = 0; x < w; ++x) {
|
||||
auto &&out = pixels[y][x];
|
||||
out.r = qRed(*(scanLine + x));
|
||||
out.g = qGreen(*(scanLine + x));
|
||||
out.b = qBlue(*(scanLine + x));
|
||||
out.a = qAlpha(*(scanLine + x));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief setMetadata
|
||||
* Reades the metadata from \a image and set its as attributes in the \a header.
|
||||
*/
|
||||
static void setMetadata(const QImage &image, Imf::Header &header)
|
||||
{
|
||||
auto dateTime = QDateTime::currentDateTime();
|
||||
for (auto &&key : image.textKeys()) {
|
||||
auto text = image.text(key);
|
||||
if (!key.compare(QStringLiteral("Comment"), Qt::CaseInsensitive)) {
|
||||
header.insert("comments", Imf::StringAttribute(text.toStdString()));
|
||||
}
|
||||
|
||||
if (!key.compare(QStringLiteral("Owner"), Qt::CaseInsensitive)) {
|
||||
header.insert("owner", Imf::StringAttribute(text.toStdString()));
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
if (!key.compare(QStringLiteral("Latitude"), Qt::CaseInsensitive) ||
|
||||
!key.compare(QStringLiteral("Longitude"), Qt::CaseInsensitive) ||
|
||||
!key.compare(QStringLiteral("Altitude"), Qt::CaseInsensitive)) {
|
||||
// clang-format on
|
||||
auto ok = false;
|
||||
auto value = QLocale::c().toFloat(text, &ok);
|
||||
if (ok) {
|
||||
header.insert(qPrintable(key.toLower()), Imf::FloatAttribute(value));
|
||||
}
|
||||
}
|
||||
|
||||
if (!key.compare(QStringLiteral("CreationDate"), Qt::CaseInsensitive)) {
|
||||
auto dt = QDateTime::fromString(text, Qt::ISODate);
|
||||
if (dt.isValid()) {
|
||||
dateTime = dt;
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef EXR_DISABLE_XMP_ATTRIBUTE // warning: Non-standard attribute!
|
||||
if (!key.compare(QStringLiteral("XML:com.adobe.xmp"), Qt::CaseInsensitive)) {
|
||||
header.insert("xmp", Imf::StringAttribute(text.toStdString()));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
if (dateTime.isValid()) {
|
||||
header.insert("capDate", Imf::StringAttribute(dateTime.toString(QStringLiteral("yyyy:MM:dd HH:mm:ss")).toStdString()));
|
||||
header.insert("utcOffset", Imf::FloatAttribute(dateTime.offsetFromUtc()));
|
||||
}
|
||||
|
||||
if (image.dotsPerMeterX() && image.dotsPerMeterY()) {
|
||||
header.insert("xDensity", Imf::FloatAttribute(image.dotsPerMeterX() * 2.54f / 100.f));
|
||||
header.insert("pixelAspectRatio", Imf::FloatAttribute(float(image.dotsPerMeterX()) / float(image.dotsPerMeterY())));
|
||||
}
|
||||
|
||||
// set default chroma (default constructor ITU-R BT.709-3 -> sRGB)
|
||||
// The image is converted to Linear sRGB so, the chroma is the default EXR value.
|
||||
// If a file 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());
|
||||
Imf::RgbaOutputFile file(ostr, header, image.hasAlphaChannel() ? Imf::RgbaChannels::WRITE_RGBA : Imf::RgbaChannels::WRITE_RGB);
|
||||
Imf::Array2D<Imf::Rgba> pixels;
|
||||
pixels.resizeErase(EXR_LINES_PER_BLOCK, width);
|
||||
|
||||
// convert the image and write into the stream
|
||||
#if defined(EXR_USE_QT6_FLOAT_IMAGE)
|
||||
auto convFormat = image.hasAlphaChannel() ? QImage::Format_RGBA16FPx4 : QImage::Format_RGBX16FPx4;
|
||||
#else
|
||||
auto convFormat = image.hasAlphaChannel() ? QImage::Format_RGBA64 : QImage::Format_RGBX64;
|
||||
#endif
|
||||
ScanLineConverter slc(convFormat);
|
||||
slc.setDefaultSourceColorSpace(QColorSpace(QColorSpace::SRgb));
|
||||
slc.setTargetColorSpace(QColorSpace(QColorSpace::SRgbLinear));
|
||||
for (int y = 0, n = 0; y < height; y += n) {
|
||||
for (n = 0; n < std::min(EXR_LINES_PER_BLOCK, height - y); ++n) {
|
||||
#if defined(EXR_USE_QT6_FLOAT_IMAGE)
|
||||
auto scanLine = reinterpret_cast<const qfloat16 *>(slc.convertedScanLine(image, y + n));
|
||||
if (scanLine == nullptr) {
|
||||
return false;
|
||||
}
|
||||
for (int x = 0; x < width; ++x) {
|
||||
auto xcs = x * 4;
|
||||
pixels[n][x].r = float(*(scanLine + xcs));
|
||||
pixels[n][x].g = float(*(scanLine + xcs + 1));
|
||||
pixels[n][x].b = float(*(scanLine + xcs + 2));
|
||||
pixels[n][x].a = float(*(scanLine + xcs + 3));
|
||||
}
|
||||
#else
|
||||
auto scanLine = reinterpret_cast<const QRgba64 *>(slc.convertedScanLine(image, y + n));
|
||||
if (scanLine == nullptr) {
|
||||
return false;
|
||||
}
|
||||
for (int x = 0; x < width; ++x) {
|
||||
pixels[n][x].r = float((scanLine + x)->red() / 65535.f);
|
||||
pixels[n][x].g = float((scanLine + x)->green() / 65535.f);
|
||||
pixels[n][x].b = float((scanLine + x)->blue() / 65535.f);
|
||||
pixels[n][x].a = float((scanLine + x)->alpha() / 65535.f);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
file.setFrameBuffer(&pixels[0][0] - qint64(y) * width, 1, width);
|
||||
file.writePixels(n);
|
||||
}
|
||||
} catch (const std::exception &) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void EXRHandler::setOption(ImageOption option, const QVariant &value)
|
||||
{
|
||||
if (option == QImageIOHandler::CompressionRatio) {
|
||||
auto ok = false;
|
||||
auto cr = value.toInt(&ok);
|
||||
if (ok) {
|
||||
m_compressionRatio = cr;
|
||||
}
|
||||
}
|
||||
if (option == QImageIOHandler::Quality) {
|
||||
auto ok = false;
|
||||
auto q = value.toInt(&ok);
|
||||
if (ok) {
|
||||
m_quality = q;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool EXRHandler::supportsOption(ImageOption option) const
|
||||
{
|
||||
if (option == QImageIOHandler::Size) {
|
||||
return true;
|
||||
}
|
||||
if (option == QImageIOHandler::ImageFormat) {
|
||||
return true;
|
||||
}
|
||||
if (option == QImageIOHandler::CompressionRatio) {
|
||||
return true;
|
||||
}
|
||||
if (option == QImageIOHandler::Quality) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QVariant EXRHandler::option(ImageOption option) const
|
||||
{
|
||||
QVariant v;
|
||||
|
||||
if (option == QImageIOHandler::Size) {
|
||||
if (auto d = device()) {
|
||||
// transactions works on both random and sequential devices
|
||||
d->startTransaction();
|
||||
try {
|
||||
K_IStream istr(d, QByteArray());
|
||||
Imf::RgbaInputFile file(istr);
|
||||
if (m_imageNumber > -1) { // set the image to read
|
||||
auto views = viewList(file.header());
|
||||
if (m_imageNumber < views.count()) {
|
||||
file.setLayerName(views.at(m_imageNumber).toStdString());
|
||||
}
|
||||
}
|
||||
Imath::Box2i dw = file.dataWindow();
|
||||
v = QVariant(QSize(dw.max.x - dw.min.x + 1, dw.max.y - dw.min.y + 1));
|
||||
} catch (const std::exception &) {
|
||||
// broken file or unsupported version
|
||||
}
|
||||
d->rollbackTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
if (option == QImageIOHandler::ImageFormat) {
|
||||
if (auto d = device()) {
|
||||
// transactions works on both random and sequential devices
|
||||
d->startTransaction();
|
||||
try {
|
||||
K_IStream istr(d, QByteArray());
|
||||
Imf::RgbaInputFile file(istr);
|
||||
v = QVariant::fromValue(imageFormat(file));
|
||||
} catch (const std::exception &) {
|
||||
// broken file or unsupported version
|
||||
}
|
||||
d->rollbackTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
if (option == QImageIOHandler::CompressionRatio) {
|
||||
v = QVariant(m_compressionRatio);
|
||||
}
|
||||
|
||||
if (option == QImageIOHandler::Quality) {
|
||||
v = QVariant(m_quality);
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
bool EXRHandler::jumpToNextImage()
|
||||
{
|
||||
return jumpToImage(m_imageNumber + 1);
|
||||
}
|
||||
|
||||
bool EXRHandler::jumpToImage(int imageNumber)
|
||||
{
|
||||
if (imageNumber < 0 || imageNumber >= imageCount()) {
|
||||
return false;
|
||||
}
|
||||
m_imageNumber = imageNumber;
|
||||
return true;
|
||||
}
|
||||
|
||||
int EXRHandler::imageCount() const
|
||||
{
|
||||
// NOTE: image count is cached for performance reason
|
||||
auto &&count = m_imageCount;
|
||||
if (count > 0) {
|
||||
return count;
|
||||
}
|
||||
|
||||
count = QImageIOHandler::imageCount();
|
||||
|
||||
auto d = device();
|
||||
d->startTransaction();
|
||||
|
||||
try {
|
||||
K_IStream istr(d, QByteArray());
|
||||
Imf::RgbaInputFile file(istr);
|
||||
auto views = viewList(file.header());
|
||||
if (!views.isEmpty()) {
|
||||
count = views.size();
|
||||
}
|
||||
} catch (const std::exception &) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
d->rollbackTransaction();
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
int EXRHandler::currentImageNumber() const
|
||||
{
|
||||
return m_imageNumber;
|
||||
}
|
||||
|
||||
bool EXRHandler::canRead(QIODevice *device)
|
||||
{
|
||||
if (!device) {
|
||||
@ -236,7 +821,7 @@ bool EXRHandler::canRead(QIODevice *device)
|
||||
QImageIOPlugin::Capabilities EXRPlugin::capabilities(QIODevice *device, const QByteArray &format) const
|
||||
{
|
||||
if (format == "exr") {
|
||||
return Capabilities(CanRead);
|
||||
return Capabilities(CanRead | CanWrite);
|
||||
}
|
||||
if (!format.isEmpty()) {
|
||||
return {};
|
||||
@ -249,6 +834,9 @@ QImageIOPlugin::Capabilities EXRPlugin::capabilities(QIODevice *device, const QB
|
||||
if (device->isReadable() && EXRHandler::canRead(device)) {
|
||||
cap |= CanRead;
|
||||
}
|
||||
if (device->isWritable()) {
|
||||
cap |= CanWrite;
|
||||
}
|
||||
return cap;
|
||||
}
|
||||
|
||||
@ -259,3 +847,5 @@ QImageIOHandler *EXRPlugin::create(QIODevice *device, const QByteArray &format)
|
||||
handler->setFormat(format);
|
||||
return handler;
|
||||
}
|
||||
|
||||
#include "moc_exr_p.cpp"
|
||||
|
@ -1,7 +0,0 @@
|
||||
[Desktop Entry]
|
||||
Type=Service
|
||||
X-KDE-ServiceTypes=QImageIOPlugins
|
||||
X-KDE-ImageFormat=exr
|
||||
X-KDE-MimeType=image/x-exr
|
||||
X-KDE-Read=true
|
||||
X-KDE-Write=false
|
@ -1,8 +1,8 @@
|
||||
/*
|
||||
QImageIO Routines to read (and perhaps in the future, write) images
|
||||
in the high definition EXR format.
|
||||
The high dynamic range EXR format support for QImage.
|
||||
|
||||
SPDX-FileCopyrightText: 2003 Brad Hards <bradh@frogmouth.net>
|
||||
SPDX-FileCopyrightText: 2023 Mirco Miranda <mircomir@outlook.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
@ -12,6 +12,44 @@
|
||||
|
||||
#include <QImageIOPlugin>
|
||||
|
||||
/*!
|
||||
* \brief The EXRHandler class
|
||||
* The handler uses the OpenEXR reference implementation of the EXR file format.
|
||||
*
|
||||
* The purpose of EXR format is to accurately and efficiently represent high-dynamic-range scene-linear
|
||||
* image data and associated metadata.
|
||||
*
|
||||
* Both reading and writing of EXR files is supported. When saving, the image is converted to 16-bit
|
||||
* and sRGB Linear color space (if not already so). If no color space is set in the image, sRGB is assumed.
|
||||
* When the handler is compiled with the default compile options, the loaded image is a 16-bit image
|
||||
* with linear color space.
|
||||
* Multiview images are also supported (read only) via imageCount(), jumpToImage(), etc....
|
||||
*
|
||||
* The following QImageIOHandler options are supported:
|
||||
* - ImageFormat: The image's data format returned by the handler.
|
||||
* - Size: The size of the image.
|
||||
* - CompressionRatio: The compression ratio of the image data (see OpenEXR compression schemes).
|
||||
* - Quality: The quality level of the image (see OpenEXR compression level of lossy codecs).
|
||||
*
|
||||
* The following metadata are set/get via QImage::setText()/QImage::text() in both read/write (if any):
|
||||
* - Latitude, Longitude, Altitude: Geographic coordinates (Float converted to string).
|
||||
* - CreationDate: Date the image was captured/created (QDateTime converted to string using Qt::ISODate).
|
||||
* - Comment: Additional image information in human-readable form, for example a verbal description of the image (QString).
|
||||
* - Owner: Name of the owner of the image (QString).
|
||||
*
|
||||
* In addition, information about image resolution is preserved and the preview is written for images larger
|
||||
* than 1024px.
|
||||
*
|
||||
* The following compile options are supported (defines):
|
||||
* - EXR_MAX_IMAGE_WIDTH: Maximum image width supported (read/write, default: 300000 px).
|
||||
* - EXR_MAX_IMAGE_HEIGHT: Maximum image height supported (read/write, default: 300000 px).
|
||||
* - EXR_LINES_PER_BLOCK: The number of scanlines buffered on both read and write operations.\n
|
||||
* The higher the value, the greater the parallelization but the RAM consumption increases (default: 128)
|
||||
* - EXR_USE_LEGACY_CONVERSIONS: The result image is an 8-bit RGB(A) converted without icc profiles (read, default: undefined).
|
||||
* - EXR_CONVERT_TO_SRGB: The resulting image is convertef in the sRGB color space (read, default: undefined).
|
||||
* - EXR_DISABLE_XMP_ATTRIBUTE: Disable the stores of XMP values in a non-standard attribute named "xmp".\n
|
||||
* The QImage metadata used is "XML:com.adobe.xmp" (write, default: undefined).
|
||||
*/
|
||||
class EXRHandler : public QImageIOHandler
|
||||
{
|
||||
public:
|
||||
@ -19,8 +57,65 @@ public:
|
||||
|
||||
bool canRead() const override;
|
||||
bool read(QImage *outImage) override;
|
||||
bool write(const QImage &image) override;
|
||||
|
||||
void setOption(ImageOption option, const QVariant &value) override;
|
||||
bool supportsOption(QImageIOHandler::ImageOption option) const override;
|
||||
QVariant option(QImageIOHandler::ImageOption option) const override;
|
||||
|
||||
bool jumpToNextImage() override;
|
||||
bool jumpToImage(int imageNumber) override;
|
||||
int imageCount() const override;
|
||||
int currentImageNumber() const override;
|
||||
|
||||
static bool canRead(QIODevice *device);
|
||||
|
||||
private:
|
||||
/*!
|
||||
* \brief m_compressionRatio
|
||||
* Value set by QImageWriter::setCompression().
|
||||
*
|
||||
* The compression scheme is the same as defined by OpenEXR library:
|
||||
* - 0: no compression
|
||||
* - 1: run length encoding
|
||||
* - 2: zlib compression, one scan line at a time
|
||||
* - 3: zlib compression, in blocks of 16 scan lines
|
||||
* - 4: piz-based wavelet compression (default)
|
||||
* - 5: lossy 24-bit float compression
|
||||
* - 6: lossy 4-by-4 pixel block compression, fixed compression rate
|
||||
* - 7: lossy 4-by-4 pixel block compression, fields are compressed more
|
||||
* - 8: lossy DCT based compression, in blocks of 32 scanlines. More efficient for partial buffer access.
|
||||
* - 9: lossy DCT based compression, in blocks of 256 scanlines. More efficient space wise and faster to decode full frames than DWAA_COMPRESSION.
|
||||
*/
|
||||
qint32 m_compressionRatio;
|
||||
|
||||
/*!
|
||||
* \brief m_quality
|
||||
* Value set by QImageWriter::setQuality().
|
||||
*
|
||||
* The quality is used on DCT compression schemes only with a
|
||||
* supposed value between 0 and 100 (default: 45).
|
||||
*/
|
||||
qint32 m_quality;
|
||||
|
||||
/*!
|
||||
* \brief m_imageNumber
|
||||
* Value set by QImageReader::jumpToImage() or QImageReader::jumpToNextImage().
|
||||
* The number of view selected in a multiview image.
|
||||
*/
|
||||
qint32 m_imageNumber;
|
||||
|
||||
/*!
|
||||
* \brief m_imageCount
|
||||
* The total number of views (cache value)
|
||||
*/
|
||||
mutable qint32 m_imageCount;
|
||||
|
||||
/*!
|
||||
* \brief m_startPos
|
||||
* The initial device position to allow multi image load (cache value).
|
||||
*/
|
||||
qint64 m_startPos;
|
||||
};
|
||||
|
||||
class EXRPlugin : public QImageIOPlugin
|
||||
|
@ -46,33 +46,6 @@ typedef enum {
|
||||
|
||||
// From GIMP "libgimp/gimpenums.h" v2.4
|
||||
|
||||
//! Effect to apply when layers are merged together.
|
||||
|
||||
typedef enum {
|
||||
NORMAL_MODE,
|
||||
DISSOLVE_MODE,
|
||||
BEHIND_MODE,
|
||||
MULTIPLY_MODE,
|
||||
SCREEN_MODE,
|
||||
OVERLAY_MODE,
|
||||
DIFFERENCE_MODE,
|
||||
ADDITION_MODE,
|
||||
SUBTRACT_MODE,
|
||||
DARKEN_ONLY_MODE,
|
||||
LIGHTEN_ONLY_MODE,
|
||||
HUE_MODE,
|
||||
SATURATION_MODE,
|
||||
COLOR_MODE,
|
||||
VALUE_MODE,
|
||||
DIVIDE_MODE,
|
||||
DODGE_MODE,
|
||||
BURN_MODE,
|
||||
HARDLIGHT_MODE,
|
||||
SOFTLIGHT_MODE,
|
||||
GRAIN_EXTRACT_MODE,
|
||||
GRAIN_MERGE_MODE
|
||||
} LayerModeEffects;
|
||||
|
||||
// From GIMP "paint_funcs.c" v1.2
|
||||
|
||||
/*!
|
||||
|
@ -9,13 +9,20 @@
|
||||
#include "hdr_p.h"
|
||||
#include "util_p.h"
|
||||
|
||||
#include <QColorSpace>
|
||||
#include <QDataStream>
|
||||
#include <QFloat16>
|
||||
#include <QImage>
|
||||
#include <QLoggingCategory>
|
||||
#include <QRegularExpressionMatch>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
/* *** HDR_HALF_QUALITY ***
|
||||
* If defined, a 16-bits float image is created, otherwise a 32-bits float ones (default).
|
||||
*/
|
||||
//#define HDR_HALF_QUALITY // default commented -> you should define it in your cmake file
|
||||
|
||||
typedef unsigned char uchar;
|
||||
|
||||
Q_LOGGING_CATEGORY(HDRPLUGIN, "kf.imageformats.plugins.hdr", QtWarningMsg)
|
||||
@ -26,15 +33,6 @@ namespace // Private.
|
||||
#define MINELEN 8 // minimum scanline length for encoding
|
||||
#define MAXELEN 0x7fff // maximum scanline length for encoding
|
||||
|
||||
static inline uchar ClipToByte(float value)
|
||||
{
|
||||
if (value > 255.0f) {
|
||||
return 255;
|
||||
}
|
||||
// else if (value < 0.0f) return 0; // we know value is positive.
|
||||
return uchar(value);
|
||||
}
|
||||
|
||||
// read an old style line from the hdr image file
|
||||
// if 'first' is true the first byte is already read
|
||||
static bool Read_Old_Line(uchar *image, int width, QDataStream &s)
|
||||
@ -42,6 +40,7 @@ static bool Read_Old_Line(uchar *image, int width, QDataStream &s)
|
||||
int rshift = 0;
|
||||
int i;
|
||||
|
||||
uchar *start = image;
|
||||
while (width > 0) {
|
||||
s >> image[0];
|
||||
s >> image[1];
|
||||
@ -53,7 +52,14 @@ static bool Read_Old_Line(uchar *image, int width, QDataStream &s)
|
||||
}
|
||||
|
||||
if ((image[0] == 1) && (image[1] == 1) && (image[2] == 1)) {
|
||||
for (i = image[3] << rshift; i > 0; i--) {
|
||||
// NOTE: we don't have an image sample that cover this code
|
||||
if (rshift > 31) {
|
||||
return false;
|
||||
}
|
||||
for (i = image[3] << rshift; i > 0 && width > 0; i--) {
|
||||
if (image == start) {
|
||||
return false; // you cannot be here at the first run
|
||||
}
|
||||
// memcpy(image, image-4, 4);
|
||||
(uint &)image[0] = (uint &)image[0 - 4];
|
||||
image += 4;
|
||||
@ -69,24 +75,38 @@ static bool Read_Old_Line(uchar *image, int width, QDataStream &s)
|
||||
return true;
|
||||
}
|
||||
|
||||
static void RGBE_To_QRgbLine(uchar *image, QRgb *scanline, int width)
|
||||
template<class float_T>
|
||||
void RGBE_To_QRgbLine(uchar *image, float_T *scanline, int width)
|
||||
{
|
||||
for (int j = 0; j < width; j++) {
|
||||
// v = ldexp(1.0, int(image[3]) - 128);
|
||||
float v;
|
||||
int e = int(image[3]) - 128;
|
||||
int e = qBound(-31, int(image[3]) - 128, 31);
|
||||
if (e > 0) {
|
||||
v = float(1 << e);
|
||||
} else {
|
||||
v = 1.0f / float(1 << -e);
|
||||
}
|
||||
|
||||
scanline[j] = qRgb(ClipToByte(float(image[0]) * v), ClipToByte(float(image[1]) * v), ClipToByte(float(image[2]) * v));
|
||||
|
||||
auto j4 = j * 4;
|
||||
auto vn = v / 255.0f;
|
||||
scanline[j4] = float_T(std::min(float(image[0]) * vn, 1.0f));
|
||||
scanline[j4 + 1] = float_T(std::min(float(image[1]) * vn, 1.0f));
|
||||
scanline[j4 + 2] = float_T(std::min(float(image[2]) * vn, 1.0f));
|
||||
scanline[j4 + 3] = float_T(1.0f);
|
||||
image += 4;
|
||||
}
|
||||
}
|
||||
|
||||
QImage::Format imageFormat()
|
||||
{
|
||||
#ifdef HDR_HALF_QUALITY
|
||||
return QImage::Format_RGBX16FPx4;
|
||||
#else
|
||||
return QImage::Format_RGBX32FPx4;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Load the HDR image.
|
||||
static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &img)
|
||||
{
|
||||
@ -94,7 +114,7 @@ static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &i
|
||||
uchar code;
|
||||
|
||||
// Create dst image.
|
||||
img = imageAlloc(width, height, QImage::Format_RGB32);
|
||||
img = imageAlloc(width, height, imageFormat());
|
||||
if (img.isNull()) {
|
||||
qCDebug(HDRPLUGIN) << "Couldn't create image with size" << width << height << "and format RGB32";
|
||||
return false;
|
||||
@ -102,10 +122,14 @@ static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &i
|
||||
|
||||
QByteArray lineArray;
|
||||
lineArray.resize(4 * width);
|
||||
uchar *image = (uchar *)lineArray.data();
|
||||
uchar *image = reinterpret_cast<uchar *>(lineArray.data());
|
||||
|
||||
for (int cline = 0; cline < height; cline++) {
|
||||
QRgb *scanline = (QRgb *)img.scanLine(cline);
|
||||
#ifdef HDR_HALF_QUALITY
|
||||
auto scanline = reinterpret_cast<qfloat16 *>(img.scanLine(cline));
|
||||
#else
|
||||
auto scanline = reinterpret_cast<float *>(img.scanLine(cline));
|
||||
#endif
|
||||
|
||||
// determine scanline type
|
||||
if ((width < MINELEN) || (MAXELEN < width)) {
|
||||
@ -148,7 +172,7 @@ static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &i
|
||||
}
|
||||
|
||||
// read each component
|
||||
for (int i = 0; i < 4; i++) {
|
||||
for (int i = 0, len = int(lineArray.size()); i < 4; i++) {
|
||||
for (int j = 0; j < width;) {
|
||||
s >> code;
|
||||
if (s.atEnd()) {
|
||||
@ -160,14 +184,20 @@ static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &i
|
||||
code &= 127;
|
||||
s >> val;
|
||||
while (code != 0) {
|
||||
image[i + j * 4] = val;
|
||||
auto idx = i + j * 4;
|
||||
if (idx < len) {
|
||||
image[idx] = val;
|
||||
}
|
||||
j++;
|
||||
code--;
|
||||
}
|
||||
} else {
|
||||
// non-run
|
||||
while (code != 0) {
|
||||
s >> image[i + j * 4];
|
||||
auto idx = i + j * 4;
|
||||
if (idx < len) {
|
||||
s >> image[idx];
|
||||
}
|
||||
j++;
|
||||
code--;
|
||||
}
|
||||
@ -181,9 +211,7 @@ static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &i
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool HDRHandler::read(QImage *outImage)
|
||||
static QSize readHeaderSize(QIODevice *device)
|
||||
{
|
||||
int len;
|
||||
QByteArray line(MAXLINE + 1, Qt::Uninitialized);
|
||||
@ -191,7 +219,7 @@ bool HDRHandler::read(QImage *outImage)
|
||||
|
||||
// Parse header
|
||||
do {
|
||||
len = device()->readLine(line.data(), MAXLINE);
|
||||
len = device->readLine(line.data(), MAXLINE);
|
||||
|
||||
if (line.startsWith("FORMAT=")) {
|
||||
format = line.mid(7, len - 7 - 1 /*\n*/);
|
||||
@ -201,10 +229,10 @@ bool HDRHandler::read(QImage *outImage)
|
||||
|
||||
if (format != "32-bit_rle_rgbe") {
|
||||
qCDebug(HDRPLUGIN) << "Unknown HDR format:" << format;
|
||||
return false;
|
||||
return QSize();
|
||||
}
|
||||
|
||||
len = device()->readLine(line.data(), MAXLINE);
|
||||
len = device->readLine(line.data(), MAXLINE);
|
||||
line.resize(len);
|
||||
|
||||
/*
|
||||
@ -224,29 +252,75 @@ bool HDRHandler::read(QImage *outImage)
|
||||
QRegularExpressionMatch match = resolutionRegExp.match(QString::fromLatin1(line));
|
||||
if (!match.hasMatch()) {
|
||||
qCDebug(HDRPLUGIN) << "Invalid HDR file, the first line after the header didn't have the expected format:" << line;
|
||||
return false;
|
||||
return QSize();
|
||||
}
|
||||
|
||||
if ((match.captured(1).at(1) != u'Y') || (match.captured(3).at(1) != u'X')) {
|
||||
qCDebug(HDRPLUGIN) << "Unsupported image orientation in HDR file.";
|
||||
return QSize();
|
||||
}
|
||||
|
||||
return QSize(match.captured(4).toInt(), match.captured(2).toInt());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool HDRHandler::read(QImage *outImage)
|
||||
{
|
||||
QDataStream s(device());
|
||||
|
||||
QSize size = readHeaderSize(s.device());
|
||||
if (!size.isValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const int width = match.captured(4).toInt();
|
||||
const int height = match.captured(2).toInt();
|
||||
|
||||
QDataStream s(device());
|
||||
|
||||
QImage img;
|
||||
if (!LoadHDR(s, width, height, img)) {
|
||||
if (!LoadHDR(s, size.width(), size.height(), img)) {
|
||||
// qDebug() << "Error loading HDR file.";
|
||||
return false;
|
||||
}
|
||||
// The images read by Gimp and Photoshop (including those of the tests) are interpreted with linear color space.
|
||||
// By setting the linear color space, programs that support profiles display HDR files as in GIMP and Photoshop.
|
||||
img.setColorSpace(QColorSpace(QColorSpace::SRgbLinear));
|
||||
|
||||
*outImage = img;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HDRHandler::supportsOption(ImageOption option) const
|
||||
{
|
||||
if (option == QImageIOHandler::Size) {
|
||||
return true;
|
||||
}
|
||||
if (option == QImageIOHandler::ImageFormat) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QVariant HDRHandler::option(ImageOption option) const
|
||||
{
|
||||
QVariant v;
|
||||
|
||||
if (option == QImageIOHandler::Size) {
|
||||
if (auto d = device()) {
|
||||
// transactions works on both random and sequential devices
|
||||
d->startTransaction();
|
||||
auto size = readHeaderSize(d);
|
||||
d->rollbackTransaction();
|
||||
if (size.isValid()) {
|
||||
v = QVariant::fromValue(size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (option == QImageIOHandler::ImageFormat) {
|
||||
v = QVariant::fromValue(imageFormat());
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
HDRHandler::HDRHandler()
|
||||
{
|
||||
}
|
||||
@ -267,7 +341,20 @@ bool HDRHandler::canRead(QIODevice *device)
|
||||
return false;
|
||||
}
|
||||
|
||||
return device->peek(11) == "#?RADIANCE\n" || device->peek(7) == "#?RGBE\n";
|
||||
// the .pic taken from official test cases does not start with this string but can be loaded.
|
||||
if(device->peek(11) == "#?RADIANCE\n" || device->peek(7) == "#?RGBE\n") {
|
||||
return true;
|
||||
}
|
||||
|
||||
// allow to load offical test cases: https://radsite.lbl.gov/radiance/framed.html
|
||||
device->startTransaction();
|
||||
QSize size = readHeaderSize(device);
|
||||
device->rollbackTransaction();
|
||||
if (size.isValid()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
QImageIOPlugin::Capabilities HDRPlugin::capabilities(QIODevice *device, const QByteArray &format) const
|
||||
@ -296,3 +383,5 @@ QImageIOHandler *HDRPlugin::create(QIODevice *device, const QByteArray &format)
|
||||
handler->setFormat(format);
|
||||
return handler;
|
||||
}
|
||||
|
||||
#include "moc_hdr_p.cpp"
|
||||
|