Compare commits
62 Commits
Author | SHA1 | Date | |
---|---|---|---|
df82311a10 | |||
1e47a751df | |||
09b9ff7bf9 | |||
ee77e349e3 | |||
4168c46964 | |||
f9f29304d8 | |||
ec0918d962 | |||
dadff2791c | |||
106279d32e | |||
f5962442ca | |||
7cc4cb8d0c | |||
2bf3a859fc | |||
3ce3ab2364 | |||
c09b88b7c6 | |||
8cc25c39b4 | |||
43c80793ac | |||
e90bca4924 | |||
bfc73ca260 | |||
f6bb59228e | |||
ee381958b2 | |||
478e49b8a6 | |||
c3daf86079 | |||
a981cefdd2 | |||
723f72930b | |||
99bb24803a | |||
63a9de758f | |||
240e28aac5 | |||
906ecce500 | |||
b2b677b8a5 | |||
bcec942cc9 | |||
66cb8c91d0 | |||
899a2df42d | |||
47920ed63c | |||
274f30e008 | |||
4348a09733 | |||
dd4576a472 | |||
9438540735 | |||
cf78907ff4 | |||
bcb5308545 | |||
c3a91c3bc6 | |||
034b8f331b | |||
ed6a3c520d | |||
bf1c7e8508 | |||
3cb6519dcc | |||
6cbf7529ee | |||
55227815d5 | |||
64d51ed610 | |||
2ca57c9c59 | |||
f7fd14d418 | |||
c9aa1ff629 | |||
91d3bd5227 | |||
bb66367bc8 | |||
14770318a3 | |||
9b1fafe29b | |||
fa673b5df8 | |||
e96b43aef5 | |||
64f3303ef0 | |||
63056c52f9 | |||
2997f7ae8d | |||
0b4741f4b7 | |||
bc52c03981 | |||
c1c57d9a11 |
@ -4,7 +4,8 @@
|
|||||||
include:
|
include:
|
||||||
- project: sysadmin/ci-utilities
|
- project: sysadmin/ci-utilities
|
||||||
file:
|
file:
|
||||||
- /gitlab-templates/linux-qt6.yml
|
- /gitlab-templates/linux.yml
|
||||||
- /gitlab-templates/android-qt6.yml
|
- /gitlab-templates/linux-static.yml
|
||||||
- /gitlab-templates/freebsd-qt6.yml
|
- /gitlab-templates/freebsd.yml
|
||||||
- /gitlab-templates/windows-qt6.yml
|
- /gitlab-templates/windows.yml
|
||||||
|
- /gitlab-templates/windows-static.yml
|
||||||
|
@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.16)
|
|||||||
project(KImageFormats)
|
project(KImageFormats)
|
||||||
|
|
||||||
include(FeatureSummary)
|
include(FeatureSummary)
|
||||||
find_package(ECM 6.0.0 NO_MODULE)
|
find_package(ECM 5.116.0 NO_MODULE)
|
||||||
set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://commits.kde.org/extra-cmake-modules")
|
set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://commits.kde.org/extra-cmake-modules")
|
||||||
feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES)
|
feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES)
|
||||||
|
|
||||||
@ -19,11 +19,11 @@ include(ECMDeprecationSettings)
|
|||||||
include(CheckIncludeFiles)
|
include(CheckIncludeFiles)
|
||||||
include(FindPkgConfig)
|
include(FindPkgConfig)
|
||||||
|
|
||||||
set(REQUIRED_QT_VERSION 6.5.0)
|
set(REQUIRED_QT_VERSION 5.15.2)
|
||||||
find_package(Qt6Gui ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE)
|
find_package(Qt${QT_MAJOR_VERSION}Gui ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE)
|
||||||
|
|
||||||
find_package(KF6Archive)
|
find_package(KF5Archive)
|
||||||
set_package_properties(KF6Archive PROPERTIES
|
set_package_properties(KF5Archive PROPERTIES
|
||||||
TYPE OPTIONAL
|
TYPE OPTIONAL
|
||||||
PURPOSE "Required for the QImage plugin for Krita and OpenRaster images"
|
PURPOSE "Required for the QImage plugin for Krita and OpenRaster images"
|
||||||
)
|
)
|
||||||
@ -32,12 +32,12 @@ set_package_properties(KF6Archive PROPERTIES
|
|||||||
# this available in PATH
|
# this available in PATH
|
||||||
set(BUILD_EPS_PLUGIN FALSE)
|
set(BUILD_EPS_PLUGIN FALSE)
|
||||||
if (UNIX)
|
if (UNIX)
|
||||||
find_package(Qt6PrintSupport ${REQUIRED_QT_VERSION} NO_MODULE)
|
find_package(Qt${QT_MAJOR_VERSION}PrintSupport ${REQUIRED_QT_VERSION} NO_MODULE)
|
||||||
set_package_properties(Qt6PrintSupport PROPERTIES
|
set_package_properties(Qt${QT_MAJOR_VERSION}PrintSupport PROPERTIES
|
||||||
PURPOSE "Required for the QImage plugin for EPS images"
|
PURPOSE "Required for the QImage plugin for EPS images"
|
||||||
TYPE OPTIONAL
|
TYPE OPTIONAL
|
||||||
)
|
)
|
||||||
if (TARGET Qt6::PrintSupport)
|
if (TARGET Qt${QT_MAJOR_VERSION}::PrintSupport)
|
||||||
set(BUILD_EPS_PLUGIN TRUE)
|
set(BUILD_EPS_PLUGIN TRUE)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
@ -81,8 +81,8 @@ set_package_properties(LibRaw PROPERTIES
|
|||||||
)
|
)
|
||||||
|
|
||||||
ecm_set_disabled_deprecation_versions(
|
ecm_set_disabled_deprecation_versions(
|
||||||
QT 6.5
|
QT 5.15.2
|
||||||
KF 5.102
|
KF 5.95
|
||||||
)
|
)
|
||||||
|
|
||||||
add_subdirectory(src)
|
add_subdirectory(src)
|
||||||
@ -91,7 +91,6 @@ if (BUILD_TESTING)
|
|||||||
add_subdirectory(tests)
|
add_subdirectory(tests)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
include(ECMFeatureSummary)
|
feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES)
|
||||||
ecm_feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES)
|
|
||||||
|
|
||||||
kde_configure_git_pre_commit_hook(CHECKS CLANG_FORMAT)
|
kde_configure_git_pre_commit_hook(CHECKS CLANG_FORMAT)
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
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.
|
|
@ -1,11 +0,0 @@
|
|||||||
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.
|
|
12
README.md
@ -16,24 +16,22 @@ The following image formats have read-only support:
|
|||||||
- Animated Windows cursors (ani)
|
- Animated Windows cursors (ani)
|
||||||
- Camera RAW images (arw, cr2, cr3, dcs, dng, ...)
|
- Camera RAW images (arw, cr2, cr3, dcs, dng, ...)
|
||||||
- Gimp (xcf)
|
- Gimp (xcf)
|
||||||
- Krita (kra)
|
- OpenEXR (exr)
|
||||||
- OpenRaster (ora)
|
|
||||||
- Photoshop documents (psd, psb, pdd, psdt)
|
- Photoshop documents (psd, psb, pdd, psdt)
|
||||||
- Radiance HDR (hdr)
|
- Radiance HDR (hdr)
|
||||||
- Sun Raster (im1, im8, im24, im32, ras, sun)
|
- Sun Raster (ras)
|
||||||
|
|
||||||
The following image formats have read and write support:
|
The following image formats have read and write support:
|
||||||
|
|
||||||
- AV1 Image File Format (AVIF)
|
- AV1 Image File Format (AVIF)
|
||||||
- Encapsulated PostScript (eps)
|
- Encapsulated PostScript (eps)
|
||||||
- High Efficiency Image File Format (heif). Can be enabled with the KIMAGEFORMATS_HEIF build option.
|
|
||||||
- JPEG XL (jxl)
|
- JPEG XL (jxl)
|
||||||
- OpenEXR (exr)
|
|
||||||
- Personal Computer Exchange (pcx)
|
- Personal Computer Exchange (pcx)
|
||||||
- Quite OK Image format (qoi)
|
- Quite OK Image format (qoi)
|
||||||
- SGI images (rgb, rgba, sgi, bw)
|
- SGI images (rgb, rgba, sgi, bw)
|
||||||
- Softimage PIC (pic)
|
- Softimage PIC (pic)
|
||||||
- Targa (tga): supports more formats than Qt's version
|
- Targa (tga): supports more formats than Qt's version
|
||||||
|
- XView (xv)
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
@ -45,6 +43,10 @@ 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
|
you are willing to sign the Qt Project contributor agreement, it may be
|
||||||
better to submit the plugin directly to the Qt Project.
|
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
|
## Duplicated Plugins
|
||||||
|
|
||||||
The TGA plugin supports more formats than Qt's own TGA plugin;
|
The TGA plugin supports more formats than Qt's own TGA plugin;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#find_package(Qt5Test ${REQUIRED_QT_VERSION} NO_MODULE)
|
||||||
|
|
||||||
include(ECMMarkAsTest)
|
include(ECMMarkAsTest)
|
||||||
|
|
||||||
add_definitions(-DPLUGIN_DIR="${CMAKE_CURRENT_BINARY_DIR}/../bin")
|
add_definitions(-DPLUGIN_DIR="${CMAKE_CURRENT_BINARY_DIR}/../bin")
|
||||||
@ -12,7 +14,7 @@ macro(kimageformats_read_tests)
|
|||||||
|
|
||||||
if (NOT TARGET readtest)
|
if (NOT TARGET readtest)
|
||||||
add_executable(readtest readtest.cpp)
|
add_executable(readtest readtest.cpp)
|
||||||
target_link_libraries(readtest Qt6::Gui)
|
target_link_libraries(readtest Qt${QT_MAJOR_VERSION}::Gui)
|
||||||
target_compile_definitions(readtest
|
target_compile_definitions(readtest
|
||||||
PRIVATE IMAGEDIR="${CMAKE_CURRENT_SOURCE_DIR}/read")
|
PRIVATE IMAGEDIR="${CMAKE_CURRENT_SOURCE_DIR}/read")
|
||||||
ecm_mark_as_test(readtest)
|
ecm_mark_as_test(readtest)
|
||||||
@ -35,7 +37,7 @@ macro(kimageformats_write_tests)
|
|||||||
|
|
||||||
if (NOT TARGET writetest)
|
if (NOT TARGET writetest)
|
||||||
add_executable(writetest writetest.cpp)
|
add_executable(writetest writetest.cpp)
|
||||||
target_link_libraries(writetest Qt6::Gui)
|
target_link_libraries(writetest Qt${QT_MAJOR_VERSION}::Gui)
|
||||||
target_compile_definitions(writetest
|
target_compile_definitions(writetest
|
||||||
PRIVATE IMAGEDIR="${CMAKE_CURRENT_SOURCE_DIR}/write")
|
PRIVATE IMAGEDIR="${CMAKE_CURRENT_SOURCE_DIR}/write")
|
||||||
ecm_mark_as_test(writetest)
|
ecm_mark_as_test(writetest)
|
||||||
@ -73,7 +75,7 @@ kimageformats_read_tests(
|
|||||||
tga
|
tga
|
||||||
)
|
)
|
||||||
|
|
||||||
if (KF6Archive_FOUND)
|
if (KF5Archive_FOUND)
|
||||||
kimageformats_read_tests(
|
kimageformats_read_tests(
|
||||||
kra
|
kra
|
||||||
ora
|
ora
|
||||||
@ -144,11 +146,6 @@ if (OpenEXR_FOUND)
|
|||||||
kimageformats_read_tests(
|
kimageformats_read_tests(
|
||||||
exr
|
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()
|
endif()
|
||||||
|
|
||||||
if (LibRaw_FOUND)
|
if (LibRaw_FOUND)
|
||||||
@ -157,19 +154,19 @@ if (LibRaw_FOUND)
|
|||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
find_package(Qt6Test ${REQUIRED_QT_VERSION} CONFIG QUIET)
|
find_package(Qt${QT_MAJOR_VERSION}Test ${REQUIRED_QT_VERSION} CONFIG QUIET)
|
||||||
|
|
||||||
if(NOT TARGET Qt6::Test)
|
if(NOT TARGET Qt${QT_MAJOR_VERSION}::Test)
|
||||||
message(STATUS "Qt6Test not found, some autotests will not be built.")
|
message(STATUS "Qt${QT_MAJOR_VERSION}Test not found, some autotests will not be built.")
|
||||||
return()
|
return()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_executable(pictest pictest.cpp)
|
add_executable(pictest pictest.cpp)
|
||||||
target_link_libraries(pictest Qt6::Gui Qt6::Test)
|
target_link_libraries(pictest Qt${QT_MAJOR_VERSION}::Gui Qt${QT_MAJOR_VERSION}::Test)
|
||||||
ecm_mark_as_test(pictest)
|
ecm_mark_as_test(pictest)
|
||||||
add_test(NAME kimageformats-pic COMMAND pictest)
|
add_test(NAME kimageformats-pic COMMAND pictest)
|
||||||
|
|
||||||
add_executable(anitest anitest.cpp)
|
add_executable(anitest anitest.cpp)
|
||||||
target_link_libraries(anitest Qt6::Gui Qt6::Test)
|
target_link_libraries(anitest Qt${QT_MAJOR_VERSION}::Gui Qt${QT_MAJOR_VERSION}::Test)
|
||||||
ecm_mark_as_test(anitest)
|
ecm_mark_as_test(anitest)
|
||||||
add_test(NAME kimageformats-ani COMMAND anitest)
|
add_test(NAME kimageformats-ani COMMAND anitest)
|
||||||
|
Before Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 189 KiB After Width: | Height: | Size: 189 KiB |
Before Width: | Height: | Size: 192 B |
Before Width: | Height: | Size: 470 B |
Before Width: | Height: | Size: 338 B |
Before Width: | Height: | Size: 7.1 KiB |
Before Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 93 KiB |
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 82 KiB |
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 72 KiB |
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 93 KiB |
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 82 KiB |
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 72 KiB |
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 112 KiB |
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 112 KiB |
@ -162,13 +162,13 @@ int main(int argc, char **argv)
|
|||||||
if (!fi.suffix().compare("png", Qt::CaseInsensitive) || !fi.suffix().compare("tif", Qt::CaseInsensitive)) {
|
if (!fi.suffix().compare("png", Qt::CaseInsensitive) || !fi.suffix().compare("tif", Qt::CaseInsensitive)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
int suffixPos = fi.filePath().size() - suffix.size();
|
int suffixPos = fi.filePath().count() - suffix.count();
|
||||||
QString inputfile = fi.filePath();
|
QString inputfile = fi.filePath();
|
||||||
QString fmt = QStringLiteral("png");
|
QString fmt = QStringLiteral("png");
|
||||||
QString expfile = fi.filePath().replace(suffixPos, suffix.size(), fmt);
|
QString expfile = fi.filePath().replace(suffixPos, suffix.count(), fmt);
|
||||||
if (!QFile::exists(expfile)) { // try with tiff
|
if (!QFile::exists(expfile)) { // try with tiff
|
||||||
fmt = QStringLiteral("tif");
|
fmt = QStringLiteral("tif");
|
||||||
expfile = fi.filePath().replace(suffixPos, suffix.size(), fmt);
|
expfile = fi.filePath().replace(suffixPos, suffix.count(), fmt);
|
||||||
}
|
}
|
||||||
QString expfilename = QFileInfo(expfile).fileName();
|
QString expfilename = QFileInfo(expfile).fileName();
|
||||||
|
|
||||||
|
@ -7,7 +7,6 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
#include <QBuffer>
|
#include <QBuffer>
|
||||||
#include <QColorSpace>
|
|
||||||
#include <QCommandLineParser>
|
#include <QCommandLineParser>
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
@ -24,8 +23,8 @@ int main(int argc, char **argv)
|
|||||||
QCoreApplication app(argc, argv);
|
QCoreApplication app(argc, argv);
|
||||||
QCoreApplication::removeLibraryPath(QStringLiteral(PLUGIN_DIR));
|
QCoreApplication::removeLibraryPath(QStringLiteral(PLUGIN_DIR));
|
||||||
QCoreApplication::addLibraryPath(QStringLiteral(PLUGIN_DIR));
|
QCoreApplication::addLibraryPath(QStringLiteral(PLUGIN_DIR));
|
||||||
QCoreApplication::setApplicationName(QStringLiteral("writetest"));
|
QCoreApplication::setApplicationName(QStringLiteral("readtest"));
|
||||||
QCoreApplication::setApplicationVersion(QStringLiteral("1.1.0"));
|
QCoreApplication::setApplicationVersion(QStringLiteral("1.0.0"));
|
||||||
|
|
||||||
QCommandLineParser parser;
|
QCommandLineParser parser;
|
||||||
parser.setApplicationDescription(QStringLiteral("Performs basic image conversion checking."));
|
parser.setApplicationDescription(QStringLiteral("Performs basic image conversion checking."));
|
||||||
@ -86,8 +85,8 @@ int main(int argc, char **argv)
|
|||||||
if (parser.isSet(ignoreDataCheck)) {
|
if (parser.isSet(ignoreDataCheck)) {
|
||||||
pngfile = fi.filePath();
|
pngfile = fi.filePath();
|
||||||
} else {
|
} else {
|
||||||
int suffixPos = fi.filePath().size() - suffix.size();
|
int suffixPos = fi.filePath().count() - suffix.count();
|
||||||
pngfile = fi.filePath().replace(suffixPos, suffix.size(), QStringLiteral("png"));
|
pngfile = fi.filePath().replace(suffixPos, suffix.count(), QStringLiteral("png"));
|
||||||
}
|
}
|
||||||
QString pngfilename = QFileInfo(pngfile).fileName();
|
QString pngfilename = QFileInfo(pngfile).fileName();
|
||||||
|
|
||||||
@ -149,14 +148,6 @@ int main(int argc, char **argv)
|
|||||||
++failed;
|
++failed;
|
||||||
continue;
|
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());
|
reReadImage = reReadImage.convertToFormat(pngImage.format());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,10 +11,9 @@ function(kimageformats_add_plugin plugin)
|
|||||||
message(FATAL_ERROR "kimageformats_add_plugin called without SOURCES parameter")
|
message(FATAL_ERROR "kimageformats_add_plugin called without SOURCES parameter")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
qt_add_plugin(${plugin} PLUGIN_TYPE imageformats)
|
add_library(${plugin} MODULE ${KIF_ADD_PLUGIN_SOURCES})
|
||||||
target_sources(${plugin} PRIVATE ${KIF_ADD_PLUGIN_SOURCES})
|
set_target_properties(${plugin} PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/imageformats")
|
||||||
set_target_properties(${plugin} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/imageformats)
|
target_link_libraries(${plugin} Qt${QT_MAJOR_VERSION}::Gui)
|
||||||
target_link_libraries(${plugin} PRIVATE Qt6::Gui)
|
|
||||||
install(TARGETS ${plugin} DESTINATION ${KDE_INSTALL_QTPLUGINDIR}/imageformats)
|
install(TARGETS ${plugin} DESTINATION ${KDE_INSTALL_QTPLUGINDIR}/imageformats)
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
@ -22,105 +21,176 @@ endfunction()
|
|||||||
|
|
||||||
kimageformats_add_plugin(kimg_ani SOURCES ani.cpp)
|
kimageformats_add_plugin(kimg_ani SOURCES ani.cpp)
|
||||||
|
|
||||||
##################################
|
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||||
|
install(FILES ani.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||||
if (TARGET avif)
|
|
||||||
kimageformats_add_plugin(kimg_avif SOURCES "avif.cpp")
|
|
||||||
target_link_libraries(kimg_avif PRIVATE "avif")
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
##################################
|
##################################
|
||||||
|
|
||||||
if (BUILD_EPS_PLUGIN)
|
if (TARGET avif)
|
||||||
if (TARGET Qt6::PrintSupport)
|
kimageformats_add_plugin(kimg_avif SOURCES "avif.cpp")
|
||||||
kimageformats_add_plugin(kimg_eps SOURCES eps.cpp)
|
target_link_libraries(kimg_avif "avif")
|
||||||
target_link_libraries(kimg_eps PRIVATE Qt6::PrintSupport)
|
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||||
|
install(FILES avif.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
##################################
|
##################################
|
||||||
|
|
||||||
|
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||||
|
install(FILES dds-qt.desktop RENAME dds.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
##################################
|
||||||
|
|
||||||
|
if (BUILD_EPS_PLUGIN)
|
||||||
|
if (TARGET Qt${QT_MAJOR_VERSION}::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()
|
||||||
|
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)
|
if(OpenEXR_FOUND)
|
||||||
kimageformats_add_plugin(kimg_exr SOURCES exr.cpp scanlineconverter.cpp)
|
kimageformats_add_plugin(kimg_exr SOURCES exr.cpp)
|
||||||
if(TARGET OpenEXR::OpenEXR)
|
if(TARGET OpenEXR::OpenEXR)
|
||||||
target_link_libraries(kimg_exr PRIVATE OpenEXR::OpenEXR)
|
target_link_libraries(kimg_exr OpenEXR::OpenEXR)
|
||||||
else()
|
else()
|
||||||
if(OpenEXR_VERSION_STRING VERSION_LESS 2.3.0)
|
if(OpenEXR_VERSION_STRING VERSION_LESS 2.3.0)
|
||||||
# Older OpenEXR versions use dynamic exception specifications, so
|
# Older OpenEXR versions use dynamic exception specifications, so
|
||||||
# cannot use C++17 with them
|
# cannot use C++17 with them
|
||||||
set_target_properties(kimg_exr PROPERTIES CXX_STANDARD 14)
|
set_target_properties(kimg_exr PROPERTIES CXX_STANDARD 14)
|
||||||
endif()
|
endif()
|
||||||
target_link_libraries(kimg_exr PRIVATE OpenEXR::IlmImf)
|
target_link_libraries(kimg_exr OpenEXR::IlmImf)
|
||||||
endif()
|
endif()
|
||||||
kde_target_enable_exceptions(kimg_exr PRIVATE)
|
kde_target_enable_exceptions(kimg_exr PRIVATE)
|
||||||
|
|
||||||
|
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||||
|
install(FILES exr.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
##################################
|
##################################
|
||||||
|
|
||||||
kimageformats_add_plugin(kimg_hdr SOURCES hdr.cpp)
|
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)
|
if (LibHeif_FOUND)
|
||||||
kimageformats_add_plugin(kimg_heif SOURCES heif.cpp)
|
kimageformats_add_plugin(kimg_heif SOURCES heif.cpp)
|
||||||
target_link_libraries(kimg_heif PRIVATE PkgConfig::LibHeif)
|
target_link_libraries(kimg_heif PkgConfig::LibHeif)
|
||||||
|
|
||||||
|
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||||
|
install(FILES heif.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
##################################
|
##################################
|
||||||
|
|
||||||
if (LibJXL_FOUND AND LibJXLThreads_FOUND)
|
if (LibJXL_FOUND AND LibJXLThreads_FOUND)
|
||||||
kimageformats_add_plugin(kimg_jxl SOURCES jxl.cpp)
|
kimageformats_add_plugin(kimg_jxl SOURCES jxl.cpp)
|
||||||
target_link_libraries(kimg_jxl PRIVATE PkgConfig::LibJXL PkgConfig::LibJXLThreads)
|
target_link_libraries(kimg_jxl PkgConfig::LibJXL PkgConfig::LibJXLThreads)
|
||||||
|
|
||||||
|
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||||
|
install(FILES jxl.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
##################################
|
##################################
|
||||||
|
|
||||||
kimageformats_add_plugin(kimg_pcx SOURCES pcx.cpp)
|
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)
|
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)
|
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_qoi SOURCES qoi.cpp scanlineconverter.cpp)
|
||||||
|
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||||
|
install(FILES qoi.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||||
|
endif()
|
||||||
|
|
||||||
##################################
|
##################################
|
||||||
|
|
||||||
kimageformats_add_plugin(kimg_ras SOURCES ras.cpp)
|
kimageformats_add_plugin(kimg_ras SOURCES ras.cpp)
|
||||||
|
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||||
|
install(FILES ras.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||||
|
endif()
|
||||||
|
|
||||||
##################################
|
##################################
|
||||||
|
|
||||||
kimageformats_add_plugin(kimg_rgb SOURCES rgb.cpp)
|
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)
|
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)
|
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)
|
if (LibRaw_FOUND)
|
||||||
kimageformats_add_plugin(kimg_raw SOURCES raw.cpp)
|
kimageformats_add_plugin(kimg_raw SOURCES raw.cpp)
|
||||||
kde_enable_exceptions()
|
kde_enable_exceptions()
|
||||||
target_link_libraries(kimg_raw PRIVATE LibRaw::LibRaw)
|
target_link_libraries(kimg_raw LibRaw::LibRaw)
|
||||||
|
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||||
|
install(FILES raw.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
##################################
|
##################################
|
||||||
|
|
||||||
if (KF6Archive_FOUND)
|
if (KF5Archive_FOUND)
|
||||||
|
|
||||||
kimageformats_add_plugin(kimg_kra SOURCES kra.cpp)
|
kimageformats_add_plugin(kimg_kra SOURCES kra.cpp)
|
||||||
target_link_libraries(kimg_kra PRIVATE KF6::Archive)
|
target_link_libraries(kimg_kra KF5::Archive)
|
||||||
|
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||||
|
install(FILES kra.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||||
|
endif()
|
||||||
|
|
||||||
kimageformats_add_plugin(kimg_ora SOURCES ora.cpp)
|
kimageformats_add_plugin(kimg_ora SOURCES ora.cpp)
|
||||||
target_link_libraries(kimg_ora PRIVATE KF6::Archive)
|
target_link_libraries(kimg_ora KF5::Archive)
|
||||||
|
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||||
|
install(FILES ora.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||||
|
endif()
|
||||||
|
|
||||||
endif()
|
endif()
|
||||||
|
@ -90,7 +90,7 @@ bool ANIHandler::read(QImage *outImage)
|
|||||||
}
|
}
|
||||||
|
|
||||||
const QByteArray frameSizeData = device()->read(sizeof(quint32_le));
|
const QByteArray frameSizeData = device()->read(sizeof(quint32_le));
|
||||||
if (frameSizeData.size() != sizeof(quint32_le)) {
|
if (frameSizeData.count() != sizeof(quint32_le)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -383,8 +383,8 @@ bool ANIHandler::ensureScanned() const
|
|||||||
|
|
||||||
// TODO should we check that the number of rate entries matches nSteps?
|
// TODO should we check that the number of rate entries matches nSteps?
|
||||||
auto *dataPtr = data.data();
|
auto *dataPtr = data.data();
|
||||||
QList<int> list;
|
QVector<int> list;
|
||||||
for (int i = 0; i < data.size(); i += sizeof(quint32_le)) {
|
for (int i = 0; i < data.count(); i += sizeof(quint32_le)) {
|
||||||
const auto entry = *(reinterpret_cast<const quint32_le *>(dataPtr + i));
|
const auto entry = *(reinterpret_cast<const quint32_le *>(dataPtr + i));
|
||||||
list.append(entry);
|
list.append(entry);
|
||||||
}
|
}
|
||||||
|
7
src/imageformats/ani.desktop
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[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_frameCount = 0; // "physical" frames
|
||||||
int m_imageCount = 0; // logical images
|
int m_imageCount = 0; // logical images
|
||||||
// Stores a custom sequence of images
|
// Stores a custom sequence of images
|
||||||
QList<int> m_imageSequence;
|
QVector<int> m_imageSequence;
|
||||||
// and the corresponding offsets where they are
|
// and the corresponding offsets where they are
|
||||||
// since we can't read the image data sequentally in this case then
|
// since we can't read the image data sequentally in this case then
|
||||||
QList<qint64> m_frameOffsets;
|
QVector<qint64> m_frameOffsets;
|
||||||
qint64 m_firstFrameOffset = 0;
|
qint64 m_firstFrameOffset = 0;
|
||||||
|
|
||||||
int m_displayRate = 0;
|
int m_displayRate = 0;
|
||||||
QList<int> m_displayRates;
|
QVector<int> m_displayRates;
|
||||||
|
|
||||||
QString m_name;
|
QString m_name;
|
||||||
QString m_artist;
|
QString m_artist;
|
||||||
|
@ -619,7 +619,15 @@ bool QAVIFHandler::write(const QImage &image)
|
|||||||
QImage tmpgrayimage = image.convertToFormat(tmpformat);
|
QImage tmpgrayimage = image.convertToFormat(tmpformat);
|
||||||
|
|
||||||
avif = avifImageCreate(tmpgrayimage.width(), tmpgrayimage.height(), save_depth, AVIF_PIXEL_FORMAT_YUV400);
|
avif = avifImageCreate(tmpgrayimage.width(), tmpgrayimage.height(), save_depth, AVIF_PIXEL_FORMAT_YUV400);
|
||||||
|
#if AVIF_VERSION >= 110000
|
||||||
|
res = avifImageAllocatePlanes(avif, AVIF_PLANES_YUV);
|
||||||
|
if (res != AVIF_RESULT_OK) {
|
||||||
|
qWarning("ERROR in avifImageAllocatePlanes: %s", avifResultToString(res));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#else
|
||||||
avifImageAllocatePlanes(avif, AVIF_PLANES_YUV);
|
avifImageAllocatePlanes(avif, AVIF_PLANES_YUV);
|
||||||
|
#endif
|
||||||
|
|
||||||
if (tmpgrayimage.colorSpace().isValid()) {
|
if (tmpgrayimage.colorSpace().isValid()) {
|
||||||
avif->colorPrimaries = (avifColorPrimaries)1;
|
avif->colorPrimaries = (avifColorPrimaries)1;
|
||||||
@ -806,7 +814,15 @@ bool QAVIFHandler::write(const QImage &image)
|
|||||||
avif->transferCharacteristics = transfer_to_save;
|
avif->transferCharacteristics = transfer_to_save;
|
||||||
|
|
||||||
if (iccprofile.size() > 0) {
|
if (iccprofile.size() > 0) {
|
||||||
|
#if AVIF_VERSION >= 1000000
|
||||||
|
res = avifImageSetProfileICC(avif, reinterpret_cast<const uint8_t *>(iccprofile.constData()), iccprofile.size());
|
||||||
|
if (res != AVIF_RESULT_OK) {
|
||||||
|
qWarning("ERROR in avifImageSetProfileICC: %s", avifResultToString(res));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#else
|
||||||
avifImageSetProfileICC(avif, reinterpret_cast<const uint8_t *>(iccprofile.constData()), iccprofile.size());
|
avifImageSetProfileICC(avif, reinterpret_cast<const uint8_t *>(iccprofile.constData()), iccprofile.size());
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
avifRGBImage rgb;
|
avifRGBImage rgb;
|
||||||
@ -971,6 +987,8 @@ bool QAVIFHandler::jumpToNextImage()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
avifResult decodeResult;
|
||||||
|
|
||||||
if (m_decoder->imageIndex >= 0) {
|
if (m_decoder->imageIndex >= 0) {
|
||||||
if (m_decoder->imageCount < 2) {
|
if (m_decoder->imageCount < 2) {
|
||||||
m_parseState = ParseAvifSuccess;
|
m_parseState = ParseAvifSuccess;
|
||||||
@ -978,11 +996,16 @@ bool QAVIFHandler::jumpToNextImage()
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (m_decoder->imageIndex >= m_decoder->imageCount - 1) { // start from beginning
|
if (m_decoder->imageIndex >= m_decoder->imageCount - 1) { // start from beginning
|
||||||
avifDecoderReset(m_decoder);
|
decodeResult = avifDecoderReset(m_decoder);
|
||||||
|
if (decodeResult != AVIF_RESULT_OK) {
|
||||||
|
qWarning("ERROR in avifDecoderReset: %s", avifResultToString(decodeResult));
|
||||||
|
m_parseState = ParseAvifError;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
avifResult decodeResult = avifDecoderNextImage(m_decoder);
|
decodeResult = avifDecoderNextImage(m_decoder);
|
||||||
|
|
||||||
if (decodeResult != AVIF_RESULT_OK) {
|
if (decodeResult != AVIF_RESULT_OK) {
|
||||||
qWarning("ERROR: Failed to decode Next image in sequence: %s", avifResultToString(decodeResult));
|
qWarning("ERROR: Failed to decode Next image in sequence: %s", avifResultToString(decodeResult));
|
||||||
|
7
src/imageformats/avif.desktop
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[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
|
7
src/imageformats/dds-qt.desktop
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[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
|
7
src/imageformats/eps.desktop
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[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,5 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
The high dynamic range EXR format support for QImage.
|
KImageIO Routines to read (and perhaps in the future, write) images
|
||||||
|
in the high dynamic range EXR format.
|
||||||
|
|
||||||
SPDX-FileCopyrightText: 2003 Brad Hards <bradh@frogmouth.net>
|
SPDX-FileCopyrightText: 2003 Brad Hards <bradh@frogmouth.net>
|
||||||
SPDX-FileCopyrightText: 2023 Mirco Miranda <mircomir@outlook.com>
|
SPDX-FileCopyrightText: 2023 Mirco Miranda <mircomir@outlook.com>
|
||||||
@ -15,52 +16,15 @@
|
|||||||
*/
|
*/
|
||||||
//#define EXR_USE_LEGACY_CONVERSIONS // default commented -> you should define it in your cmake file
|
//#define EXR_USE_LEGACY_CONVERSIONS // default commented -> you should define it in your cmake file
|
||||||
|
|
||||||
/* *** EXR_CONVERT_TO_SRGB ***
|
/* *** EXR_ALLOW_LINEAR_COLORSPACE ***
|
||||||
* If defined, the linear data is converted to sRGB on read to accommodate
|
* If defined, the linear data is kept and it is the display program that
|
||||||
* programs that do not support color profiles.
|
* must convert to the monitor profile. Otherwise the data is converted to sRGB
|
||||||
* Otherwise the data are kept as is and it is the display program that
|
* to accommodate programs that do not support color profiles.
|
||||||
* must convert to the monitor profile.
|
|
||||||
* NOTE: If EXR_USE_LEGACY_CONVERSIONS is active, this is ignored.
|
* 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
|
//#define EXR_ALLOW_LINEAR_COLORSPACE // 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 "exr_p.h"
|
||||||
#include "scanlineconverter_p.h"
|
|
||||||
#include "util_p.h"
|
#include "util_p.h"
|
||||||
|
|
||||||
#include <IexThrowErrnoExc.h>
|
#include <IexThrowErrnoExc.h>
|
||||||
@ -75,9 +39,10 @@
|
|||||||
#include <ImfInt64.h>
|
#include <ImfInt64.h>
|
||||||
#include <ImfIntAttribute.h>
|
#include <ImfIntAttribute.h>
|
||||||
#include <ImfLineOrderAttribute.h>
|
#include <ImfLineOrderAttribute.h>
|
||||||
#include <ImfPreviewImage.h>
|
|
||||||
#include <ImfRgbaFile.h>
|
#include <ImfRgbaFile.h>
|
||||||
#include <ImfStandardAttributes.h>
|
#include <ImfStandardAttributes.h>
|
||||||
|
#include <ImfStringAttribute.h>
|
||||||
|
#include <ImfVecAttribute.h>
|
||||||
#include <ImfVersion.h>
|
#include <ImfVersion.h>
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
@ -88,13 +53,14 @@
|
|||||||
#include <QFloat16>
|
#include <QFloat16>
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
#include <QImageIOPlugin>
|
#include <QImageIOPlugin>
|
||||||
#include <QLocale>
|
|
||||||
#include <QThread>
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
|
||||||
#include <QTimeZone>
|
#include <QTimeZone>
|
||||||
|
#endif
|
||||||
|
|
||||||
// Allow the code to works on all QT versions supported by KDE
|
// Allow the code to works on all QT versions supported by KDE
|
||||||
// project (Qt 5.15 and Qt 6.x) to easy backports fixes.
|
// project (Qt 5.15 and Qt 6.x) to easy backports fixes.
|
||||||
#if !defined(EXR_USE_LEGACY_CONVERSIONS)
|
#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
|
// If uncommented, the image is rendered in a float16 format, the result is very precise
|
||||||
#define EXR_USE_QT6_FLOAT_IMAGE // default uncommented
|
#define EXR_USE_QT6_FLOAT_IMAGE // default uncommented
|
||||||
#endif
|
#endif
|
||||||
@ -102,8 +68,8 @@
|
|||||||
class K_IStream : public Imf::IStream
|
class K_IStream : public Imf::IStream
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
K_IStream(QIODevice *dev, const QByteArray &fileName)
|
K_IStream(QIODevice *dev)
|
||||||
: IStream(fileName.data())
|
: IStream("K_IStream")
|
||||||
, m_dev(dev)
|
, m_dev(dev)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -158,57 +124,6 @@ void K_IStream::clear()
|
|||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
class K_OStream : public Imf::OStream
|
|
||||||
{
|
|
||||||
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
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef EXR_USE_LEGACY_CONVERSIONS
|
#ifdef EXR_USE_LEGACY_CONVERSIONS
|
||||||
// source: https://openexr.com/en/latest/ReadingAndWritingImageFiles.html
|
// source: https://openexr.com/en/latest/ReadingAndWritingImageFiles.html
|
||||||
inline unsigned char gamma(float x)
|
inline unsigned char gamma(float x)
|
||||||
@ -226,14 +141,7 @@ inline QRgb RgbaToQrgba(struct Imf::Rgba &imagePixel)
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
EXRHandler::EXRHandler()
|
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
|
bool EXRHandler::canRead() const
|
||||||
@ -245,574 +153,117 @@ bool EXRHandler::canRead() const
|
|||||||
return false;
|
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)
|
bool EXRHandler::read(QImage *outImage)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
auto d = device();
|
int width;
|
||||||
|
int height;
|
||||||
|
|
||||||
// set the image position after the first run.
|
K_IStream istr(device());
|
||||||
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);
|
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();
|
Imath::Box2i dw = file.dataWindow();
|
||||||
qint32 width = dw.max.x - dw.min.x + 1;
|
bool isRgba = file.channels() & Imf::RgbaChannels::WRITE_A;
|
||||||
qint32 height = dw.max.y - dw.min.y + 1;
|
|
||||||
|
|
||||||
// limiting the maximum image size on a reasonable size (as done in other plugins)
|
width = dw.max.x - dw.min.x + 1;
|
||||||
if (width > EXR_MAX_IMAGE_WIDTH || height > EXR_MAX_IMAGE_HEIGHT) {
|
height = dw.max.y - dw.min.y + 1;
|
||||||
qWarning() << "The maximum image size is limited to" << EXR_MAX_IMAGE_WIDTH << "x" << EXR_MAX_IMAGE_HEIGHT << "px";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// creating the image
|
#if defined(EXR_USE_LEGACY_CONVERSIONS)
|
||||||
QImage image = imageAlloc(width, height, imageFormat(file));
|
QImage image = imageAlloc(width, height, isRgba ? QImage::Format_ARGB32 : QImage::Format_RGB32);
|
||||||
|
#elif defined(EXR_USE_QT6_FLOAT_IMAGE)
|
||||||
|
QImage image = imageAlloc(width, height, isRgba ? QImage::Format_RGBA16FPx4 : QImage::Format_RGBX16FPx4);
|
||||||
|
#else
|
||||||
|
QImage image = imageAlloc(width, height, isRgba ? QImage::Format_RGBA64 : QImage::Format_RGBX64);
|
||||||
|
#endif
|
||||||
if (image.isNull()) {
|
if (image.isNull()) {
|
||||||
qWarning() << "Failed to allocate image, invalid size?" << QSize(width, height);
|
qWarning() << "Failed to allocate image, invalid size?" << QSize(width, height);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Imf::Array2D<Imf::Rgba> pixels;
|
// set some useful metadata
|
||||||
pixels.resizeErase(EXR_LINES_PER_BLOCK, width);
|
auto &&h = file.header();
|
||||||
bool isRgba = image.hasAlphaChannel();
|
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()) {
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
|
||||||
|
dateTime.setTimeZone(QTimeZone::fromSecondsAheadOfUtc(off));
|
||||||
|
#else
|
||||||
|
dateTime.setOffsetFromUtc(off);
|
||||||
|
#endif
|
||||||
|
image.setText(QStringLiteral("Date"), dateTime.toString(Qt::ISODate));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (auto xDensity = h.findTypedAttribute<Imf::FloatAttribute>("xDensity")) {
|
||||||
|
float par = 1;
|
||||||
|
if (auto pixelAspectRatio = h.findTypedAttribute<Imf::FloatAttribute>("pixelAspectRatio")) {
|
||||||
|
par = pixelAspectRatio->value();
|
||||||
|
}
|
||||||
|
image.setDotsPerMeterX(qRound(xDensity->value() * 100.0 / 2.54));
|
||||||
|
image.setDotsPerMeterY(qRound(xDensity->value() * par * 100.0 / 2.54));
|
||||||
|
}
|
||||||
|
|
||||||
|
Imf::Array<Imf::Rgba> pixels;
|
||||||
|
pixels.resizeErase(width);
|
||||||
|
|
||||||
// somehow copy pixels into image
|
// somehow copy pixels into image
|
||||||
for (int y = 0, n = 0; y < height; y += n) {
|
for (int y = 0; y < height; ++y) {
|
||||||
auto my = dw.min.y + y;
|
auto my = dw.min.y + y;
|
||||||
if (my > dw.max.y) { // paranoia check
|
if (my <= dw.max.y) { // paranoia check
|
||||||
break;
|
file.setFrameBuffer(&pixels[0] - dw.min.x - qint64(my) * width, 1, width);
|
||||||
}
|
file.readPixels(my, my);
|
||||||
|
|
||||||
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)
|
#if defined(EXR_USE_LEGACY_CONVERSIONS)
|
||||||
Q_UNUSED(isRgba)
|
auto scanLine = reinterpret_cast<QRgb *>(image.scanLine(y));
|
||||||
auto scanLine = reinterpret_cast<QRgb *>(image.scanLine(y + n));
|
|
||||||
for (int x = 0; x < width; ++x) {
|
for (int x = 0; x < width; ++x) {
|
||||||
*(scanLine + x) = RgbaToQrgba(pixels[n][x]);
|
*(scanLine + x) = RgbaToQrgba(pixels[x]);
|
||||||
}
|
}
|
||||||
#elif defined(EXR_USE_QT6_FLOAT_IMAGE)
|
#elif defined(EXR_USE_QT6_FLOAT_IMAGE)
|
||||||
auto scanLine = reinterpret_cast<qfloat16 *>(image.scanLine(y + n));
|
auto scanLine = reinterpret_cast<qfloat16 *>(image.scanLine(y));
|
||||||
for (int x = 0; x < width; ++x) {
|
for (int x = 0; x < width; ++x) {
|
||||||
auto xcs = x * 4;
|
auto xcs = x * 4;
|
||||||
*(scanLine + xcs) = qfloat16(qBound(0.f, float(pixels[n][x].r), 1.f));
|
*(scanLine + xcs) = qfloat16(qBound(0.f, float(pixels[x].r), 1.f));
|
||||||
*(scanLine + xcs + 1) = qfloat16(qBound(0.f, float(pixels[n][x].g), 1.f));
|
*(scanLine + xcs + 1) = qfloat16(qBound(0.f, float(pixels[x].g), 1.f));
|
||||||
*(scanLine + xcs + 2) = qfloat16(qBound(0.f, float(pixels[n][x].b), 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[n][x].a), 1.f) : 1.f);
|
*(scanLine + xcs + 3) = qfloat16(isRgba ? qBound(0.f, float(pixels[x].a), 1.f) : 1.f);
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
auto scanLine = reinterpret_cast<QRgba64 *>(image.scanLine(y + n));
|
auto scanLine = reinterpret_cast<QRgba64 *>(image.scanLine(y));
|
||||||
for (int x = 0; x < width; ++x) {
|
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)),
|
*(scanLine + x) = QRgba64::fromRgba64(quint16(qBound(0.f, float(pixels[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[x].g) * 65535.f + 0.5f, 65535.f)),
|
||||||
quint16(qBound(0.f, float(pixels[n][x].b) * 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[n][x].a) * 65535.f + 0.5f, 65535.f)) : quint16(65535));
|
isRgba ? quint16(qBound(0.f, float(pixels[x].a) * 65535.f + 0.5f, 65535.f)) : quint16(65535));
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// set some useful metadata
|
|
||||||
readMetadata(header, image);
|
|
||||||
// final color operations
|
// final color operations
|
||||||
readColorSpace(header, image);
|
#ifndef EXR_USE_LEGACY_CONVERSIONS
|
||||||
|
image.setColorSpace(QColorSpace(QColorSpace::SRgbLinear));
|
||||||
|
#ifndef EXR_ALLOW_LINEAR_COLORSPACE
|
||||||
|
image.convertToColorSpace(QColorSpace(QColorSpace::SRgb));
|
||||||
|
#endif // !EXR_ALLOW_LINEAR_COLORSPACE
|
||||||
|
#endif // !EXR_USE_LEGACY_CONVERSIONS
|
||||||
|
|
||||||
*outImage = image;
|
*outImage = image;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (const std::exception &) {
|
} catch (const std::exception &exc) {
|
||||||
|
// qDebug() << exc.what();
|
||||||
return false;
|
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());
|
|
||||||
auto channelsType = image.hasAlphaChannel() ? Imf::RgbaChannels::WRITE_RGBA : Imf::RgbaChannels::WRITE_RGB;
|
|
||||||
if (image.format() == QImage::Format_Mono ||
|
|
||||||
image.format() == QImage::Format_MonoLSB ||
|
|
||||||
image.format() == QImage::Format_Grayscale16 ||
|
|
||||||
image.format() == QImage::Format_Grayscale8) {
|
|
||||||
channelsType = Imf::RgbaChannels::WRITE_Y;
|
|
||||||
}
|
|
||||||
Imf::RgbaOutputFile file(ostr, header, channelsType);
|
|
||||||
Imf::Array2D<Imf::Rgba> pixels;
|
|
||||||
pixels.resizeErase(EXR_LINES_PER_BLOCK, width);
|
|
||||||
|
|
||||||
// convert the image and write into the stream
|
|
||||||
#if defined(EXR_USE_QT6_FLOAT_IMAGE)
|
|
||||||
auto convFormat = image.hasAlphaChannel() ? QImage::Format_RGBA16FPx4 : QImage::Format_RGBX16FPx4;
|
|
||||||
#else
|
|
||||||
auto convFormat = image.hasAlphaChannel() ? QImage::Format_RGBA64 : QImage::Format_RGBX64;
|
|
||||||
#endif
|
|
||||||
ScanLineConverter slc(convFormat);
|
|
||||||
slc.setDefaultSourceColorSpace(QColorSpace(QColorSpace::SRgb));
|
|
||||||
slc.setTargetColorSpace(QColorSpace(QColorSpace::SRgbLinear));
|
|
||||||
for (int y = 0, n = 0; y < height; y += n) {
|
|
||||||
for (n = 0; n < std::min(EXR_LINES_PER_BLOCK, height - y); ++n) {
|
|
||||||
#if defined(EXR_USE_QT6_FLOAT_IMAGE)
|
|
||||||
auto scanLine = reinterpret_cast<const qfloat16 *>(slc.convertedScanLine(image, y + n));
|
|
||||||
if (scanLine == nullptr) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
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)
|
bool EXRHandler::canRead(QIODevice *device)
|
||||||
{
|
{
|
||||||
if (!device) {
|
if (!device) {
|
||||||
@ -820,6 +271,13 @@ bool EXRHandler::canRead(QIODevice *device)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if OPENEXR_VERSION_MAJOR == 3 && OPENEXR_VERSION_MINOR > 2
|
||||||
|
// openexpr >= 3.3 uses seek and tell extensively
|
||||||
|
if (device->isSequential()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
const QByteArray head = device->peek(4);
|
const QByteArray head = device->peek(4);
|
||||||
|
|
||||||
return Imf::isImfMagic(head.data());
|
return Imf::isImfMagic(head.data());
|
||||||
@ -828,7 +286,7 @@ bool EXRHandler::canRead(QIODevice *device)
|
|||||||
QImageIOPlugin::Capabilities EXRPlugin::capabilities(QIODevice *device, const QByteArray &format) const
|
QImageIOPlugin::Capabilities EXRPlugin::capabilities(QIODevice *device, const QByteArray &format) const
|
||||||
{
|
{
|
||||||
if (format == "exr") {
|
if (format == "exr") {
|
||||||
return Capabilities(CanRead | CanWrite);
|
return Capabilities(CanRead);
|
||||||
}
|
}
|
||||||
if (!format.isEmpty()) {
|
if (!format.isEmpty()) {
|
||||||
return {};
|
return {};
|
||||||
@ -841,9 +299,6 @@ QImageIOPlugin::Capabilities EXRPlugin::capabilities(QIODevice *device, const QB
|
|||||||
if (device->isReadable() && EXRHandler::canRead(device)) {
|
if (device->isReadable() && EXRHandler::canRead(device)) {
|
||||||
cap |= CanRead;
|
cap |= CanRead;
|
||||||
}
|
}
|
||||||
if (device->isWritable()) {
|
|
||||||
cap |= CanWrite;
|
|
||||||
}
|
|
||||||
return cap;
|
return cap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
7
src/imageformats/exr.desktop
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[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 @@
|
|||||||
/*
|
/*
|
||||||
The high dynamic range EXR format support for QImage.
|
QImageIO Routines to read (and perhaps in the future, write) images
|
||||||
|
in the high definition EXR format.
|
||||||
|
|
||||||
SPDX-FileCopyrightText: 2003 Brad Hards <bradh@frogmouth.net>
|
SPDX-FileCopyrightText: 2003 Brad Hards <bradh@frogmouth.net>
|
||||||
SPDX-FileCopyrightText: 2023 Mirco Miranda <mircomir@outlook.com>
|
|
||||||
|
|
||||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||||
*/
|
*/
|
||||||
@ -12,44 +12,6 @@
|
|||||||
|
|
||||||
#include <QImageIOPlugin>
|
#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
|
class EXRHandler : public QImageIOHandler
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -57,65 +19,8 @@ public:
|
|||||||
|
|
||||||
bool canRead() const override;
|
bool canRead() const override;
|
||||||
bool read(QImage *outImage) 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);
|
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
|
class EXRPlugin : public QImageIOPlugin
|
||||||
|
@ -11,18 +11,12 @@
|
|||||||
|
|
||||||
#include <QColorSpace>
|
#include <QColorSpace>
|
||||||
#include <QDataStream>
|
#include <QDataStream>
|
||||||
#include <QFloat16>
|
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
#include <QLoggingCategory>
|
#include <QLoggingCategory>
|
||||||
#include <QRegularExpressionMatch>
|
#include <QRegularExpressionMatch>
|
||||||
|
|
||||||
#include <QDebug>
|
#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;
|
typedef unsigned char uchar;
|
||||||
|
|
||||||
Q_LOGGING_CATEGORY(HDRPLUGIN, "kf.imageformats.plugins.hdr", QtWarningMsg)
|
Q_LOGGING_CATEGORY(HDRPLUGIN, "kf.imageformats.plugins.hdr", QtWarningMsg)
|
||||||
@ -33,6 +27,12 @@ namespace // Private.
|
|||||||
#define MINELEN 8 // minimum scanline length for encoding
|
#define MINELEN 8 // minimum scanline length for encoding
|
||||||
#define MAXELEN 0x7fff // maximum scanline length for encoding
|
#define MAXELEN 0x7fff // maximum scanline length for encoding
|
||||||
|
|
||||||
|
static inline uchar ClipToByte(float value)
|
||||||
|
{
|
||||||
|
// we know value is positive.
|
||||||
|
return uchar(std::min(value + 0.5f, 255.0f));
|
||||||
|
}
|
||||||
|
|
||||||
// read an old style line from the hdr image file
|
// read an old style line from the hdr image file
|
||||||
// if 'first' is true the first byte is already read
|
// if 'first' is true the first byte is already read
|
||||||
static bool Read_Old_Line(uchar *image, int width, QDataStream &s)
|
static bool Read_Old_Line(uchar *image, int width, QDataStream &s)
|
||||||
@ -75,8 +75,7 @@ static bool Read_Old_Line(uchar *image, int width, QDataStream &s)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class float_T>
|
static void RGBE_To_QRgbLine(uchar *image, QRgb *scanline, int width)
|
||||||
void RGBE_To_QRgbLine(uchar *image, float_T *scanline, int width)
|
|
||||||
{
|
{
|
||||||
for (int j = 0; j < width; j++) {
|
for (int j = 0; j < width; j++) {
|
||||||
// v = ldexp(1.0, int(image[3]) - 128);
|
// v = ldexp(1.0, int(image[3]) - 128);
|
||||||
@ -88,25 +87,12 @@ void RGBE_To_QRgbLine(uchar *image, float_T *scanline, int width)
|
|||||||
v = 1.0f / float(1 << -e);
|
v = 1.0f / float(1 << -e);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto j4 = j * 4;
|
scanline[j] = qRgb(ClipToByte(float(image[0]) * v), ClipToByte(float(image[1]) * v), ClipToByte(float(image[2]) * v));
|
||||||
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;
|
image += 4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QImage::Format imageFormat()
|
|
||||||
{
|
|
||||||
#ifdef HDR_HALF_QUALITY
|
|
||||||
return QImage::Format_RGBX16FPx4;
|
|
||||||
#else
|
|
||||||
return QImage::Format_RGBX32FPx4;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load the HDR image.
|
// Load the HDR image.
|
||||||
static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &img)
|
static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &img)
|
||||||
{
|
{
|
||||||
@ -114,7 +100,7 @@ static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &i
|
|||||||
uchar code;
|
uchar code;
|
||||||
|
|
||||||
// Create dst image.
|
// Create dst image.
|
||||||
img = imageAlloc(width, height, imageFormat());
|
img = imageAlloc(width, height, QImage::Format_RGB32);
|
||||||
if (img.isNull()) {
|
if (img.isNull()) {
|
||||||
qCDebug(HDRPLUGIN) << "Couldn't create image with size" << width << height << "and format RGB32";
|
qCDebug(HDRPLUGIN) << "Couldn't create image with size" << width << height << "and format RGB32";
|
||||||
return false;
|
return false;
|
||||||
@ -122,14 +108,10 @@ static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &i
|
|||||||
|
|
||||||
QByteArray lineArray;
|
QByteArray lineArray;
|
||||||
lineArray.resize(4 * width);
|
lineArray.resize(4 * width);
|
||||||
uchar *image = reinterpret_cast<uchar *>(lineArray.data());
|
uchar *image = (uchar *)lineArray.data();
|
||||||
|
|
||||||
for (int cline = 0; cline < height; cline++) {
|
for (int cline = 0; cline < height; cline++) {
|
||||||
#ifdef HDR_HALF_QUALITY
|
QRgb *scanline = (QRgb *)img.scanLine(cline);
|
||||||
auto scanline = reinterpret_cast<qfloat16 *>(img.scanLine(cline));
|
|
||||||
#else
|
|
||||||
auto scanline = reinterpret_cast<float *>(img.scanLine(cline));
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// determine scanline type
|
// determine scanline type
|
||||||
if ((width < MINELEN) || (MAXELEN < width)) {
|
if ((width < MINELEN) || (MAXELEN < width)) {
|
||||||
@ -211,7 +193,9 @@ static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &i
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static QSize readHeaderSize(QIODevice *device)
|
} // namespace
|
||||||
|
|
||||||
|
bool HDRHandler::read(QImage *outImage)
|
||||||
{
|
{
|
||||||
int len;
|
int len;
|
||||||
QByteArray line(MAXLINE + 1, Qt::Uninitialized);
|
QByteArray line(MAXLINE + 1, Qt::Uninitialized);
|
||||||
@ -219,7 +203,7 @@ static QSize readHeaderSize(QIODevice *device)
|
|||||||
|
|
||||||
// Parse header
|
// Parse header
|
||||||
do {
|
do {
|
||||||
len = device->readLine(line.data(), MAXLINE);
|
len = device()->readLine(line.data(), MAXLINE);
|
||||||
|
|
||||||
if (line.startsWith("FORMAT=")) {
|
if (line.startsWith("FORMAT=")) {
|
||||||
format = line.mid(7, len - 7 - 1 /*\n*/);
|
format = line.mid(7, len - 7 - 1 /*\n*/);
|
||||||
@ -229,10 +213,10 @@ static QSize readHeaderSize(QIODevice *device)
|
|||||||
|
|
||||||
if (format != "32-bit_rle_rgbe") {
|
if (format != "32-bit_rle_rgbe") {
|
||||||
qCDebug(HDRPLUGIN) << "Unknown HDR format:" << format;
|
qCDebug(HDRPLUGIN) << "Unknown HDR format:" << format;
|
||||||
return QSize();
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
len = device->readLine(line.data(), MAXLINE);
|
len = device()->readLine(line.data(), MAXLINE);
|
||||||
line.resize(len);
|
line.resize(len);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -252,30 +236,21 @@ static QSize readHeaderSize(QIODevice *device)
|
|||||||
QRegularExpressionMatch match = resolutionRegExp.match(QString::fromLatin1(line));
|
QRegularExpressionMatch match = resolutionRegExp.match(QString::fromLatin1(line));
|
||||||
if (!match.hasMatch()) {
|
if (!match.hasMatch()) {
|
||||||
qCDebug(HDRPLUGIN) << "Invalid HDR file, the first line after the header didn't have the expected format:" << line;
|
qCDebug(HDRPLUGIN) << "Invalid HDR file, the first line after the header didn't have the expected format:" << line;
|
||||||
return QSize();
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((match.captured(1).at(1) != u'Y') || (match.captured(3).at(1) != u'X')) {
|
if ((match.captured(1).at(1) != u'Y') || (match.captured(3).at(1) != u'X')) {
|
||||||
qCDebug(HDRPLUGIN) << "Unsupported image orientation in HDR file.";
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const int width = match.captured(4).toInt();
|
||||||
|
const int height = match.captured(2).toInt();
|
||||||
|
|
||||||
|
QDataStream s(device());
|
||||||
|
|
||||||
QImage img;
|
QImage img;
|
||||||
if (!LoadHDR(s, size.width(), size.height(), img)) {
|
if (!LoadHDR(s, width, height, img)) {
|
||||||
// qDebug() << "Error loading HDR file.";
|
// qDebug() << "Error loading HDR file.";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -287,40 +262,6 @@ bool HDRHandler::read(QImage *outImage)
|
|||||||
return true;
|
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()
|
HDRHandler::HDRHandler()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -341,20 +282,7 @@ bool HDRHandler::canRead(QIODevice *device)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// the .pic taken from official test cases does not start with this string but can be loaded.
|
return device->peek(11) == "#?RADIANCE\n" || device->peek(7) == "#?RGBE\n";
|
||||||
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
|
QImageIOPlugin::Capabilities HDRPlugin::capabilities(QIODevice *device, const QByteArray &format) const
|
||||||
|
7
src/imageformats/hdr.desktop
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Type=Service
|
||||||
|
X-KDE-ServiceTypes=QImageIOPlugins
|
||||||
|
X-KDE-ImageFormat=hdr
|
||||||
|
X-KDE-MimeType=image/x-hdr
|
||||||
|
X-KDE-Read=true
|
||||||
|
X-KDE-Write=false
|
@ -18,9 +18,6 @@ public:
|
|||||||
bool canRead() const override;
|
bool canRead() const override;
|
||||||
bool read(QImage *outImage) override;
|
bool read(QImage *outImage) override;
|
||||||
|
|
||||||
bool supportsOption(QImageIOHandler::ImageOption option) const override;
|
|
||||||
QVariant option(QImageIOHandler::ImageOption option) const override;
|
|
||||||
|
|
||||||
static bool canRead(QIODevice *device);
|
static bool canRead(QIODevice *device);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -476,8 +476,17 @@ bool HEIFHandler::ensureDecoder()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool hasAlphaChannel = heif_image_handle_has_alpha_channel(handle);
|
|
||||||
const int bit_depth = heif_image_handle_get_luma_bits_per_pixel(handle);
|
const int bit_depth = heif_image_handle_get_luma_bits_per_pixel(handle);
|
||||||
|
|
||||||
|
if (bit_depth < 8) {
|
||||||
|
m_parseState = ParseHeicError;
|
||||||
|
heif_image_handle_release(handle);
|
||||||
|
heif_context_free(ctx);
|
||||||
|
qWarning() << "HEIF image with undefined or unsupported bit depth.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool hasAlphaChannel = heif_image_handle_has_alpha_channel(handle);
|
||||||
heif_chroma chroma;
|
heif_chroma chroma;
|
||||||
|
|
||||||
QImage::Format target_image_format;
|
QImage::Format target_image_format;
|
||||||
@ -502,11 +511,7 @@ bool HEIFHandler::ensureDecoder()
|
|||||||
m_parseState = ParseHeicError;
|
m_parseState = ParseHeicError;
|
||||||
heif_image_handle_release(handle);
|
heif_image_handle_release(handle);
|
||||||
heif_context_free(ctx);
|
heif_context_free(ctx);
|
||||||
if (bit_depth > 0) {
|
qWarning() << "Unsupported bit depth:" << bit_depth;
|
||||||
qWarning() << "Unsupported bit depth:" << bit_depth;
|
|
||||||
} else {
|
|
||||||
qWarning() << "Undefined bit depth.";
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -519,6 +524,16 @@ bool HEIFHandler::ensureDecoder()
|
|||||||
struct heif_image *img = nullptr;
|
struct heif_image *img = nullptr;
|
||||||
err = heif_decode_image(handle, &img, heif_colorspace_RGB, chroma, decoder_option);
|
err = heif_decode_image(handle, &img, heif_colorspace_RGB, chroma, decoder_option);
|
||||||
|
|
||||||
|
#if LIBHEIF_HAVE_VERSION(1, 13, 0)
|
||||||
|
if (err.code == heif_error_Invalid_input && err.subcode == heif_suberror_Unknown_NCLX_matrix_coefficients && img == nullptr && buffer.contains("Xiaomi")) {
|
||||||
|
qWarning() << "Non-standard HEIF image with invalid matrix_coefficients, probably made by a Xiaomi device!";
|
||||||
|
|
||||||
|
// second try to decode with strict decoding disabled
|
||||||
|
decoder_option->strict_decoding = 0;
|
||||||
|
err = heif_decode_image(handle, &img, heif_colorspace_RGB, chroma, decoder_option);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (decoder_option) {
|
if (decoder_option) {
|
||||||
heif_decoding_options_free(decoder_option);
|
heif_decoding_options_free(decoder_option);
|
||||||
}
|
}
|
||||||
|
7
src/imageformats/heif.desktop
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Type=Service
|
||||||
|
X-KDE-ServiceTypes=QImageIOPlugins
|
||||||
|
X-KDE-ImageFormat=heif
|
||||||
|
X-KDE-MimeType=image/heif
|
||||||
|
X-KDE-Read=true
|
||||||
|
X-KDE-Write=true
|
7
src/imageformats/jp2.desktop
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Type=Service
|
||||||
|
X-KDE-ServiceTypes=QImageIOPlugins
|
||||||
|
X-KDE-ImageFormat=jp2
|
||||||
|
X-KDE-MimeType=image/jp2
|
||||||
|
X-KDE-Read=true
|
||||||
|
X-KDE-Write=true
|
@ -228,7 +228,7 @@ bool QJpegXLHandler::countALLFrames()
|
|||||||
}
|
}
|
||||||
|
|
||||||
JxlColorEncoding color_encoding;
|
JxlColorEncoding color_encoding;
|
||||||
if (m_basicinfo.uses_original_profile == JXL_FALSE) {
|
if (m_basicinfo.uses_original_profile == JXL_FALSE && m_basicinfo.have_animation == JXL_FALSE) {
|
||||||
JxlColorEncodingSetToSRGB(&color_encoding, JXL_FALSE);
|
JxlColorEncodingSetToSRGB(&color_encoding, JXL_FALSE);
|
||||||
JxlDecoderSetPreferredColorProfile(m_decoder, &color_encoding);
|
JxlDecoderSetPreferredColorProfile(m_decoder, &color_encoding);
|
||||||
}
|
}
|
||||||
@ -960,13 +960,7 @@ bool QJpegXLHandler::rewind()
|
|||||||
|
|
||||||
JxlDecoderCloseInput(m_decoder);
|
JxlDecoderCloseInput(m_decoder);
|
||||||
|
|
||||||
if (m_basicinfo.uses_original_profile) {
|
if (m_basicinfo.uses_original_profile == JXL_FALSE && m_basicinfo.have_animation == JXL_FALSE) {
|
||||||
if (JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_FULL_IMAGE) != JXL_DEC_SUCCESS) {
|
|
||||||
qWarning("ERROR: JxlDecoderSubscribeEvents failed");
|
|
||||||
m_parseState = ParseJpegXLError;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_COLOR_ENCODING | JXL_DEC_FULL_IMAGE) != JXL_DEC_SUCCESS) {
|
if (JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_COLOR_ENCODING | JXL_DEC_FULL_IMAGE) != JXL_DEC_SUCCESS) {
|
||||||
qWarning("ERROR: JxlDecoderSubscribeEvents failed");
|
qWarning("ERROR: JxlDecoderSubscribeEvents failed");
|
||||||
m_parseState = ParseJpegXLError;
|
m_parseState = ParseJpegXLError;
|
||||||
@ -983,6 +977,12 @@ bool QJpegXLHandler::rewind()
|
|||||||
JxlColorEncoding color_encoding;
|
JxlColorEncoding color_encoding;
|
||||||
JxlColorEncodingSetToSRGB(&color_encoding, JXL_FALSE);
|
JxlColorEncodingSetToSRGB(&color_encoding, JXL_FALSE);
|
||||||
JxlDecoderSetPreferredColorProfile(m_decoder, &color_encoding);
|
JxlDecoderSetPreferredColorProfile(m_decoder, &color_encoding);
|
||||||
|
} else {
|
||||||
|
if (JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_FULL_IMAGE) != JXL_DEC_SUCCESS) {
|
||||||
|
qWarning("ERROR: JxlDecoderSubscribeEvents failed");
|
||||||
|
m_parseState = ParseJpegXLError;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
7
src/imageformats/jxl.desktop
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Type=Service
|
||||||
|
X-KDE-ServiceTypes=QImageIOPlugins
|
||||||
|
X-KDE-ImageFormat=jxl
|
||||||
|
X-KDE-MimeType=image/jxl
|
||||||
|
X-KDE-Read=true
|
||||||
|
X-KDE-Write=true
|
@ -14,8 +14,8 @@
|
|||||||
#include <QImage>
|
#include <QImage>
|
||||||
#include <QImageIOHandler>
|
#include <QImageIOHandler>
|
||||||
#include <QImageIOPlugin>
|
#include <QImageIOPlugin>
|
||||||
#include <QList>
|
|
||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
|
#include <QVector>
|
||||||
|
|
||||||
#include <jxl/decode.h>
|
#include <jxl/decode.h>
|
||||||
|
|
||||||
@ -71,7 +71,7 @@ private:
|
|||||||
void *m_runner;
|
void *m_runner;
|
||||||
JxlBasicInfo m_basicinfo;
|
JxlBasicInfo m_basicinfo;
|
||||||
|
|
||||||
QList<int> m_framedelays;
|
QVector<int> m_framedelays;
|
||||||
int m_next_image_delay;
|
int m_next_image_delay;
|
||||||
|
|
||||||
QImage m_current_image;
|
QImage m_current_image;
|
||||||
|
7
src/imageformats/kra.desktop
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Type=Service
|
||||||
|
X-KDE-ServiceTypes=QImageIOPlugins
|
||||||
|
X-KDE-ImageFormat=kra
|
||||||
|
X-KDE-MimeType=application/x-krita
|
||||||
|
X-KDE-Read=true
|
||||||
|
X-KDE-Write=false
|
7
src/imageformats/ora.desktop
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Type=Service
|
||||||
|
X-KDE-ServiceTypes=QImageIOPlugins
|
||||||
|
X-KDE-ImageFormat=ora
|
||||||
|
X-KDE-MimeType=image/openraster
|
||||||
|
X-KDE-Read=true
|
||||||
|
X-KDE-Write=false
|
@ -308,6 +308,11 @@ static bool readImage4(QImage &img, QDataStream &s, const PCXHEADER &header)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (header.BytesPerLine < (header.width() + 7) / 8) {
|
||||||
|
qWarning() << "PCX image has invalid BytesPerLine value";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
for (int y = 0; y < header.height(); ++y) {
|
for (int y = 0; y < header.height(); ++y) {
|
||||||
if (s.atEnd()) {
|
if (s.atEnd()) {
|
||||||
return false;
|
return false;
|
||||||
@ -418,6 +423,8 @@ static bool readImage24(QImage &img, QDataStream &s, const PCXHEADER &header)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const unsigned int bpl = std::min(header.BytesPerLine, static_cast<quint16>(header.width()));
|
||||||
|
|
||||||
for (int y = 0; y < header.height(); ++y) {
|
for (int y = 0; y < header.height(); ++y) {
|
||||||
if (s.atEnd()) {
|
if (s.atEnd()) {
|
||||||
return false;
|
return false;
|
||||||
@ -434,7 +441,8 @@ static bool readImage24(QImage &img, QDataStream &s, const PCXHEADER &header)
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint *p = (uint *)img.scanLine(y);
|
uint *p = (uint *)img.scanLine(y);
|
||||||
for (int x = 0; x < header.width(); ++x) {
|
|
||||||
|
for (unsigned int x = 0; x < bpl; ++x) {
|
||||||
p[x] = qRgb(r_buf[x], g_buf[x], b_buf[x]);
|
p[x] = qRgb(r_buf[x], g_buf[x], b_buf[x]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
7
src/imageformats/pcx.desktop
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Type=Service
|
||||||
|
X-KDE-ServiceTypes=QImageIOPlugins
|
||||||
|
X-KDE-ImageFormat=pcx
|
||||||
|
X-KDE-MimeType=image/x-pcx
|
||||||
|
X-KDE-Read=true
|
||||||
|
X-KDE-Write=true
|
7
src/imageformats/pic.desktop
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Type=Service
|
||||||
|
X-KDE-ServiceTypes=QImageIOPlugins
|
||||||
|
X-KDE-ImageFormat=pic
|
||||||
|
X-KDE-MimeType=image/x-pic
|
||||||
|
X-KDE-Read=true
|
||||||
|
X-KDE-Write=true
|
@ -20,13 +20,13 @@
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* Limitations of the current code:
|
* Limitations of the current code:
|
||||||
* - Color spaces other than RGB/Grayscale cannot be read due to lack of QImage
|
* - 32-bit float image are converted to 16-bit integer image.
|
||||||
* support. Where possible, a conversion to RGB is done:
|
* - Other color spaces cannot directly be read due to lack of QImage support for
|
||||||
|
* color spaces other than RGB (and Grayscale). Where possible, a conversion
|
||||||
|
* to RGB is done:
|
||||||
* - CMYK images are converted using an approximated way that ignores the color
|
* - CMYK images are converted using an approximated way that ignores the color
|
||||||
* information (ICC profile).
|
* information (ICC profile).
|
||||||
* - LAB images are converted to sRGB using literature formulas.
|
* - LAB images are converted to sRGB using literature formulas.
|
||||||
* - MULICHANNEL images more than 3 channels are converted as CMYK images.
|
|
||||||
* - DUOTONE images are considered as Grayscale images.
|
|
||||||
*
|
*
|
||||||
* NOTE: The best way to convert between different color spaces is to use a
|
* NOTE: The best way to convert between different color spaces is to use a
|
||||||
* color management engine (e.g. LittleCMS).
|
* color management engine (e.g. LittleCMS).
|
||||||
@ -126,7 +126,7 @@ struct PSDDuotoneOptions {
|
|||||||
*/
|
*/
|
||||||
struct PSDColorModeDataSection {
|
struct PSDColorModeDataSection {
|
||||||
PSDDuotoneOptions duotone;
|
PSDDuotoneOptions duotone;
|
||||||
QList<QRgb> palette;
|
QVector<QRgb> palette;
|
||||||
};
|
};
|
||||||
|
|
||||||
using PSDImageResourceSection = QHash<quint16, PSDImageResourceBlock>;
|
using PSDImageResourceSection = QHash<quint16, PSDImageResourceBlock>;
|
||||||
@ -458,7 +458,7 @@ PSDColorModeDataSection readColorModeDataSection(QDataStream &s, bool *ok = null
|
|||||||
}
|
}
|
||||||
else { // read the palette (768 bytes)
|
else { // read the palette (768 bytes)
|
||||||
auto&& palette = cms.palette;
|
auto&& palette = cms.palette;
|
||||||
QList<quint8> vect(size);
|
QVector<quint8> vect(size);
|
||||||
for (auto&& v : vect)
|
for (auto&& v : vect)
|
||||||
s >> v;
|
s >> v;
|
||||||
for (qsizetype i = 0, n = vect.size()/3; i < n; ++i)
|
for (qsizetype i = 0, n = vect.size()/3; i < n; ++i)
|
||||||
@ -631,7 +631,7 @@ static bool IsValid(const PSDHeader &header)
|
|||||||
qDebug() << "PSD header: invalid color mode" << header.color_mode;
|
qDebug() << "PSD header: invalid color mode" << header.color_mode;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Specs tells: "Supported range is 1 to 56" but when the alpha channel is present the limit is 57:
|
// Specs tells: "Supported range is 1 to 56" but the limit is 57:
|
||||||
// Photoshop does not make you add more (see also 53alphas.psd test case).
|
// Photoshop does not make you add more (see also 53alphas.psd test case).
|
||||||
if (header.channel_count < 1 || header.channel_count > 57) {
|
if (header.channel_count < 1 || header.channel_count > 57) {
|
||||||
qDebug() << "PSD header: invalid number of channels" << header.channel_count;
|
qDebug() << "PSD header: invalid number of channels" << header.channel_count;
|
||||||
@ -733,9 +733,7 @@ static QImage::Format imageFormat(const PSDHeader &header, bool alpha)
|
|||||||
auto format = QImage::Format_Invalid;
|
auto format = QImage::Format_Invalid;
|
||||||
switch(header.color_mode) {
|
switch(header.color_mode) {
|
||||||
case CM_RGB:
|
case CM_RGB:
|
||||||
if (header.depth == 32)
|
if (header.depth == 16 || header.depth == 32)
|
||||||
format = header.channel_count < 4 || !alpha ? QImage::Format_RGBX32FPx4 : QImage::Format_RGBA32FPx4_Premultiplied;
|
|
||||||
else if (header.depth == 16)
|
|
||||||
format = header.channel_count < 4 || !alpha ? QImage::Format_RGBX64 : QImage::Format_RGBA64_Premultiplied;
|
format = header.channel_count < 4 || !alpha ? QImage::Format_RGBX64 : QImage::Format_RGBA64_Premultiplied;
|
||||||
else
|
else
|
||||||
format = header.channel_count < 4 || !alpha ? QImage::Format_RGB888 : QImage::Format_RGBA8888_Premultiplied;
|
format = header.channel_count < 4 || !alpha ? QImage::Format_RGB888 : QImage::Format_RGBA8888_Premultiplied;
|
||||||
@ -791,13 +789,11 @@ static qint32 imageChannels(const QImage::Format& format)
|
|||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline quint8 xchg(quint8 v)
|
inline quint8 xchg(quint8 v) {
|
||||||
{
|
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline quint16 xchg(quint16 v)
|
inline quint16 xchg(quint16 v) {
|
||||||
{
|
|
||||||
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
|
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
|
||||||
return quint16( (v>>8) | (v<<8) );
|
return quint16( (v>>8) | (v<<8) );
|
||||||
#else
|
#else
|
||||||
@ -805,8 +801,7 @@ inline quint16 xchg(quint16 v)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
inline quint32 xchg(quint32 v)
|
inline quint32 xchg(quint32 v) {
|
||||||
{
|
|
||||||
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
|
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
|
||||||
return quint32( (v>>24) | ((v & 0x00FF0000)>>8) | ((v & 0x0000FF00)<<8) | (v<<24) );
|
return quint32( (v>>24) | ((v & 0x00FF0000)>>8) | ((v & 0x0000FF00)<<8) | (v<<24) );
|
||||||
#else
|
#else
|
||||||
@ -863,9 +858,8 @@ enum class PremulConversion {
|
|||||||
template<class T>
|
template<class T>
|
||||||
inline void premulConversion(char *stride, qint32 width, qint32 ac, qint32 cn, const PremulConversion &conv)
|
inline void premulConversion(char *stride, qint32 width, qint32 ac, qint32 cn, const PremulConversion &conv)
|
||||||
{
|
{
|
||||||
auto s = reinterpret_cast<T*>(stride);
|
auto s = reinterpret_cast<T *>(stride);
|
||||||
// NOTE: to avoid overflows, max is casted to qint64: that is possible because max is always an integer (even if T is float)
|
auto max = qint64(std::numeric_limits<T>::max());
|
||||||
auto max = qint64(std::numeric_limits<T>::is_integer ? std::numeric_limits<T>::max() : 1);
|
|
||||||
|
|
||||||
for (qint32 c = 0; c < ac; ++c) {
|
for (qint32 c = 0; c < ac; ++c) {
|
||||||
if (conv == PremulConversion::PS2P) {
|
if (conv == PremulConversion::PS2P) {
|
||||||
@ -874,16 +868,14 @@ inline void premulConversion(char *stride, qint32 width, qint32 ac, qint32 cn, c
|
|||||||
auto alpha = *(s + xcn + ac);
|
auto alpha = *(s + xcn + ac);
|
||||||
*(s + xcn + c) = *(s + xcn + c) + alpha - max;
|
*(s + xcn + c) = *(s + xcn + c) + alpha - max;
|
||||||
}
|
}
|
||||||
}
|
} else if (conv == PremulConversion::PS2A || (conv == PremulConversion::PSLab2A && c == 0)) {
|
||||||
else if (conv == PremulConversion::PS2A || (conv == PremulConversion::PSLab2A && c == 0)) {
|
|
||||||
for (qint32 x = 0; x < width; ++x) {
|
for (qint32 x = 0; x < width; ++x) {
|
||||||
auto xcn = x * cn;
|
auto xcn = x * cn;
|
||||||
auto alpha = *(s + xcn + ac);
|
auto alpha = *(s + xcn + ac);
|
||||||
if (alpha > 0)
|
if (alpha > 0)
|
||||||
*(s + xcn + c) = ((*(s + xcn + c) + alpha - max) * max + alpha / 2) / alpha;
|
*(s + xcn + c) = ((*(s + xcn + c) + alpha - max) * max + alpha / 2) / alpha;
|
||||||
}
|
}
|
||||||
}
|
} else if (conv == PremulConversion::PSLab2A) {
|
||||||
else if (conv == PremulConversion::PSLab2A) {
|
|
||||||
for (qint32 x = 0; x < width; ++x) {
|
for (qint32 x = 0; x < width; ++x) {
|
||||||
auto xcn = x * cn;
|
auto xcn = x * cn;
|
||||||
auto alpha = *(s + xcn + ac);
|
auto alpha = *(s + xcn + ac);
|
||||||
@ -906,8 +898,8 @@ inline void monoInvert(uchar *target, const char* source, qint32 bytes)
|
|||||||
template<class T>
|
template<class T>
|
||||||
inline void rawChannelsCopy(uchar *target, qint32 targetChannels, const char *source, qint32 sourceChannels, qint32 width)
|
inline void rawChannelsCopy(uchar *target, qint32 targetChannels, const char *source, qint32 sourceChannels, qint32 width)
|
||||||
{
|
{
|
||||||
auto s = reinterpret_cast<const T*>(source);
|
auto s = reinterpret_cast<const T *>(source);
|
||||||
auto t = reinterpret_cast<T*>(target);
|
auto t = reinterpret_cast<T *>(target);
|
||||||
for (qint32 c = 0, cs = std::min(targetChannels, sourceChannels); c < cs; ++c) {
|
for (qint32 c = 0, cs = std::min(targetChannels, sourceChannels); c < cs; ++c) {
|
||||||
for (qint32 x = 0; x < width; ++x) {
|
for (qint32 x = 0; x < width; ++x) {
|
||||||
t[x * targetChannels + c] = s[x * sourceChannels + c];
|
t[x * targetChannels + c] = s[x * sourceChannels + c];
|
||||||
@ -1109,7 +1101,7 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<quint32> strides(header.height * header.channel_count, raw_count);
|
QVector<quint32> strides(header.height * header.channel_count, raw_count);
|
||||||
// Read the compressed stride sizes
|
// Read the compressed stride sizes
|
||||||
if (compression) {
|
if (compression) {
|
||||||
for (auto&& v : strides) {
|
for (auto&& v : strides) {
|
||||||
@ -1124,7 +1116,7 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
|
|||||||
}
|
}
|
||||||
// calculate the absolute file positions of each stride (required when a colorspace conversion should be done)
|
// calculate the absolute file positions of each stride (required when a colorspace conversion should be done)
|
||||||
auto device = stream.device();
|
auto device = stream.device();
|
||||||
QList<quint64> stridePositions(strides.size());
|
QVector<quint64> stridePositions(strides.size());
|
||||||
if (!stridePositions.isEmpty()) {
|
if (!stridePositions.isEmpty()) {
|
||||||
stridePositions[0] = device->pos();
|
stridePositions[0] = device->pos();
|
||||||
}
|
}
|
||||||
@ -1139,15 +1131,15 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
|
|||||||
// clang-format off
|
// clang-format off
|
||||||
// checks the need of color conversion (that requires random access to the image)
|
// checks the need of color conversion (that requires random access to the image)
|
||||||
auto randomAccess = (header.color_mode == CM_CMYK) ||
|
auto randomAccess = (header.color_mode == CM_CMYK) ||
|
||||||
(header.color_mode == CM_LABCOLOR) ||
|
(header.color_mode == CM_LABCOLOR) ||
|
||||||
(header.color_mode == CM_MULTICHANNEL) ||
|
(header.color_mode == CM_MULTICHANNEL) ||
|
||||||
(header.color_mode != CM_INDEXED && img.hasAlphaChannel());
|
(header.color_mode != CM_INDEXED && img.hasAlphaChannel());
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
if (randomAccess) {
|
if (randomAccess) {
|
||||||
// In order to make a colorspace transformation, we need all channels of a scanline
|
// In order to make a colorspace transformation, we need all channels of a scanline
|
||||||
QByteArray psdScanline;
|
QByteArray psdScanline;
|
||||||
psdScanline.resize(qsizetype(header.width * header.depth * header.channel_count + 7) / 8);
|
psdScanline.resize(qsizetype(header.width * std::min(header.depth, quint16(16)) * header.channel_count + 7) / 8);
|
||||||
for (qint32 y = 0, h = header.height; y < h; ++y) {
|
for (qint32 y = 0, h = header.height; y < h; ++y) {
|
||||||
for (qint32 c = 0; c < header.channel_count; ++c) {
|
for (qint32 c = 0; c < header.channel_count; ++c) {
|
||||||
auto strideNumber = c * qsizetype(h) + y;
|
auto strideNumber = c * qsizetype(h) + y;
|
||||||
@ -1164,37 +1156,32 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
|
|||||||
auto scanLine = reinterpret_cast<unsigned char*>(psdScanline.data());
|
auto scanLine = reinterpret_cast<unsigned char*>(psdScanline.data());
|
||||||
if (header.depth == 8) {
|
if (header.depth == 8) {
|
||||||
planarToChunchy<quint8>(scanLine, rawStride.data(), header.width, c, header.channel_count);
|
planarToChunchy<quint8>(scanLine, rawStride.data(), header.width, c, header.channel_count);
|
||||||
}
|
} else if (header.depth == 16) {
|
||||||
else if (header.depth == 16) {
|
|
||||||
planarToChunchy<quint16>(scanLine, rawStride.data(), header.width, c, header.channel_count);
|
planarToChunchy<quint16>(scanLine, rawStride.data(), header.width, c, header.channel_count);
|
||||||
}
|
} else if (header.depth == 32) {
|
||||||
else if (header.depth == 32) {
|
planarToChunchyFloatToUInt16<float>(scanLine, rawStride.data(), header.width, c, header.channel_count);
|
||||||
planarToChunchy<float>(scanLine, rawStride.data(), header.width, c, header.channel_count);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert premultiplied data to unassociated data
|
// Convert premultiplied data to unassociated data
|
||||||
if (img.hasAlphaChannel()) {
|
if (img.hasAlphaChannel()) {
|
||||||
auto scanLine = reinterpret_cast<char*>(psdScanline.data());
|
|
||||||
if (header.color_mode == CM_CMYK) {
|
if (header.color_mode == CM_CMYK) {
|
||||||
if (header.depth == 8)
|
if (header.depth == 8)
|
||||||
premulConversion<quint8>(scanLine, header.width, 4, header.channel_count, PremulConversion::PS2A);
|
premulConversion<quint8>(psdScanline.data(), header.width, 4, header.channel_count, PremulConversion::PS2A);
|
||||||
else if (header.depth == 16)
|
else if (header.depth == 16)
|
||||||
premulConversion<quint16>(scanLine, header.width, 4, header.channel_count, PremulConversion::PS2A);
|
premulConversion<quint16>(psdScanline.data(), header.width, 4, header.channel_count, PremulConversion::PS2A);
|
||||||
}
|
}
|
||||||
if (header.color_mode == CM_LABCOLOR) {
|
if (header.color_mode == CM_LABCOLOR) {
|
||||||
if (header.depth == 8)
|
if (header.depth == 8)
|
||||||
premulConversion<quint8>(scanLine, header.width, 3, header.channel_count, PremulConversion::PSLab2A);
|
premulConversion<quint8>(psdScanline.data(), header.width, 3, header.channel_count, PremulConversion::PSLab2A);
|
||||||
else if (header.depth == 16)
|
else if (header.depth == 16)
|
||||||
premulConversion<quint16>(scanLine, header.width, 3, header.channel_count, PremulConversion::PSLab2A);
|
premulConversion<quint16>(psdScanline.data(), header.width, 3, header.channel_count, PremulConversion::PSLab2A);
|
||||||
}
|
}
|
||||||
if (header.color_mode == CM_RGB) {
|
if (header.color_mode == CM_RGB) {
|
||||||
if (header.depth == 8)
|
if (header.depth == 8)
|
||||||
premulConversion<quint8>(scanLine, header.width, 3, header.channel_count, PremulConversion::PS2P);
|
premulConversion<quint8>(psdScanline.data(), header.width, 3, header.channel_count, PremulConversion::PS2P);
|
||||||
else if (header.depth == 16)
|
else if (header.depth == 16 || header.depth == 32)
|
||||||
premulConversion<quint16>(scanLine, header.width, 3, header.channel_count, PremulConversion::PS2P);
|
premulConversion<quint16>(psdScanline.data(), header.width, 3, header.channel_count, PremulConversion::PS2P);
|
||||||
else if (header.depth == 32)
|
|
||||||
premulConversion<float>(scanLine, header.width, 3, header.channel_count, PremulConversion::PS2P);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1214,14 +1201,11 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
|
|||||||
if (header.color_mode == CM_RGB) {
|
if (header.color_mode == CM_RGB) {
|
||||||
if (header.depth == 8)
|
if (header.depth == 8)
|
||||||
rawChannelsCopy<quint8>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width);
|
rawChannelsCopy<quint8>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width);
|
||||||
else if (header.depth == 16)
|
else if (header.depth == 16 || header.depth == 32)
|
||||||
rawChannelsCopy<quint16>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width);
|
rawChannelsCopy<quint16>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width);
|
||||||
else if (header.depth == 32)
|
|
||||||
rawChannelsCopy<float>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
// Linear read (no position jumps): optimized code usable only for the colorspaces supported by QImage
|
// Linear read (no position jumps): optimized code usable only for the colorspaces supported by QImage
|
||||||
for (qint32 c = 0; c < channel_num; ++c) {
|
for (qint32 c = 0; c < channel_num; ++c) {
|
||||||
for (qint32 y = 0, h = header.height; y < h; ++y) {
|
for (qint32 y = 0, h = header.height; y < h; ++y) {
|
||||||
@ -1234,23 +1218,25 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
|
|||||||
auto scanLine = img.scanLine(y);
|
auto scanLine = img.scanLine(y);
|
||||||
if (header.depth == 1) { // Bitmap
|
if (header.depth == 1) { // Bitmap
|
||||||
monoInvert(scanLine, rawStride.data(), std::min(rawStride.size(), img.bytesPerLine()));
|
monoInvert(scanLine, rawStride.data(), std::min(rawStride.size(), img.bytesPerLine()));
|
||||||
}
|
} else if (header.depth == 8) { // 8-bits images: Indexed, Grayscale, RGB/RGBA
|
||||||
else if (header.depth == 8) { // 8-bits images: Indexed, Grayscale, RGB/RGBA
|
|
||||||
planarToChunchy<quint8>(scanLine, rawStride.data(), header.width, c, imgChannels);
|
planarToChunchy<quint8>(scanLine, rawStride.data(), header.width, c, imgChannels);
|
||||||
}
|
} else if (header.depth == 16) { // 16-bits integer images: Grayscale, RGB/RGBA
|
||||||
else if (header.depth == 16) { // 16-bits integer images: Grayscale, RGB/RGBA
|
|
||||||
planarToChunchy<quint16>(scanLine, rawStride.data(), header.width, c, imgChannels);
|
planarToChunchy<quint16>(scanLine, rawStride.data(), header.width, c, imgChannels);
|
||||||
}
|
} else if (header.depth == 32) { // 32-bits float images: Grayscale, RGB/RGBA (coverted to equivalent integer 16-bits)
|
||||||
else if (header.depth == 32 && header.color_mode == CM_RGB) { // 32-bits float images: RGB/RGBA
|
|
||||||
planarToChunchy<float>(scanLine, rawStride.data(), header.width, c, imgChannels);
|
|
||||||
}
|
|
||||||
else if (header.depth == 32 && header.color_mode == CM_GRAYSCALE) { // 32-bits float images: Grayscale (coverted to equivalent integer 16-bits)
|
|
||||||
planarToChunchyFloatToUInt16<float>(scanLine, rawStride.data(), header.width, c, imgChannels);
|
planarToChunchyFloatToUInt16<float>(scanLine, rawStride.data(), header.width, c, imgChannels);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LAB conversion generates a sRGB image
|
||||||
|
if (header.color_mode == CM_LABCOLOR) {
|
||||||
|
#ifdef PSD_FAST_LAB_CONVERSION
|
||||||
|
img.setColorSpace(QColorSpace(QColorSpace::SRgbLinear));
|
||||||
|
#else
|
||||||
|
img.setColorSpace(QColorSpace(QColorSpace::SRgb));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
// Resolution info
|
// Resolution info
|
||||||
if (!setResolution(img, irs)) {
|
if (!setResolution(img, irs)) {
|
||||||
@ -1258,15 +1244,7 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ICC profile
|
// ICC profile
|
||||||
if (header.color_mode == CM_LABCOLOR) {
|
if (!setColorSpace(img, irs)) {
|
||||||
// LAB conversion generates a sRGB image
|
|
||||||
#ifdef PSD_FAST_LAB_CONVERSION
|
|
||||||
img.setColorSpace(QColorSpace(QColorSpace::SRgbLinear));
|
|
||||||
#else
|
|
||||||
img.setColorSpace(QColorSpace(QColorSpace::SRgb));
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
else if (!setColorSpace(img, irs)) {
|
|
||||||
// qDebug() << "No colorspace info set!";
|
// qDebug() << "No colorspace info set!";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
7
src/imageformats/psd.desktop
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Type=Service
|
||||||
|
X-KDE-ServiceTypes=QImageIOPlugins
|
||||||
|
X-KDE-ImageFormat=psd,psb,pdd,psdt
|
||||||
|
X-KDE-MimeType=image/vnd.adobe.photoshop
|
||||||
|
X-KDE-Read=true
|
||||||
|
X-KDE-Write=false
|
7
src/imageformats/qoi.desktop
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Type=Service
|
||||||
|
X-KDE-ServiceTypes=QImageIOPlugins
|
||||||
|
X-KDE-ImageFormat=qoi
|
||||||
|
X-KDE-MimeType=image/qoi
|
||||||
|
X-KDE-Read=true
|
||||||
|
X-KDE-Write=true
|
@ -3,7 +3,6 @@
|
|||||||
SPDX-FileCopyrightText: 2003 Dominik Seichter <domseichter@web.de>
|
SPDX-FileCopyrightText: 2003 Dominik Seichter <domseichter@web.de>
|
||||||
SPDX-FileCopyrightText: 2004 Ignacio Castaño <castano@ludicon.com>
|
SPDX-FileCopyrightText: 2004 Ignacio Castaño <castano@ludicon.com>
|
||||||
SPDX-FileCopyrightText: 2010 Troy Unrau <troy@kde.org>
|
SPDX-FileCopyrightText: 2010 Troy Unrau <troy@kde.org>
|
||||||
SPDX-FileCopyrightText: 2023 Mirco Miranda <mircomir@outlook.com>
|
|
||||||
|
|
||||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||||
*/
|
*/
|
||||||
@ -40,14 +39,14 @@ enum RASColorMapType {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct RasHeader {
|
struct RasHeader {
|
||||||
quint32 MagicNumber = 0;
|
quint32 MagicNumber;
|
||||||
quint32 Width = 0;
|
quint32 Width;
|
||||||
quint32 Height = 0;
|
quint32 Height;
|
||||||
quint32 Depth = 0;
|
quint32 Depth;
|
||||||
quint32 Length = 0;
|
quint32 Length;
|
||||||
quint32 Type = 0;
|
quint32 Type;
|
||||||
quint32 ColorMapType = 0;
|
quint32 ColorMapType;
|
||||||
quint32 ColorMapLength = 0;
|
quint32 ColorMapLength;
|
||||||
enum {
|
enum {
|
||||||
SIZE = 32,
|
SIZE = 32,
|
||||||
}; // 8 fields of four bytes each
|
}; // 8 fields of four bytes each
|
||||||
@ -81,251 +80,152 @@ static bool IsSupported(const RasHeader &head)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// check for an appropriate depth
|
// check for an appropriate depth
|
||||||
if (head.Depth != 1 && head.Depth != 8 && head.Depth != 24 && head.Depth != 32) {
|
// we support 8bit+palette, 24bit and 32bit ONLY!
|
||||||
return false;
|
// TODO: add support for 1bit
|
||||||
}
|
if (!((head.Depth == 8 && head.ColorMapType == 1) || head.Depth == 24 || head.Depth == 32)) {
|
||||||
if (head.Width == 0 || head.Height == 0) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// the Type field adds support for RLE(BGR), RGB and other encodings
|
// the Type field adds support for RLE(BGR), RGB and other encodings
|
||||||
// we support Type 1: Normal(BGR), Type 2: RLE(BGR) and Type 3: Normal(RGB) ONLY!
|
// we support Type 1: Normal(BGR) and Type 3: Normal(RGB) ONLY!
|
||||||
// TODO: add support for Type 4,5: TIFF/IFF
|
// TODO: add support for Type 2: RLE(BGR) & Type 4,5: TIFF/IFF
|
||||||
if (!(head.Type == RAS_TYPE_STANDARD || head.Type == RAS_TYPE_RGB_FORMAT || head.Type == RAS_TYPE_BYTE_ENCODED)) {
|
if (!(head.Type == 1 || head.Type == 3)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Old files didn't have Length set - reject them for now
|
||||||
|
// TODO: add length recalculation to support old files
|
||||||
|
if (!head.Length) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static QImage::Format imageFormat(const RasHeader &header)
|
|
||||||
{
|
|
||||||
if (header.ColorMapType == RAS_COLOR_MAP_TYPE_RGB) {
|
|
||||||
return QImage::Format_Indexed8;
|
|
||||||
}
|
|
||||||
if (header.Depth == 8 && header.ColorMapType == RAS_COLOR_MAP_TYPE_NONE) {
|
|
||||||
return QImage::Format_Grayscale8;
|
|
||||||
}
|
|
||||||
if (header.Depth == 1) {
|
|
||||||
return QImage::Format_Mono;
|
|
||||||
}
|
|
||||||
return QImage::Format_RGB32;
|
|
||||||
}
|
|
||||||
|
|
||||||
class LineDecoder
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
LineDecoder(QIODevice *d, const RasHeader &ras)
|
|
||||||
: device(d)
|
|
||||||
, header(ras)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray readLine(qint64 size)
|
|
||||||
{
|
|
||||||
/* *** uncompressed
|
|
||||||
*/
|
|
||||||
if (header.Type != RAS_TYPE_BYTE_ENCODED) {
|
|
||||||
return device->read(size);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* *** rle compressed
|
|
||||||
* The Run-length encoding (RLE) scheme optionally used in Sun Raster
|
|
||||||
* files (Type = 0002h) is used to encode bytes of image data
|
|
||||||
* separately. RLE encoding may be found in any Sun Raster file
|
|
||||||
* regardless of the type of image data it contains.
|
|
||||||
*
|
|
||||||
* The RLE packets are typically three bytes in size:
|
|
||||||
* - The first byte is a Flag Value indicating the type of RLE packet.
|
|
||||||
* - The second byte is the Run Count.
|
|
||||||
* - The third byte is the Run Value.
|
|
||||||
*
|
|
||||||
* A Flag Value of 80h is followed by a Run Count in the range of 01h
|
|
||||||
* to FFh. The Run Value follows the Run count and is in the range of
|
|
||||||
* 00h to FFh. The pixel run is the Run Value repeated Run Count times.
|
|
||||||
* There are two exceptions to this algorithm. First, if the Run Count
|
|
||||||
* following the Flag Value is 00h, this is an indication that the run
|
|
||||||
* is a single byte in length and has a value of 80h. And second, if
|
|
||||||
* the Flag Value is not 80h, then it is assumed that the data is
|
|
||||||
* unencoded pixel data and is written directly to the output stream.
|
|
||||||
*
|
|
||||||
* source: http://www.fileformat.info/format/sunraster/egff.htm
|
|
||||||
*/
|
|
||||||
for (qsizetype psz = 0, ptr = 0; uncBuffer.size() < size;) {
|
|
||||||
rleBuffer.append(device->read(std::min(qint64(32768), size)));
|
|
||||||
qsizetype sz = rleBuffer.size();
|
|
||||||
if (psz == sz) {
|
|
||||||
break; // avoid infinite loop (data corrupted?!)
|
|
||||||
}
|
|
||||||
auto data = reinterpret_cast<uchar *>(rleBuffer.data());
|
|
||||||
for (; ptr < sz;) {
|
|
||||||
auto flag = data[ptr++];
|
|
||||||
if (flag == 0x80) {
|
|
||||||
if (ptr >= sz) {
|
|
||||||
ptr -= 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
auto cnt = data[ptr++];
|
|
||||||
if (cnt == 0) {
|
|
||||||
uncBuffer.append(char(0x80));
|
|
||||||
continue;
|
|
||||||
} else if (ptr >= sz) {
|
|
||||||
ptr -= 2;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
auto val = data[ptr++];
|
|
||||||
uncBuffer.append(QByteArray(1 + cnt, char(val)));
|
|
||||||
} else {
|
|
||||||
uncBuffer.append(char(flag));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (ptr) { // remove consumed data
|
|
||||||
rleBuffer.remove(0, ptr);
|
|
||||||
ptr = 0;
|
|
||||||
}
|
|
||||||
psz = rleBuffer.size();
|
|
||||||
}
|
|
||||||
if (uncBuffer.size() < size) {
|
|
||||||
return QByteArray(); // something wrong
|
|
||||||
}
|
|
||||||
auto line = uncBuffer.mid(0, size);
|
|
||||||
uncBuffer.remove(0, line.size()); // remove consumed data
|
|
||||||
return line;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
QIODevice *device;
|
|
||||||
RasHeader header;
|
|
||||||
|
|
||||||
// RLE decoding buffers
|
|
||||||
QByteArray rleBuffer;
|
|
||||||
QByteArray uncBuffer;
|
|
||||||
};
|
|
||||||
|
|
||||||
static bool LoadRAS(QDataStream &s, const RasHeader &ras, QImage &img)
|
static bool LoadRAS(QDataStream &s, const RasHeader &ras, QImage &img)
|
||||||
{
|
{
|
||||||
s.device()->seek(RasHeader::SIZE);
|
s.device()->seek(RasHeader::SIZE);
|
||||||
|
|
||||||
// The width of a scan line is always a multiple of 16 bits, padded when necessary.
|
if (ras.ColorMapLength > kMaxQVectorSize) {
|
||||||
auto rasLineSize = (qint64(ras.Width) * ras.Depth + 7) / 8;
|
qWarning() << "LoadRAS() unsupported image color map length in file header" << ras.ColorMapLength;
|
||||||
if (rasLineSize & 1)
|
|
||||||
++rasLineSize;
|
|
||||||
if (rasLineSize > kMaxQVectorSize) {
|
|
||||||
qWarning() << "LoadRAS() unsupported line size" << rasLineSize;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allocate image
|
|
||||||
img = imageAlloc(ras.Width, ras.Height, imageFormat(ras));
|
|
||||||
if (img.isNull()) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read palette if needed.
|
// Read palette if needed.
|
||||||
if (ras.ColorMapType == RAS_COLOR_MAP_TYPE_RGB) {
|
QVector<quint8> palette(ras.ColorMapLength);
|
||||||
QList<quint8> palette(ras.ColorMapLength);
|
if (ras.ColorMapType == 1) {
|
||||||
for (quint32 i = 0; i < ras.ColorMapLength; ++i) {
|
for (quint32 i = 0; i < ras.ColorMapLength; ++i) {
|
||||||
s >> palette[i];
|
s >> palette[i];
|
||||||
}
|
}
|
||||||
QList<QRgb> colorTable;
|
}
|
||||||
for (quint32 i = 0, n = ras.ColorMapLength / 3; i < n; ++i) {
|
|
||||||
colorTable << qRgb(palette.at(i), palette.at(i + n), palette.at(i + 2 * n));
|
const int bpp = ras.Depth / 8;
|
||||||
|
if (ras.Height == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (bpp == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (ras.Length / ras.Height / bpp < ras.Width) {
|
||||||
|
qWarning() << "LoadRAS() mistmatch between height and width" << ras.Width << ras.Height << ras.Length << ras.Depth;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (ras.Length > kMaxQVectorSize) {
|
||||||
|
qWarning() << "LoadRAS() unsupported image length in file header" << ras.Length;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// each line must be a factor of 16 bits, so they may contain padding
|
||||||
|
// this will be 1 if padding required, 0 otherwise
|
||||||
|
const int paddingrequired = (ras.Width * bpp % 2);
|
||||||
|
|
||||||
|
// qDebug() << "paddingrequired: " << paddingrequired;
|
||||||
|
// don't trust ras.Length
|
||||||
|
QVector<quint8> input(ras.Length);
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
while (!s.atEnd() && i < input.size()) {
|
||||||
|
s >> input[i];
|
||||||
|
// I guess we need to find out if we're at the end of a line
|
||||||
|
if (paddingrequired && i != 0 && !(i % (ras.Width * bpp))) {
|
||||||
|
s >> input[i];
|
||||||
}
|
}
|
||||||
for (; colorTable.size() < 256;) {
|
i++;
|
||||||
colorTable << qRgb(255, 255, 255);
|
}
|
||||||
}
|
|
||||||
img.setColorTable(colorTable);
|
// Allocate image
|
||||||
if (s.status() != QDataStream::Ok) {
|
img = imageAlloc(ras.Width, ras.Height, QImage::Format_ARGB32);
|
||||||
return false;
|
if (img.isNull()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reconstruct image from RGB palette if we have a palette
|
||||||
|
// TODO: make generic so it works with 24bit or 32bit palettes
|
||||||
|
if (ras.ColorMapType == 1 && ras.Depth == 8) {
|
||||||
|
quint8 red;
|
||||||
|
quint8 green;
|
||||||
|
quint8 blue;
|
||||||
|
for (quint32 y = 0; y < ras.Height; y++) {
|
||||||
|
for (quint32 x = 0; x < ras.Width; x++) {
|
||||||
|
red = palette.value((int)input[y * ras.Width + x]);
|
||||||
|
green = palette.value((int)input[y * ras.Width + x] + (ras.ColorMapLength / 3));
|
||||||
|
blue = palette.value((int)input[y * ras.Width + x] + 2 * (ras.ColorMapLength / 3));
|
||||||
|
img.setPixel(x, y, qRgb(red, green, blue));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LineDecoder dec(s.device(), ras);
|
if (ras.ColorMapType == 0 && ras.Depth == 24 && (ras.Type == 1 || ras.Type == 2)) {
|
||||||
auto bytesPerLine = std::min(img.bytesPerLine(), qsizetype(rasLineSize));
|
quint8 red;
|
||||||
for (quint32 y = 0; y < ras.Height; ++y) {
|
quint8 green;
|
||||||
auto rasLine = dec.readLine(rasLineSize);
|
quint8 blue;
|
||||||
if (rasLine.size() != rasLineSize) {
|
for (quint32 y = 0; y < ras.Height; y++) {
|
||||||
qWarning() << "LoadRAS() unable to read line" << y << ": the seems corrupted!";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Grayscale 1-bit / Grayscale 8-bit (never seen)
|
|
||||||
if (ras.ColorMapType == RAS_COLOR_MAP_TYPE_NONE && (ras.Depth == 1 || ras.Depth == 8)) {
|
|
||||||
for (auto &&b : rasLine) {
|
|
||||||
b = ~b;
|
|
||||||
}
|
|
||||||
std::memcpy(img.scanLine(y), rasLine.constData(), bytesPerLine);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Image with palette
|
|
||||||
if (ras.ColorMapType == RAS_COLOR_MAP_TYPE_RGB && (ras.Depth == 1 || ras.Depth == 8)) {
|
|
||||||
std::memcpy(img.scanLine(y), rasLine.constData(), bytesPerLine);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// BGR 24-bit
|
|
||||||
if (ras.ColorMapType == RAS_COLOR_MAP_TYPE_NONE && ras.Depth == 24 && (ras.Type == RAS_TYPE_STANDARD || ras.Type == RAS_TYPE_BYTE_ENCODED)) {
|
|
||||||
quint8 red;
|
|
||||||
quint8 green;
|
|
||||||
quint8 blue;
|
|
||||||
auto scanLine = reinterpret_cast<QRgb *>(img.scanLine(y));
|
|
||||||
for (quint32 x = 0; x < ras.Width; x++) {
|
for (quint32 x = 0; x < ras.Width; x++) {
|
||||||
red = rasLine.at(x * 3 + 2);
|
red = input[y * 3 * ras.Width + x * 3 + 2];
|
||||||
green = rasLine.at(x * 3 + 1);
|
green = input[y * 3 * ras.Width + x * 3 + 1];
|
||||||
blue = rasLine.at(x * 3);
|
blue = input[y * 3 * ras.Width + x * 3];
|
||||||
*(scanLine + x) = qRgb(red, green, blue);
|
img.setPixel(x, y, qRgb(red, green, blue));
|
||||||
}
|
}
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// RGB 24-bit
|
if (ras.ColorMapType == 0 && ras.Depth == 24 && ras.Type == 3) {
|
||||||
if (ras.ColorMapType == RAS_COLOR_MAP_TYPE_NONE && ras.Depth == 24 && ras.Type == RAS_TYPE_RGB_FORMAT) {
|
quint8 red;
|
||||||
quint8 red;
|
quint8 green;
|
||||||
quint8 green;
|
quint8 blue;
|
||||||
quint8 blue;
|
for (quint32 y = 0; y < ras.Height; y++) {
|
||||||
auto scanLine = reinterpret_cast<QRgb *>(img.scanLine(y));
|
|
||||||
for (quint32 x = 0; x < ras.Width; x++) {
|
for (quint32 x = 0; x < ras.Width; x++) {
|
||||||
red = rasLine.at(x * 3);
|
red = input[y * 3 * ras.Width + x * 3];
|
||||||
green = rasLine.at(x * 3 + 1);
|
green = input[y * 3 * ras.Width + x * 3 + 1];
|
||||||
blue = rasLine.at(x * 3 + 2);
|
blue = input[y * 3 * ras.Width + x * 3 + 2];
|
||||||
*(scanLine + x) = qRgb(red, green, blue);
|
img.setPixel(x, y, qRgb(red, green, blue));
|
||||||
}
|
}
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// BGR 32-bit (not tested: test case missing)
|
if (ras.ColorMapType == 0 && ras.Depth == 32 && (ras.Type == 1 || ras.Type == 2)) {
|
||||||
if (ras.ColorMapType == RAS_COLOR_MAP_TYPE_NONE && ras.Depth == 32 && (ras.Type == RAS_TYPE_STANDARD || ras.Type == RAS_TYPE_BYTE_ENCODED)) {
|
quint8 red;
|
||||||
quint8 red;
|
quint8 green;
|
||||||
quint8 green;
|
quint8 blue;
|
||||||
quint8 blue;
|
for (quint32 y = 0; y < ras.Height; y++) {
|
||||||
auto scanLine = reinterpret_cast<QRgb *>(img.scanLine(y));
|
|
||||||
for (quint32 x = 0; x < ras.Width; x++) {
|
for (quint32 x = 0; x < ras.Width; x++) {
|
||||||
red = rasLine.at(x * 4 + 3);
|
red = input[y * 4 * ras.Width + x * 4 + 3];
|
||||||
green = rasLine.at(x * 4 + 2);
|
green = input[y * 4 * ras.Width + x * 4 + 2];
|
||||||
blue = rasLine.at(x * 4 + 1);
|
blue = input[y * 4 * ras.Width + x * 4 + 1];
|
||||||
*(scanLine + x) = qRgb(red, green, blue);
|
img.setPixel(x, y, qRgb(red, green, blue));
|
||||||
}
|
}
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// RGB 32-bit (tested: test case missing due to image too large)
|
if (ras.ColorMapType == 0 && ras.Depth == 32 && ras.Type == 3) {
|
||||||
if (ras.ColorMapType == RAS_COLOR_MAP_TYPE_NONE && ras.Depth == 32 && ras.Type == RAS_TYPE_RGB_FORMAT) {
|
quint8 red;
|
||||||
quint8 red;
|
quint8 green;
|
||||||
quint8 green;
|
quint8 blue;
|
||||||
quint8 blue;
|
for (quint32 y = 0; y < ras.Height; y++) {
|
||||||
auto scanLine = reinterpret_cast<QRgb *>(img.scanLine(y));
|
|
||||||
for (quint32 x = 0; x < ras.Width; x++) {
|
for (quint32 x = 0; x < ras.Width; x++) {
|
||||||
red = rasLine.at(x * 4 + 1);
|
red = input[y * 4 * ras.Width + x * 4 + 1];
|
||||||
green = rasLine.at(x * 4 + 2);
|
green = input[y * 4 * ras.Width + x * 4 + 2];
|
||||||
blue = rasLine.at(x * 4 + 3);
|
blue = input[y * 4 * ras.Width + x * 4 + 3];
|
||||||
*(scanLine + x) = qRgb(red, green, blue);
|
img.setPixel(x, y, qRgb(red, green, blue));
|
||||||
}
|
}
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
qWarning() << "LoadRAS() unsupported format!"
|
|
||||||
<< "ColorMapType:" << ras.ColorMapType << "Type:" << ras.Type << "Depth:" << ras.Depth;
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -383,8 +283,16 @@ bool RASHandler::read(QImage *outImage)
|
|||||||
RasHeader ras;
|
RasHeader ras;
|
||||||
s >> ras;
|
s >> ras;
|
||||||
|
|
||||||
if (ras.ColorMapLength > kMaxQVectorSize) {
|
if (ras.ColorMapLength > std::numeric_limits<int>::max()) {
|
||||||
qWarning() << "LoadRAS() unsupported image color map length in file header" << ras.ColorMapLength;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: add support for old versions of RAS where Length may be zero in header
|
||||||
|
s.device()->seek(RasHeader::SIZE + ras.Length + ras.ColorMapLength);
|
||||||
|
|
||||||
|
// Check image file format. Type 2 is RLE, which causing seeking to be silly.
|
||||||
|
if (!s.atEnd() && ras.Type != 2) {
|
||||||
|
// qDebug() << "This RAS file is not valid, or an older version of the format.";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -406,65 +314,9 @@ bool RASHandler::read(QImage *outImage)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RASHandler::supportsOption(ImageOption option) const
|
|
||||||
{
|
|
||||||
if (option == QImageIOHandler::Size) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (option == QImageIOHandler::ImageFormat) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariant RASHandler::option(ImageOption option) const
|
|
||||||
{
|
|
||||||
QVariant v;
|
|
||||||
|
|
||||||
if (option == QImageIOHandler::Size) {
|
|
||||||
if (auto d = device()) {
|
|
||||||
// transactions works on both random and sequential devices
|
|
||||||
d->startTransaction();
|
|
||||||
auto ba = d->read(RasHeader::SIZE);
|
|
||||||
d->rollbackTransaction();
|
|
||||||
|
|
||||||
QDataStream s(ba);
|
|
||||||
s.setByteOrder(QDataStream::BigEndian);
|
|
||||||
|
|
||||||
RasHeader header;
|
|
||||||
s >> header;
|
|
||||||
|
|
||||||
if (s.status() == QDataStream::Ok && IsSupported(header)) {
|
|
||||||
v = QVariant::fromValue(QSize(header.Width, header.Height));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (option == QImageIOHandler::ImageFormat) {
|
|
||||||
if (auto d = device()) {
|
|
||||||
// transactions works on both random and sequential devices
|
|
||||||
d->startTransaction();
|
|
||||||
auto ba = d->read(RasHeader::SIZE);
|
|
||||||
d->rollbackTransaction();
|
|
||||||
|
|
||||||
QDataStream s(ba);
|
|
||||||
s.setByteOrder(QDataStream::BigEndian);
|
|
||||||
|
|
||||||
RasHeader header;
|
|
||||||
s >> header;
|
|
||||||
|
|
||||||
if (s.status() == QDataStream::Ok && IsSupported(header)) {
|
|
||||||
v = QVariant::fromValue(imageFormat(header));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
QImageIOPlugin::Capabilities RASPlugin::capabilities(QIODevice *device, const QByteArray &format) const
|
QImageIOPlugin::Capabilities RASPlugin::capabilities(QIODevice *device, const QByteArray &format) const
|
||||||
{
|
{
|
||||||
if (format == "im1" || format == "im8" || format == "im24" || format == "im32" || format == "ras" || format == "sun") {
|
if (format == "ras") {
|
||||||
return Capabilities(CanRead);
|
return Capabilities(CanRead);
|
||||||
}
|
}
|
||||||
if (!format.isEmpty()) {
|
if (!format.isEmpty()) {
|
||||||
|
7
src/imageformats/ras.desktop
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Type=Service
|
||||||
|
X-KDE-ServiceTypes=QImageIOPlugins
|
||||||
|
X-KDE-ImageFormat=ras
|
||||||
|
X-KDE-MimeType=image/x-sun-raster
|
||||||
|
X-KDE-Read=true
|
||||||
|
X-KDE-Write=false
|
@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"Keys": [ "im1", "im8", "im24", "im32", "ras", "sun" ],
|
"Keys": [ "ras" ],
|
||||||
"MimeTypes": [ "image/x-sun-raster", "image/x-sun-raster", "image/x-sun-raster", "image/x-sun-raster", "image/x-sun-raster", "image/x-sun-raster" ]
|
"MimeTypes": [ "image/x-sun-raster" ]
|
||||||
}
|
}
|
||||||
|
@ -19,9 +19,6 @@ public:
|
|||||||
bool canRead() const override;
|
bool canRead() const override;
|
||||||
bool read(QImage *image) override;
|
bool read(QImage *image) override;
|
||||||
|
|
||||||
bool supportsOption(QImageIOHandler::ImageOption option) const override;
|
|
||||||
QVariant option(QImageIOHandler::ImageOption option) const override;
|
|
||||||
|
|
||||||
static bool canRead(QIODevice *device);
|
static bool canRead(QIODevice *device);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
#include <QSet>
|
#include <QSet>
|
||||||
#include <QTimeZone>
|
|
||||||
|
|
||||||
#if defined(Q_OS_WINDOWS) && !defined(NOMINMAX)
|
#if defined(Q_OS_WINDOWS) && !defined(NOMINMAX)
|
||||||
#define NOMINMAX
|
#define NOMINMAX
|
||||||
@ -223,7 +222,7 @@ QString createTag(char *asciiz, const char *tag)
|
|||||||
|
|
||||||
QString createTimeTag(time_t time, const char *tag)
|
QString createTimeTag(time_t time, const char *tag)
|
||||||
{
|
{
|
||||||
auto value = QDateTime::fromSecsSinceEpoch(time, QTimeZone::utc());
|
auto value = QDateTime::fromSecsSinceEpoch(time, Qt::UTC);
|
||||||
if (value.isValid() && time > 0) {
|
if (value.isValid() && time > 0) {
|
||||||
return createTag(value.toString(Qt::ISODate), tag);
|
return createTag(value.toString(Qt::ISODate), tag);
|
||||||
}
|
}
|
||||||
@ -418,7 +417,7 @@ inline void rgbToRgbX(uchar *target, const uchar *source, qint32 targetSize, qin
|
|||||||
#define C_NR(a) (((a) & 0x3) << 17)
|
#define C_NR(a) (((a) & 0x3) << 17)
|
||||||
#define C_FC(a) (((a) & 0x1) << 19)
|
#define C_FC(a) (((a) & 0x1) << 19)
|
||||||
#define C_SR(a) (((a) & 0x1) << 20)
|
#define C_SR(a) (((a) & 0x1) << 20)
|
||||||
#define C_FLAGS(a) (((a) & 0x1) << 31) // flags mode
|
#define C_PRESET(a) ((a) & 0xF)
|
||||||
|
|
||||||
#define T_IQ(a) (((a) >> 4) & 0xF)
|
#define T_IQ(a) (((a) >> 4) & 0xF)
|
||||||
#define T_OC(a) (((a) >> 8) & 0xF)
|
#define T_OC(a) (((a) >> 8) & 0xF)
|
||||||
@ -430,10 +429,10 @@ inline void rgbToRgbX(uchar *target, const uchar *source, qint32 targetSize, qin
|
|||||||
#define T_NR(a) (((a) >> 17) & 0x3)
|
#define T_NR(a) (((a) >> 17) & 0x3)
|
||||||
#define T_FC(a) (((a) >> 19) & 0x1)
|
#define T_FC(a) (((a) >> 19) & 0x1)
|
||||||
#define T_SR(a) (((a) >> 20) & 0x1)
|
#define T_SR(a) (((a) >> 20) & 0x1)
|
||||||
#define T_FLAGS(a) (((a) >> 31) & 0x1)
|
#define T_PRESET(a) ((a) & 0xF)
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
#define DEFAULT_QUALITY (C_IQ(3) | C_OC(1) | C_CW(1) | C_AW(1) | C_BT(1) | C_HS(0) | C_FLAGS(1))
|
#define DEFAULT_QUALITY (C_IQ(3) | C_OC(1) | C_CW(1) | C_AW(1) | C_BT(1) | C_HS(0))
|
||||||
|
|
||||||
void setParams(QImageIOHandler *handler, LibRaw *rawProcessor)
|
void setParams(QImageIOHandler *handler, LibRaw *rawProcessor)
|
||||||
{
|
{
|
||||||
@ -461,45 +460,47 @@ void setParams(QImageIOHandler *handler, LibRaw *rawProcessor)
|
|||||||
if (handler->supportsOption(QImageIOHandler::Quality)) {
|
if (handler->supportsOption(QImageIOHandler::Quality)) {
|
||||||
quality = handler->option(QImageIOHandler::Quality).toInt();
|
quality = handler->option(QImageIOHandler::Quality).toInt();
|
||||||
}
|
}
|
||||||
if (quality > -1) {
|
if (quality < 0) {
|
||||||
switch (quality / 10) {
|
|
||||||
case 0:
|
|
||||||
quality = C_IQ(0) | C_OC(1) | C_CW(1) | C_AW(1) | C_BT(0) | C_HS(1);
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
quality = C_IQ(0) | C_OC(1) | C_CW(1) | C_AW(1) | C_BT(0) | C_HS(0);
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
quality = C_IQ(3) | C_OC(1) | C_CW(1) | C_AW(1) | C_BT(0) | C_HS(0);
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
quality = C_IQ(3) | C_OC(1) | C_CW(1) | C_AW(1) | C_BT(1) | C_HS(0);
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
quality = C_IQ(3) | C_OC(2) | C_CW(1) | C_AW(1) | C_BT(1) | C_HS(0);
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
quality = C_IQ(3) | C_OC(4) | C_CW(1) | C_AW(1) | C_BT(1) | C_HS(0);
|
|
||||||
break;
|
|
||||||
case 6:
|
|
||||||
quality = C_IQ(11) | C_OC(1) | C_CW(1) | C_AW(1) | C_BT(0) | C_HS(0);
|
|
||||||
break;
|
|
||||||
case 7:
|
|
||||||
quality = C_IQ(11) | C_OC(1) | C_CW(1) | C_AW(1) | C_BT(1) | C_HS(0);
|
|
||||||
break;
|
|
||||||
case 8:
|
|
||||||
quality = C_IQ(11) | C_OC(2) | C_CW(1) | C_AW(1) | C_BT(1) | C_HS(0);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
quality = C_IQ(11) | C_OC(4) | C_CW(1) | C_AW(1) | C_BT(1) | C_HS(0);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
quality |= C_FLAGS(1);
|
|
||||||
}
|
|
||||||
if (quality == -1) {
|
|
||||||
quality = DEFAULT_QUALITY;
|
quality = DEFAULT_QUALITY;
|
||||||
}
|
}
|
||||||
Q_ASSERT(T_FLAGS(quality));
|
|
||||||
|
switch (T_PRESET(quality)) {
|
||||||
|
case 0:
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
quality = C_IQ(0) | C_OC(1) | C_CW(1) | C_AW(1) | C_BT(0) | C_HS(1);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
quality = C_IQ(0) | C_OC(1) | C_CW(1) | C_AW(1) | C_BT(0) | C_HS(0);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
quality = C_IQ(3) | C_OC(1) | C_CW(1) | C_AW(1) | C_BT(0) | C_HS(0);
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
quality = C_IQ(3) | C_OC(1) | C_CW(1) | C_AW(1) | C_BT(1) | C_HS(0);
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
quality = C_IQ(3) | C_OC(2) | C_CW(1) | C_AW(1) | C_BT(1) | C_HS(0);
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
quality = C_IQ(3) | C_OC(4) | C_CW(1) | C_AW(1) | C_BT(1) | C_HS(0);
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
quality = C_IQ(11) | C_OC(1) | C_CW(1) | C_AW(1) | C_BT(0) | C_HS(0);
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
quality = C_IQ(11) | C_OC(1) | C_CW(1) | C_AW(1) | C_BT(1) | C_HS(0);
|
||||||
|
break;
|
||||||
|
case 9:
|
||||||
|
quality = C_IQ(11) | C_OC(2) | C_CW(1) | C_AW(1) | C_BT(1) | C_HS(0);
|
||||||
|
break;
|
||||||
|
case 10:
|
||||||
|
quality = C_IQ(11) | C_OC(4) | C_CW(1) | C_AW(1) | C_BT(1) | C_HS(0);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
quality = DEFAULT_QUALITY;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
auto &¶ms = rawProcessor->imgdata.params;
|
auto &¶ms = rawProcessor->imgdata.params;
|
||||||
|
|
||||||
@ -806,12 +807,10 @@ QVariant RAWHandler::option(ImageOption option) const
|
|||||||
rawProcessor->imgdata.rawparams.shot_select = currentImageNumber();
|
rawProcessor->imgdata.rawparams.shot_select = currentImageNumber();
|
||||||
#endif
|
#endif
|
||||||
if (rawProcessor->open_datastream(&stream) == LIBRAW_SUCCESS) {
|
if (rawProcessor->open_datastream(&stream) == LIBRAW_SUCCESS) {
|
||||||
if (rawProcessor->unpack() == LIBRAW_SUCCESS) {
|
auto w = libraw_get_iwidth(&rawProcessor->imgdata);
|
||||||
auto w = libraw_get_iwidth(&rawProcessor->imgdata);
|
auto h = libraw_get_iheight(&rawProcessor->imgdata);
|
||||||
auto h = libraw_get_iheight(&rawProcessor->imgdata);
|
// flip & 4: taken from LibRaw code
|
||||||
// flip & 4: taken from LibRaw code
|
v = (rawProcessor->imgdata.sizes.flip & 4) ? QSize(h, w) : QSize(w, h);
|
||||||
v = (rawProcessor->imgdata.sizes.flip & 4) ? QSize(h, w) : QSize(w, h);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
d->rollbackTransaction();
|
d->rollbackTransaction();
|
||||||
}
|
}
|
||||||
|
7
src/imageformats/raw.desktop
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Type=Service
|
||||||
|
X-KDE-ServiceTypes=QImageIOPlugins
|
||||||
|
X-KDE-ImageFormat=3fr,arw,arq,bay,bmq,crw,cr2,cr3,cap,cine,cs1,dcs,dc2,dcr,dng,drf,dxo,eip,erf,fff,hdr,iiq,k25,kdc,kc2,mdc,mef,mfw,mos,mrw,nef,nrw,obm,orf,ori,pef,ptx,pxn,qtk,r3d,raf,raw,rdc,rwl,rw2,rwz,sr2,srf,srw,sti,x3f
|
||||||
|
X-KDE-MimeType=image/x-hasselblad-3fr,image/x-sony-arw,image/x-arq,image/x-bay,image/x-bmq,image/x-canon-crw,image/x-canon-cr2,image/x-canon-cr3,image/x-cap,image/x-cine,image/x-cs1,image/x-kodak-dcs,image/x-dc2,image/x-kodak-dcr,image/x-adobe-dng,image/x-drf,image/x-dxo,image/x-epson-eip,image/x-epson-erf,image/x-fff,image/x-hdr,image/x-iiq,image/x-kodak-k25,image/x-kodak-kdc,image/x-kodak-kc2,image/x-minolta-mdc,image/x-mamiya-mef,image/x-mfw,image/x-aptus-mos,image/x-minolta-mrw,image/x-nikon-nef,image/x-nikon-nrw,image/x-obm,image/x-olympus-orf,image/x-ori,image/x-pentax-pef,image/x-ptx,image/x-pxn,image/x-qtk,image/x-r3d,image/x-fuji-raf,image/x-raw,image/x-rdc,image/x-rwl,image/x-panasonic-rw2,image/x-rwz,image/x-sony-sr2,image/x-sony-srf,image/x-samsung-srw,image/x-sti,image/x-sigma-x3f
|
||||||
|
X-KDE-Read=true
|
||||||
|
X-KDE-Write=false
|
@ -40,13 +40,30 @@ private:
|
|||||||
* Change the quality of the conversion. If -1, default quality is used.
|
* Change the quality of the conversion. If -1, default quality is used.
|
||||||
* @note Verify that the quality change support has been compiled with supportsOption()
|
* @note Verify that the quality change support has been compiled with supportsOption()
|
||||||
*
|
*
|
||||||
* When the quality value is negative (but not -1), we assume we want to work with flags according to the following scheme:
|
|
||||||
* 3 2 1 0
|
* 3 2 1 0
|
||||||
* 1 0 9 8 7 6 5 4 3 2 1 0 9 87 6 5 4 3 2 1098 7654 3210
|
* 1 0 9 8 7 6 5 4 3 2 1 0 9 87 6 5 4 3 2 1098 7654 3210
|
||||||
* 1 _ _ _ _ _ _ _ _ _ _ S F NN E H B A W CCCC IIII ____
|
* _ _ _ _ _ _ _ _ _ _ _ S F NN E H B A W CCCC IIII PPPP
|
||||||
*
|
*
|
||||||
* Where:
|
* Where:
|
||||||
* _: reserved (should be zero)
|
*
|
||||||
|
* _: reserved
|
||||||
|
* P: preset values: *** if set, other flags are ignored! ***
|
||||||
|
* - 0: Use other flags (no preset)
|
||||||
|
* - 1: I = 0, C = 1, B = 0, W = 1, A = 1, H = 1 (Linear, sRGB, 8-bits, Camera White, Auto White, Half-size)
|
||||||
|
* - 2: I = 0, C = 1, B = 0, W = 1, A = 1, H = 0 (Linear, sRGB, 8-bits, Camera White, Auto White)
|
||||||
|
* - 3: I = 3, C = 1, B = 0, W = 1, A = 1, H = 0 (AHD, sRGB, 8-bits, Camera White, Auto White)
|
||||||
|
* - 4: I = 3, C = 1, B = 1, W = 1, A = 1, H = 0 (AHD, sRGB, 16-bits, Camera White, Auto White)
|
||||||
|
* - 5: I = 3, C = 2, B = 1, W = 1, A = 1, H = 0 (AHD, Adobe, 16-bits, Camera White, Auto White)
|
||||||
|
* - 6: I = 3, C = 4, B = 1, W = 1, A = 1, H = 0 (AHD, ProPhoto, 16-bits, Camera White, Auto White)
|
||||||
|
* - 7: I = 11, C = 1, B = 0, W = 1, A = 1, H = 0 (DHT, sRGB, 8-bits, Camera White, Auto White)
|
||||||
|
* - 8: I = 11, C = 1, B = 1, W = 1, A = 1, H = 0 (DHT, sRGB, 16-bits, Camera White, Auto White)
|
||||||
|
* - 9: I = 11, C = 2, B = 1, W = 1, A = 1, H = 0 (DHT, Adobe, 16-bits, Camera White, Auto White)
|
||||||
|
* - 10: I = 11, C = 4, B = 1, W = 1, A = 1, H = 0 (DHT, ProPhoto, 16-bits, Camera White, Auto White)
|
||||||
|
* - 11: reserved
|
||||||
|
* - 12: reserved
|
||||||
|
* - 13: reserved
|
||||||
|
* - 14: reserved
|
||||||
|
* - 15: reserved
|
||||||
* I: interpolation quality (0 - linear, 1 - VNG, 2 - PPG, 3 - AHD, 4 - DCB, 11 - DHT, 12 - AAHD)
|
* I: interpolation quality (0 - linear, 1 - VNG, 2 - PPG, 3 - AHD, 4 - DCB, 11 - DHT, 12 - AAHD)
|
||||||
* C: output colorspace (0 - raw, 1 - sRGB, 2 - Adobe, 3 - Wide, 4 - ProPhoto, 5 - XYZ, 6 - ACES, 7 - DCI-P3, 8 - Rec2020)
|
* C: output colorspace (0 - raw, 1 - sRGB, 2 - Adobe, 3 - Wide, 4 - ProPhoto, 5 - XYZ, 6 - ACES, 7 - DCI-P3, 8 - Rec2020)
|
||||||
* W: use camera white balace (0 - off, 1 - on)
|
* W: use camera white balace (0 - off, 1 - on)
|
||||||
@ -57,21 +74,8 @@ private:
|
|||||||
* N: FBDD noise reduction (0 - off, 1 - light, 2 - full)
|
* N: FBDD noise reduction (0 - off, 1 - light, 2 - full)
|
||||||
* F: Interpolate RGGB as four colors (0 - off, 1 - on)
|
* F: Interpolate RGGB as four colors (0 - off, 1 - on)
|
||||||
* S: Don't stretch or rotate raw pixels (0 - rotate and stretch, 1 - don't rotate and stretch)
|
* S: Don't stretch or rotate raw pixels (0 - rotate and stretch, 1 - don't rotate and stretch)
|
||||||
|
*
|
||||||
* @note It is safe to set both W and A: W is used if camera white balance is found, otherwise A is used.
|
* @note It is safe to set both W and A: W is used if camera white balance is found, otherwise A is used.
|
||||||
*
|
|
||||||
* When quality is a positive value, a value between 0 and 100 is expected. The values are interpreted as follows:
|
|
||||||
* - 00-09: I = 0, C = 1, B = 0, W = 1, A = 1, H = 1 (Linear, sRGB, 8-bits, Camera White, Auto White, Half-size)
|
|
||||||
* - 10-19: I = 0, C = 1, B = 0, W = 1, A = 1, H = 0 (Linear, sRGB, 8-bits, Camera White, Auto White)
|
|
||||||
* - 20-29: I = 3, C = 1, B = 0, W = 1, A = 1, H = 0 (AHD, sRGB, 8-bits, Camera White, Auto White)
|
|
||||||
* - 30-39: I = 3, C = 1, B = 1, W = 1, A = 1, H = 0 (AHD, sRGB, 16-bits, Camera White, Auto White) [Default]
|
|
||||||
* - 40-49: I = 3, C = 2, B = 1, W = 1, A = 1, H = 0 (AHD, Adobe, 16-bits, Camera White, Auto White)
|
|
||||||
* - 50-59: I = 3, C = 4, B = 1, W = 1, A = 1, H = 0 (AHD, ProPhoto, 16-bits, Camera White, Auto White)
|
|
||||||
* - 60-69: I = 11, C = 1, B = 0, W = 1, A = 1, H = 0 (DHT, sRGB, 8-bits, Camera White, Auto White)
|
|
||||||
* - 70-79: I = 11, C = 1, B = 1, W = 1, A = 1, H = 0 (DHT, sRGB, 16-bits, Camera White, Auto White)
|
|
||||||
* - 80-89: I = 11, C = 2, B = 1, W = 1, A = 1, H = 0 (DHT, Adobe, 16-bits, Camera White, Auto White)
|
|
||||||
* - >= 90: I = 11, C = 4, B = 1, W = 1, A = 1, H = 0 (DHT, ProPhoto, 16-bits, Camera White, Auto White)
|
|
||||||
*
|
|
||||||
* When the quality is -1, default quality is used.
|
|
||||||
*/
|
*/
|
||||||
qint32 m_quality;
|
qint32 m_quality;
|
||||||
|
|
||||||
|
@ -22,13 +22,13 @@
|
|||||||
#include "rgb_p.h"
|
#include "rgb_p.h"
|
||||||
#include "util_p.h"
|
#include "util_p.h"
|
||||||
|
|
||||||
#include <QList>
|
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
|
#include <QVector>
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
|
|
||||||
class RLEData : public QList<uchar>
|
class RLEData : public QVector<uchar>
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
RLEData()
|
RLEData()
|
||||||
@ -61,7 +61,7 @@ public:
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
uint insert(const uchar *d, uint l);
|
uint insert(const uchar *d, uint l);
|
||||||
QList<const RLEData *> vector();
|
QVector<const RLEData *> vector();
|
||||||
void setBaseOffset(uint o)
|
void setBaseOffset(uint o)
|
||||||
{
|
{
|
||||||
_offset = o;
|
_offset = o;
|
||||||
@ -107,7 +107,7 @@ private:
|
|||||||
QByteArray _data;
|
QByteArray _data;
|
||||||
QByteArray::Iterator _pos;
|
QByteArray::Iterator _pos;
|
||||||
RLEMap _rlemap;
|
RLEMap _rlemap;
|
||||||
QList<const RLEData *> _rlevector;
|
QVector<const RLEData *> _rlevector;
|
||||||
uint _numrows;
|
uint _numrows;
|
||||||
|
|
||||||
bool readData(QImage &);
|
bool readData(QImage &);
|
||||||
@ -421,9 +421,9 @@ uint RLEMap::insert(const uchar *d, uint l)
|
|||||||
return QMap<RLEData, uint>::insert(data, _counter++).value();
|
return QMap<RLEData, uint>::insert(data, _counter++).value();
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<const RLEData *> RLEMap::vector()
|
QVector<const RLEData *> RLEMap::vector()
|
||||||
{
|
{
|
||||||
QList<const RLEData *> v(size());
|
QVector<const RLEData *> v(size());
|
||||||
for (Iterator it = begin(); it != end(); ++it) {
|
for (Iterator it = begin(); it != end(); ++it) {
|
||||||
v.replace(it.value(), &it.key());
|
v.replace(it.value(), &it.key());
|
||||||
}
|
}
|
||||||
|
7
src/imageformats/rgb.desktop
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Type=Service
|
||||||
|
X-KDE-ServiceTypes=QImageIOPlugins
|
||||||
|
X-KDE-ImageFormat=rgb,rgba,bw,sgi
|
||||||
|
X-KDE-MimeType=image/x-rgb
|
||||||
|
X-KDE-Read=true
|
||||||
|
X-KDE-Write=true
|
@ -15,15 +15,13 @@ ScanLineConverter::ScanLineConverter(const QImage::Format &targetFormat)
|
|||||||
ScanLineConverter::ScanLineConverter(const ScanLineConverter &other)
|
ScanLineConverter::ScanLineConverter(const ScanLineConverter &other)
|
||||||
: _targetFormat(other._targetFormat)
|
: _targetFormat(other._targetFormat)
|
||||||
, _colorSpace(other._colorSpace)
|
, _colorSpace(other._colorSpace)
|
||||||
, _defaultColorSpace(other._defaultColorSpace)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
ScanLineConverter &ScanLineConverter::operator=(const ScanLineConverter &other)
|
ScanLineConverter &ScanLineConverter::operator=(const ScanLineConverter &other)
|
||||||
{
|
{
|
||||||
_targetFormat = other._targetFormat;
|
this->_targetFormat = other._targetFormat;
|
||||||
_colorSpace = other._colorSpace;
|
this->_colorSpace = other._colorSpace;
|
||||||
_defaultColorSpace = other._defaultColorSpace;
|
|
||||||
return (*this);
|
return (*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,51 +40,24 @@ QColorSpace ScanLineConverter::targetColorSpace() const
|
|||||||
return _colorSpace;
|
return _colorSpace;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScanLineConverter::setDefaultSourceColorSpace(const QColorSpace &colorSpace)
|
|
||||||
{
|
|
||||||
_defaultColorSpace = colorSpace;
|
|
||||||
}
|
|
||||||
|
|
||||||
QColorSpace ScanLineConverter::defaultSourceColorSpace() const
|
|
||||||
{
|
|
||||||
return _defaultColorSpace;
|
|
||||||
}
|
|
||||||
|
|
||||||
const uchar *ScanLineConverter::convertedScanLine(const QImage &image, qint32 y)
|
const uchar *ScanLineConverter::convertedScanLine(const QImage &image, qint32 y)
|
||||||
{
|
{
|
||||||
auto colorSpaceConversion = isColorSpaceConversionNeeded(image);
|
auto colorSpaceConversion = isColorSpaceConversionNeeded(image, _colorSpace);
|
||||||
if (image.format() == _targetFormat && !colorSpaceConversion) {
|
if (image.format() == _targetFormat && !colorSpaceConversion) {
|
||||||
return image.constScanLine(y);
|
return image.constScanLine(y);
|
||||||
}
|
}
|
||||||
if (image.width() != _tmpBuffer.width() || image.format() != _tmpBuffer.format()) {
|
if (image.width() != _tmpBuffer.width() || image.format() != _tmpBuffer.format()) {
|
||||||
_tmpBuffer = QImage(image.width(), 1, image.format());
|
_tmpBuffer = QImage(image.width(), 1, image.format());
|
||||||
_tmpBuffer.setColorTable(image.colorTable());
|
|
||||||
}
|
}
|
||||||
if (_tmpBuffer.isNull()) {
|
if (_tmpBuffer.isNull()) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
std::memcpy(_tmpBuffer.bits(), image.constScanLine(y), std::min(_tmpBuffer.bytesPerLine(), image.bytesPerLine()));
|
std::memcpy(_tmpBuffer.bits(), image.constScanLine(y), std::min(_tmpBuffer.bytesPerLine(), image.bytesPerLine()));
|
||||||
auto tmp = _tmpBuffer;
|
|
||||||
if (colorSpaceConversion) {
|
if (colorSpaceConversion) {
|
||||||
auto cs = image.colorSpace();
|
_tmpBuffer.setColorSpace(image.colorSpace());
|
||||||
if (!cs.isValid()) {
|
_tmpBuffer.convertToColorSpace(_colorSpace);
|
||||||
cs = _defaultColorSpace;
|
|
||||||
}
|
|
||||||
if (tmp.depth() < 24) {
|
|
||||||
tmp.convertTo(tmp.hasAlphaChannel() ? QImage::Format_ARGB32 : QImage::Format_RGB32);
|
|
||||||
}
|
|
||||||
tmp.setColorSpace(cs);
|
|
||||||
tmp.convertToColorSpace(_colorSpace);
|
|
||||||
}
|
}
|
||||||
|
_convBuffer = _tmpBuffer.convertToFormat(_targetFormat);
|
||||||
/*
|
|
||||||
* Work Around for wrong RGBA64 -> 16FPx4/32FPx4 conversion on Intel architecture.
|
|
||||||
* Luckily convertTo() works fine with 16FPx4 images so I can use it instead convertToFormat().
|
|
||||||
* See also: https://bugreports.qt.io/browse/QTBUG-120614
|
|
||||||
*/
|
|
||||||
tmp.convertTo(_targetFormat);
|
|
||||||
_convBuffer = tmp;
|
|
||||||
|
|
||||||
if (_convBuffer.isNull()) {
|
if (_convBuffer.isNull()) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@ -101,12 +72,12 @@ qsizetype ScanLineConverter::bytesPerLine() const
|
|||||||
return _convBuffer.bytesPerLine();
|
return _convBuffer.bytesPerLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ScanLineConverter::isColorSpaceConversionNeeded(const QImage &image, const QColorSpace &targetColorSpace, const QColorSpace &defaultColorSpace)
|
bool ScanLineConverter::isColorSpaceConversionNeeded(const QImage &image, const QColorSpace &targetColorSpace) const
|
||||||
{
|
{
|
||||||
auto sourceColorSpace = image.colorSpace();
|
if (image.depth() < 24) { // RGB 8 bit or grater only
|
||||||
if (!sourceColorSpace.isValid()) {
|
return false;
|
||||||
sourceColorSpace = defaultColorSpace;
|
|
||||||
}
|
}
|
||||||
|
auto sourceColorSpace = image.colorSpace();
|
||||||
if (!sourceColorSpace.isValid() || !targetColorSpace.isValid()) {
|
if (!sourceColorSpace.isValid() || !targetColorSpace.isValid()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -42,14 +42,6 @@ public:
|
|||||||
void setTargetColorSpace(const QColorSpace &colorSpace);
|
void setTargetColorSpace(const QColorSpace &colorSpace);
|
||||||
QColorSpace targetColorSpace() const;
|
QColorSpace targetColorSpace() const;
|
||||||
|
|
||||||
/*!
|
|
||||||
* \brief setDefaultSourceColorSpace
|
|
||||||
* Set the source color space to use when the image does not have a color space.
|
|
||||||
* \param colorSpace
|
|
||||||
*/
|
|
||||||
void setDefaultSourceColorSpace(const QColorSpace &colorSpace);
|
|
||||||
QColorSpace defaultSourceColorSpace() const;
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief convertedScanLine
|
* \brief convertedScanLine
|
||||||
* Convert the scanline \a y.
|
* Convert the scanline \a y.
|
||||||
@ -70,24 +62,14 @@ public:
|
|||||||
* \note Only 24 bit or grater images.
|
* \note Only 24 bit or grater images.
|
||||||
* \param image The source image.
|
* \param image The source image.
|
||||||
* \param targetColorSpace The target color space.
|
* \param targetColorSpace The target color space.
|
||||||
* \param defaultColorSpace The default color space to use it image does not contains one.
|
|
||||||
* \return True if the conversion should be done otherwise false.
|
* \return True if the conversion should be done otherwise false.
|
||||||
*/
|
*/
|
||||||
static bool isColorSpaceConversionNeeded(const QImage &image, const QColorSpace &targetColorSpace, const QColorSpace &defaultColorSpace = QColorSpace());
|
bool isColorSpaceConversionNeeded(const QImage &image, const QColorSpace &targetColorSpace) const;
|
||||||
|
|
||||||
/*!
|
|
||||||
* \brief isColorSpaceConversionNeeded
|
|
||||||
*/
|
|
||||||
inline bool isColorSpaceConversionNeeded(const QImage &image) const
|
|
||||||
{
|
|
||||||
return isColorSpaceConversionNeeded(image, _colorSpace, _defaultColorSpace);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// data
|
// data
|
||||||
QImage::Format _targetFormat;
|
QImage::Format _targetFormat;
|
||||||
QColorSpace _colorSpace;
|
QColorSpace _colorSpace;
|
||||||
QColorSpace _defaultColorSpace;
|
|
||||||
|
|
||||||
// internal buffers
|
// internal buffers
|
||||||
QImage _tmpBuffer;
|
QImage _tmpBuffer;
|
||||||
|
@ -88,10 +88,6 @@ static QDataStream &operator>>(QDataStream &s, TgaHeader &head)
|
|||||||
s >> head.height;
|
s >> head.height;
|
||||||
s >> head.pixel_size;
|
s >> head.pixel_size;
|
||||||
s >> head.flags;
|
s >> head.flags;
|
||||||
/*qDebug() << "id_length: " << head.id_length << " - colormap_type: " << head.colormap_type << " - image_type: " << head.image_type;
|
|
||||||
qDebug() << "colormap_index: " << head.colormap_index << " - colormap_length: " << head.colormap_length << " - colormap_size: " << head.colormap_size;
|
|
||||||
qDebug() << "x_origin: " << head.x_origin << " - y_origin: " << head.y_origin << " - width:" << head.width << " - height:" << head.height << " - pixelsize:
|
|
||||||
" << head.pixel_size << " - flags: " << head.flags;*/
|
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,6 +113,10 @@ static bool IsSupported(const TgaHeader &head)
|
|||||||
if (head.pixel_size != 8 && head.pixel_size != 16 && head.pixel_size != 24 && head.pixel_size != 32) {
|
if (head.pixel_size != 8 && head.pixel_size != 16 && head.pixel_size != 24 && head.pixel_size != 32) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
// If the colormap_type field is set to zero, indicating that no color map exists, then colormap_size, colormap_index and colormap_length should be set to zero.
|
||||||
|
if (head.colormap_type == 0 && (head.colormap_size != 0 || head.colormap_index != 0 || head.colormap_length != 0)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,10 +170,57 @@ struct TgaHeaderInfo {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static QImage::Format imageFormat(const TgaHeader &head)
|
||||||
|
{
|
||||||
|
auto format = QImage::Format_Invalid;
|
||||||
|
if (IsSupported(head)) {
|
||||||
|
// Bits 0-3 are the numbers of alpha bits (can be zero!)
|
||||||
|
const int numAlphaBits = head.flags & 0xf;
|
||||||
|
// However alpha exists only in the 32 bit format.
|
||||||
|
if ((head.pixel_size == 32) && (head.flags & 0xf)) {
|
||||||
|
if (numAlphaBits <= 8) {
|
||||||
|
format = QImage::Format_ARGB32;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
format = QImage::Format_RGB32;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return format;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief peekHeader
|
||||||
|
* Reads the header but does not change the position in the device.
|
||||||
|
*/
|
||||||
|
static bool peekHeader(QIODevice *device, TgaHeader &header)
|
||||||
|
{
|
||||||
|
qint64 oldPos = device->pos();
|
||||||
|
QByteArray head = device->read(TgaHeader::SIZE);
|
||||||
|
int readBytes = head.size();
|
||||||
|
|
||||||
|
if (device->isSequential()) {
|
||||||
|
for (int pos = readBytes - 1; pos >= 0; --pos) {
|
||||||
|
device->ungetChar(head[pos]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
device->seek(oldPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (readBytes < TgaHeader::SIZE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QDataStream stream(head);
|
||||||
|
stream.setByteOrder(QDataStream::LittleEndian);
|
||||||
|
stream >> header;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static bool LoadTGA(QDataStream &s, const TgaHeader &tga, QImage &img)
|
static bool LoadTGA(QDataStream &s, const TgaHeader &tga, QImage &img)
|
||||||
{
|
{
|
||||||
// Create image.
|
img = imageAlloc(tga.width, tga.height, imageFormat(tga));
|
||||||
img = imageAlloc(tga.width, tga.height, QImage::Format_RGB32);
|
|
||||||
if (img.isNull()) {
|
if (img.isNull()) {
|
||||||
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(tga.width, tga.height);
|
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(tga.width, tga.height);
|
||||||
return false;
|
return false;
|
||||||
@ -181,21 +228,7 @@ static bool LoadTGA(QDataStream &s, const TgaHeader &tga, QImage &img)
|
|||||||
|
|
||||||
TgaHeaderInfo info(tga);
|
TgaHeaderInfo info(tga);
|
||||||
|
|
||||||
// Bits 0-3 are the numbers of alpha bits (can be zero!)
|
|
||||||
const int numAlphaBits = tga.flags & 0xf;
|
const int numAlphaBits = tga.flags & 0xf;
|
||||||
// However alpha exists only in the 32 bit format.
|
|
||||||
if ((tga.pixel_size == 32) && (tga.flags & 0xf)) {
|
|
||||||
img = imageAlloc(tga.width, tga.height, QImage::Format_ARGB32);
|
|
||||||
if (img.isNull()) {
|
|
||||||
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(tga.width, tga.height);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (numAlphaBits > 8) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uint pixel_size = (tga.pixel_size / 8);
|
uint pixel_size = (tga.pixel_size / 8);
|
||||||
qint64 size = qint64(tga.width) * qint64(tga.height) * pixel_size;
|
qint64 size = qint64(tga.width) * qint64(tga.height) * pixel_size;
|
||||||
|
|
||||||
@ -391,23 +424,25 @@ bool TGAHandler::read(QImage *outImage)
|
|||||||
{
|
{
|
||||||
// qDebug() << "Loading TGA file!";
|
// qDebug() << "Loading TGA file!";
|
||||||
|
|
||||||
QDataStream s(device());
|
auto d = device();
|
||||||
s.setByteOrder(QDataStream::LittleEndian);
|
|
||||||
|
|
||||||
// Read image header.
|
|
||||||
TgaHeader tga;
|
TgaHeader tga;
|
||||||
s >> tga;
|
if (!peekHeader(d, tga) || !IsSupported(tga)) {
|
||||||
s.device()->seek(TgaHeader::SIZE + tga.id_length);
|
|
||||||
|
|
||||||
// Check image file format.
|
|
||||||
if (s.atEnd()) {
|
|
||||||
// qDebug() << "This TGA file is not valid.";
|
// qDebug() << "This TGA file is not valid.";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check supported file types.
|
if (d->isSequential()) {
|
||||||
if (!IsSupported(tga)) {
|
d->read(TgaHeader::SIZE + tga.id_length);
|
||||||
// qDebug() << "This TGA file is not supported.";
|
} else {
|
||||||
|
d->seek(TgaHeader::SIZE + tga.id_length);
|
||||||
|
}
|
||||||
|
|
||||||
|
QDataStream s(d);
|
||||||
|
s.setByteOrder(QDataStream::LittleEndian);
|
||||||
|
|
||||||
|
// Check image file format.
|
||||||
|
if (s.atEnd()) {
|
||||||
|
// qDebug() << "This TGA file is not valid.";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -468,6 +503,42 @@ bool TGAHandler::write(const QImage &image)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool TGAHandler::supportsOption(ImageOption option) const
|
||||||
|
{
|
||||||
|
if (option == QImageIOHandler::Size) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (option == QImageIOHandler::ImageFormat) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant TGAHandler::option(ImageOption option) const
|
||||||
|
{
|
||||||
|
QVariant v;
|
||||||
|
|
||||||
|
if (option == QImageIOHandler::Size) {
|
||||||
|
if (auto d = device()) {
|
||||||
|
TgaHeader header;
|
||||||
|
if (peekHeader(d, header) && IsSupported(header)) {
|
||||||
|
v = QVariant::fromValue(QSize(header.width, header.height));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (option == QImageIOHandler::ImageFormat) {
|
||||||
|
if (auto d = device()) {
|
||||||
|
TgaHeader header;
|
||||||
|
if (peekHeader(d, header) && IsSupported(header)) {
|
||||||
|
v = QVariant::fromValue(imageFormat(header));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
bool TGAHandler::canRead(QIODevice *device)
|
bool TGAHandler::canRead(QIODevice *device)
|
||||||
{
|
{
|
||||||
if (!device) {
|
if (!device) {
|
||||||
@ -491,10 +562,12 @@ bool TGAHandler::canRead(QIODevice *device)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
QDataStream stream(head);
|
|
||||||
stream.setByteOrder(QDataStream::LittleEndian);
|
|
||||||
TgaHeader tga;
|
TgaHeader tga;
|
||||||
stream >> tga;
|
if (!peekHeader(device, tga)) {
|
||||||
|
qWarning("TGAHandler::canRead() error while reading the header");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return IsSupported(tga);
|
return IsSupported(tga);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
7
src/imageformats/tga.desktop
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Type=Service
|
||||||
|
X-KDE-ServiceTypes=QImageIOPlugins
|
||||||
|
X-KDE-ImageFormat=tga
|
||||||
|
X-KDE-MimeType=image/x-tga
|
||||||
|
X-KDE-Read=true
|
||||||
|
X-KDE-Write=true
|
@ -19,6 +19,9 @@ public:
|
|||||||
bool read(QImage *image) override;
|
bool read(QImage *image) override;
|
||||||
bool write(const QImage &image) override;
|
bool write(const QImage &image) override;
|
||||||
|
|
||||||
|
bool supportsOption(QImageIOHandler::ImageOption option) const override;
|
||||||
|
QVariant option(QImageIOHandler::ImageOption option) const override;
|
||||||
|
|
||||||
static bool canRead(QIODevice *device);
|
static bool canRead(QIODevice *device);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -11,9 +11,12 @@
|
|||||||
#include <limits>
|
#include <limits>
|
||||||
|
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
#include <QImageIOHandler>
|
|
||||||
|
|
||||||
// QList uses some extra space for stuff, hence the 32 here suggested by Thiago Macieira
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||||
|
#include <QImageIOHandler>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// QVector uses some extra space for stuff, hence the 32 here suggested by Thiago Macieira
|
||||||
static constexpr int kMaxQVectorSize = std::numeric_limits<int>::max() - 32;
|
static constexpr int kMaxQVectorSize = std::numeric_limits<int>::max() - 32;
|
||||||
|
|
||||||
// On Qt 6 to make the plugins fail to allocate if the image size is greater than QImageReader::allocationLimit()
|
// On Qt 6 to make the plugins fail to allocate if the image size is greater than QImageReader::allocationLimit()
|
||||||
@ -21,9 +24,13 @@ static constexpr int kMaxQVectorSize = std::numeric_limits<int>::max() - 32;
|
|||||||
inline QImage imageAlloc(const QSize &size, const QImage::Format &format)
|
inline QImage imageAlloc(const QSize &size, const QImage::Format &format)
|
||||||
{
|
{
|
||||||
QImage img;
|
QImage img;
|
||||||
|
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||||
|
img = QImage(size, format);
|
||||||
|
#else
|
||||||
if (!QImageIOHandler::allocateImage(size, format, &img)) {
|
if (!QImageIOHandler::allocateImage(size, format, &img)) {
|
||||||
img = QImage(); // paranoia
|
img = QImage(); // paranoia
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
return img;
|
return img;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,12 +14,18 @@
|
|||||||
#include <QIODevice>
|
#include <QIODevice>
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
#include <QImageReader>
|
#include <QImageReader>
|
||||||
#include <QList>
|
|
||||||
#include <QLoggingCategory>
|
#include <QLoggingCategory>
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
#include <QStack>
|
#include <QStack>
|
||||||
|
#include <QVector>
|
||||||
#include <QtEndian>
|
#include <QtEndian>
|
||||||
|
|
||||||
|
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||||
|
// To test the plugin with OSS FUZZ (in 2023) you need to make it compatible with Qt 5.
|
||||||
|
// The plugin has been made compatible with all Qt (5 and 6) versions supported by the KDE project.
|
||||||
|
#define XCF_QT5_SUPPORT
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifndef XCF_QT5_SUPPORT
|
#ifndef XCF_QT5_SUPPORT
|
||||||
// Float images are not supported by Qt 5 and can be disabled in QT 6 to reduce memory usage.
|
// Float images are not supported by Qt 5 and can be disabled in QT 6 to reduce memory usage.
|
||||||
// Unfortunately enabling/disabling this define results in slightly different images, so leave the default if possible.
|
// Unfortunately enabling/disabling this define results in slightly different images, so leave the default if possible.
|
||||||
@ -110,7 +116,7 @@ struct RandomTable {
|
|||||||
* parallel processing on a tile-by-tile basis. Here, though,
|
* parallel processing on a tile-by-tile basis. Here, though,
|
||||||
* we just read them in en-masse and store them in a matrix.
|
* we just read them in en-masse and store them in a matrix.
|
||||||
*/
|
*/
|
||||||
typedef QList<QList<QImage>> Tiles;
|
typedef QVector<QVector<QImage>> Tiles;
|
||||||
|
|
||||||
class XCFImageFormat
|
class XCFImageFormat
|
||||||
{
|
{
|
||||||
@ -492,7 +498,7 @@ public:
|
|||||||
qint32 tattoo; //!< (unique identifier?)
|
qint32 tattoo; //!< (unique identifier?)
|
||||||
quint32 unit; //!< Units of The GIMP (inch, mm, pica, etc...)
|
quint32 unit; //!< Units of The GIMP (inch, mm, pica, etc...)
|
||||||
qint32 num_colors = 0; //!< number of colors in an indexed image
|
qint32 num_colors = 0; //!< number of colors in an indexed image
|
||||||
QList<QRgb> palette; //!< indexed image color palette
|
QVector<QRgb> palette; //!< indexed image color palette
|
||||||
|
|
||||||
int num_layers; //!< number of layers
|
int num_layers; //!< number of layers
|
||||||
Layer layer; //!< most recently read layer
|
Layer layer; //!< most recently read layer
|
||||||
@ -545,7 +551,7 @@ private:
|
|||||||
//! This table is used as a shared grayscale ramp to be set on grayscale
|
//! This table is used as a shared grayscale ramp to be set on grayscale
|
||||||
//! images. This is because Qt does not differentiate between indexed and
|
//! images. This is because Qt does not differentiate between indexed and
|
||||||
//! grayscale images.
|
//! grayscale images.
|
||||||
static QList<QRgb> grayTable;
|
static QVector<QRgb> grayTable;
|
||||||
|
|
||||||
//! This table provides the add_pixel saturation values (i.e. 250 + 250 = 255).
|
//! This table provides the add_pixel saturation values (i.e. 250 + 250 = 255).
|
||||||
// static int add_lut[256][256]; - this is so lame waste of 256k of memory
|
// static int add_lut[256][256]; - this is so lame waste of 256k of memory
|
||||||
@ -646,7 +652,7 @@ bool XCFImageFormat::random_table_initialized;
|
|||||||
|
|
||||||
constexpr RandomTable XCFImageFormat::randomTable;
|
constexpr RandomTable XCFImageFormat::randomTable;
|
||||||
|
|
||||||
QList<QRgb> XCFImageFormat::grayTable;
|
QVector<QRgb> XCFImageFormat::grayTable;
|
||||||
|
|
||||||
bool XCFImageFormat::modeAffectsSourceAlpha(const quint32 type)
|
bool XCFImageFormat::modeAffectsSourceAlpha(const quint32 type)
|
||||||
{
|
{
|
||||||
@ -960,11 +966,7 @@ bool XCFImageFormat::loadImageProperties(QDataStream &xcf_io, XCFImage &xcf_imag
|
|||||||
case PROP_PARASITES:
|
case PROP_PARASITES:
|
||||||
while (!property.atEnd()) {
|
while (!property.atEnd()) {
|
||||||
char *tag;
|
char *tag;
|
||||||
#if QT_VERSION < QT_VERSION_CHECK(6, 7, 0)
|
|
||||||
quint32 size;
|
quint32 size;
|
||||||
#else
|
|
||||||
qsizetype size;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
property.readBytes(tag, size);
|
property.readBytes(tag, size);
|
||||||
|
|
||||||
@ -997,7 +999,7 @@ bool XCFImageFormat::loadImageProperties(QDataStream &xcf_io, XCFImage &xcf_imag
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
xcf_image.palette = QList<QRgb>();
|
xcf_image.palette = QVector<QRgb>();
|
||||||
xcf_image.palette.reserve(xcf_image.num_colors);
|
xcf_image.palette.reserve(xcf_image.num_colors);
|
||||||
|
|
||||||
for (int i = 0; i < xcf_image.num_colors; i++) {
|
for (int i = 0; i < xcf_image.num_colors; i++) {
|
||||||
@ -1091,7 +1093,9 @@ bool XCFImageFormat::loadProperty(QDataStream &xcf_io, PropType &type, QByteArra
|
|||||||
size = 0;
|
size = 0;
|
||||||
} else {
|
} else {
|
||||||
xcf_io >> size;
|
xcf_io >> size;
|
||||||
if (size > 256000) {
|
if (size > 256000 * 4) {
|
||||||
|
// NOTE: I didn't find any reference to maximum property dimensions in the specs, so I assume it's just a sanity check.
|
||||||
|
qCDebug(XCFPLUGIN) << "XCF: loadProperty skips" << type << "due to size being too large";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
data = new char[size];
|
data = new char[size];
|
||||||
@ -1670,8 +1674,12 @@ bool XCFImageFormat::assignImageBytes(Layer &layer, uint i, uint j, const GimpPr
|
|||||||
for (int y = 0; y < height; y++) {
|
for (int y = 0; y < height; y++) {
|
||||||
uchar *dataPtr = bits + y * bytesPerLine;
|
uchar *dataPtr = bits + y * bytesPerLine;
|
||||||
uchar *alphaPtr = nullptr;
|
uchar *alphaPtr = nullptr;
|
||||||
if (!layer.alpha_tiles.isEmpty())
|
if (layer.alpha_tiles.size() > j && layer.alpha_tiles.at(j).size() > i) {
|
||||||
alphaPtr = layer.alpha_tiles[j][i].scanLine(y);
|
QImage &alphaTile = layer.alpha_tiles[j][i];
|
||||||
|
if (alphaTile.width() >= width && alphaTile.height() > y) {
|
||||||
|
alphaPtr = alphaTile.scanLine(y);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (bpc == 4) {
|
if (bpc == 4) {
|
||||||
#ifdef USE_FLOAT_IMAGES
|
#ifdef USE_FLOAT_IMAGES
|
||||||
if (precision < GimpPrecision::GIMP_PRECISION_HALF_LINEAR) {
|
if (precision < GimpPrecision::GIMP_PRECISION_HALF_LINEAR) {
|
||||||
@ -1968,6 +1976,12 @@ static bool convertFloatTo16Bit(uchar *output, quint64 outputSize, uchar *input)
|
|||||||
*/
|
*/
|
||||||
bool XCFImageFormat::loadLevel(QDataStream &xcf_io, Layer &layer, qint32 bpp, const GimpPrecision precision)
|
bool XCFImageFormat::loadLevel(QDataStream &xcf_io, Layer &layer, qint32 bpp, const GimpPrecision precision)
|
||||||
{
|
{
|
||||||
|
auto bpc = bytesPerChannel(precision);
|
||||||
|
if ((bpc == 0) || (bpp % bpc)) {
|
||||||
|
qCDebug(XCFPLUGIN) << "XCF: the stream seems corrupted";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
qint32 width;
|
qint32 width;
|
||||||
qint32 height;
|
qint32 height;
|
||||||
|
|
||||||
@ -2017,7 +2031,7 @@ bool XCFImageFormat::loadLevel(QDataStream &xcf_io, Layer &layer, qint32 bpp, co
|
|||||||
|
|
||||||
const uint blockSize = TILE_WIDTH * TILE_HEIGHT * bpp * 1.5;
|
const uint blockSize = TILE_WIDTH * TILE_HEIGHT * bpp * 1.5;
|
||||||
|
|
||||||
QList<uchar> buffer;
|
QVector<uchar> buffer;
|
||||||
if (needConvert) {
|
if (needConvert) {
|
||||||
buffer.resize(blockSize * (bpp == 2 ? 2 : 1));
|
buffer.resize(blockSize * (bpp == 2 ? 2 : 1));
|
||||||
}
|
}
|
||||||
@ -2753,10 +2767,10 @@ void XCFImageFormat::copyLayerToImage(XCFImage &xcf_image)
|
|||||||
// For each tile...
|
// For each tile...
|
||||||
|
|
||||||
for (uint j = 0; j < layer.nrows; j++) {
|
for (uint j = 0; j < layer.nrows; j++) {
|
||||||
uint y = j * TILE_HEIGHT;
|
qint32 y = qint32(j * TILE_HEIGHT);
|
||||||
|
|
||||||
for (uint i = 0; i < layer.ncols; i++) {
|
for (uint i = 0; i < layer.ncols; i++) {
|
||||||
uint x = i * TILE_WIDTH;
|
qint32 x = qint32(i * TILE_WIDTH);
|
||||||
|
|
||||||
// This seems the best place to apply the dissolve because it
|
// This seems the best place to apply the dissolve because it
|
||||||
// depends on the global position of each tile's
|
// depends on the global position of each tile's
|
||||||
@ -3043,7 +3057,7 @@ void XCFImageFormat::mergeLayerIntoImage(XCFImage &xcf_image)
|
|||||||
merge = mergeRGBToRGB;
|
merge = mergeRGBToRGB;
|
||||||
break;
|
break;
|
||||||
case GRAY_GIMAGE:
|
case GRAY_GIMAGE:
|
||||||
if (layer.opacity == OPAQUE_OPACITY) {
|
if (layer.opacity == OPAQUE_OPACITY && xcf_image.image.depth() <= 8) {
|
||||||
merge = mergeGrayToGray;
|
merge = mergeGrayToGray;
|
||||||
} else {
|
} else {
|
||||||
merge = mergeGrayToRGB;
|
merge = mergeGrayToRGB;
|
||||||
@ -3179,10 +3193,10 @@ void XCFImageFormat::mergeLayerIntoImage(XCFImage &xcf_image)
|
|||||||
qCDebug(XCFPLUGIN) << "Using QPainter for mode" << layer.mode;
|
qCDebug(XCFPLUGIN) << "Using QPainter for mode" << layer.mode;
|
||||||
|
|
||||||
for (uint j = 0; j < layer.nrows; j++) {
|
for (uint j = 0; j < layer.nrows; j++) {
|
||||||
uint y = j * TILE_HEIGHT;
|
qint32 y = qint32(j * TILE_HEIGHT);
|
||||||
|
|
||||||
for (uint i = 0; i < layer.ncols; i++) {
|
for (uint i = 0; i < layer.ncols; i++) {
|
||||||
uint x = i * TILE_WIDTH;
|
qint32 x = qint32(i * TILE_WIDTH);
|
||||||
|
|
||||||
QImage &tile = layer.image_tiles[j][i];
|
QImage &tile = layer.image_tiles[j][i];
|
||||||
if (x + layer.x_offset < MAX_IMAGE_WIDTH &&
|
if (x + layer.x_offset < MAX_IMAGE_WIDTH &&
|
||||||
@ -3208,10 +3222,10 @@ void XCFImageFormat::mergeLayerIntoImage(XCFImage &xcf_image)
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
for (uint j = 0; j < layer.nrows; j++) {
|
for (uint j = 0; j < layer.nrows; j++) {
|
||||||
uint y = j * TILE_HEIGHT;
|
qint32 y = qint32(j * TILE_HEIGHT);
|
||||||
|
|
||||||
for (uint i = 0; i < layer.ncols; i++) {
|
for (uint i = 0; i < layer.ncols; i++) {
|
||||||
uint x = i * TILE_WIDTH;
|
qint32 x = qint32(i * TILE_WIDTH);
|
||||||
|
|
||||||
// This seems the best place to apply the dissolve because it
|
// This seems the best place to apply the dissolve because it
|
||||||
// depends on the global position of each tile's
|
// depends on the global position of each tile's
|
||||||
@ -3851,6 +3865,9 @@ bool XCFImageFormat::mergeGrayAToRGB(const Layer &layer, uint i, uint j, int k,
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (layer.mode) {
|
switch (layer.mode) {
|
||||||
|
case GIMP_LAYER_MODE_NORMAL:
|
||||||
|
case GIMP_LAYER_MODE_NORMAL_LEGACY:
|
||||||
|
break;
|
||||||
case GIMP_LAYER_MODE_MULTIPLY:
|
case GIMP_LAYER_MODE_MULTIPLY:
|
||||||
case GIMP_LAYER_MODE_MULTIPLY_LEGACY: {
|
case GIMP_LAYER_MODE_MULTIPLY_LEGACY: {
|
||||||
src = INT_MULT(src, dst);
|
src = INT_MULT(src, dst);
|
||||||
@ -4144,7 +4161,9 @@ bool XCFHandler::canRead() const
|
|||||||
bool XCFHandler::read(QImage *image)
|
bool XCFHandler::read(QImage *image)
|
||||||
{
|
{
|
||||||
XCFImageFormat xcfif;
|
XCFImageFormat xcfif;
|
||||||
return xcfif.readXCF(device(), image);
|
auto ok = xcfif.readXCF(device(), image);
|
||||||
|
m_imageSize = image->size();
|
||||||
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool XCFHandler::write(const QImage &)
|
bool XCFHandler::write(const QImage &)
|
||||||
@ -4164,6 +4183,9 @@ QVariant XCFHandler::option(ImageOption option) const
|
|||||||
QVariant v;
|
QVariant v;
|
||||||
|
|
||||||
if (option == QImageIOHandler::Size) {
|
if (option == QImageIOHandler::Size) {
|
||||||
|
if (!m_imageSize.isEmpty()) {
|
||||||
|
return m_imageSize;
|
||||||
|
}
|
||||||
/*
|
/*
|
||||||
* The image structure always starts at offset 0 in the XCF file.
|
* The image structure always starts at offset 0 in the XCF file.
|
||||||
* byte[9] "gimp xcf " File type identification
|
* byte[9] "gimp xcf " File type identification
|
||||||
@ -4176,7 +4198,7 @@ QVariant XCFHandler::option(ImageOption option) const
|
|||||||
* uint32 width Width of canvas
|
* uint32 width Width of canvas
|
||||||
* uint32 height Height of canvas
|
* uint32 height Height of canvas
|
||||||
*/
|
*/
|
||||||
if (auto d = device()) {
|
else if (auto d = device()) {
|
||||||
// transactions works on both random and sequential devices
|
// transactions works on both random and sequential devices
|
||||||
d->startTransaction();
|
d->startTransaction();
|
||||||
auto ba9 = d->read(9); // "gimp xcf "
|
auto ba9 = d->read(9); // "gimp xcf "
|
||||||
|
7
src/imageformats/xcf.desktop
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Type=Service
|
||||||
|
X-KDE-ServiceTypes=QImageIOPlugins
|
||||||
|
X-KDE-ImageFormat=xcf
|
||||||
|
X-KDE-MimeType=image/x-xcf
|
||||||
|
X-KDE-Read=true
|
||||||
|
X-KDE-Write=false
|
@ -24,6 +24,13 @@ public:
|
|||||||
QVariant option(QImageIOHandler::ImageOption option) const override;
|
QVariant option(QImageIOHandler::ImageOption option) const override;
|
||||||
|
|
||||||
static bool canRead(QIODevice *device);
|
static bool canRead(QIODevice *device);
|
||||||
|
|
||||||
|
private:
|
||||||
|
/*!
|
||||||
|
* \brief m_imageSize
|
||||||
|
* Image size cache used by option()
|
||||||
|
*/
|
||||||
|
QSize m_imageSize;
|
||||||
};
|
};
|
||||||
|
|
||||||
class XCFPlugin : public QImageIOPlugin
|
class XCFPlugin : public QImageIOPlugin
|
||||||
|
@ -5,7 +5,7 @@ include(ECMMarkAsTest)
|
|||||||
macro(kimageformats_executable_tests)
|
macro(kimageformats_executable_tests)
|
||||||
foreach(_testname ${ARGN})
|
foreach(_testname ${ARGN})
|
||||||
add_executable(${_testname} ${_testname}.cpp)
|
add_executable(${_testname} ${_testname}.cpp)
|
||||||
target_link_libraries(${_testname} Qt6::Gui)
|
target_link_libraries(${_testname} Qt${QT_MAJOR_VERSION}::Gui)
|
||||||
ecm_mark_as_test(${_testname})
|
ecm_mark_as_test(${_testname})
|
||||||
endforeach(_testname)
|
endforeach(_testname)
|
||||||
endmacro()
|
endmacro()
|
||||||
|