Compare commits

..

54 Commits

Author SHA1 Message Date
106279d32e GIT_SILENT Upgrade ECM and KF version requirements for 5.116.0 release. 2024-05-04 11:40:17 +00:00
f5962442ca TGA: added options support
Code aligned with KF6 (MR !210) to mitigate CCBUG: 413801 and CCBUG: 479612

- Added Size and Format options support
- Fixed a double image allocation when reading RGBA images (RGB was always allocated and then replaced by RGBA one)
- Fixed the code for sequential devices
2024-03-14 21:46:04 +00:00
7cc4cb8d0c More header checks (CCBUG: 479612)
(cherry picked from commit 0710bc65f6)
2024-02-29 23:59:57 +01:00
2bf3a859fc GIT_SILENT Upgrade ECM and KF version requirements for 5.115.0 release. 2024-02-03 14:24:22 +00:00
3ce3ab2364 GIT_SILENT Upgrade ECM and KF version requirements for 5.114.0 release. 2024-01-06 08:53:06 +00:00
c09b88b7c6 avif: new quality settings
Backported from master
2023-12-14 15:57:50 +01:00
8cc25c39b4 Update CI template 2023-12-07 13:48:53 +01:00
43c80793ac HEIF plug-in extended to support HEJ2 format
HEJ2 is JPEG 2000 codec encapsulated in HEIF container.
Only HEJ2 reading is implemented.
2023-12-07 12:58:14 +01:00
e90bca4924 GIT_SILENT Upgrade ECM and KF version requirements for 5.113.0 release. 2023-12-02 09:26:53 +00:00
bfc73ca260 GIT_SILENT Upgrade ECM and KF version requirements for 5.112.0 release. 2023-11-04 10:03:03 +00:00
f6bb59228e GIT_SILENT Upgrade ECM and KF version requirements for 5.111.0 release. 2023-10-07 10:20:46 +00:00
ee381958b2 avif: support repetition count
and minor performance optimizations.
2023-10-06 12:52:48 +02:00
478e49b8a6 raw: fix multi image load
Fixes not loading a second image in the file. This patch allow code like the following.

QImageReader r(file);
do {
    auto qi = r.read();
    if (!qi.isNull()) {
        qi.save(QString("/tmp/%1_%2.tif")
                .arg(QFileInfo(file).baseName())
                .arg(r.currentImageNumber()));
    }
}
while (r.jumpToNextImage());

m_startPos is used to reposition the device if you decide to do a subsequent read: libraw wants it to be at the beginning of the RAW stream

(cherry picked from commit 18ea0492bc)
2023-09-25 22:56:39 +02:00
c3daf86079 hdr: fix oss-fuzz issue 62197
Fixes the following error:

| /src/kimageformats/src/imageformats/hdr.cpp:56:31: runtime error: shift exponent 32 is too large for 32-bit type 'int' |
|------------------------------------------------------------------------------------------------------------------------|
2023-09-11 09:07:53 +00:00
a981cefdd2 hdr: fix crash (oss-fuzz)
This patch Fixes crash when RLE data is corrupted (test cases attached).

Should also fixes (no test cases available):
- Issue 62044 in oss-fuzz: kimageformats:kimgio_hdr_fuzzer: Undefined-shift in RGBE_To_QRgbLine
- Issue 62057 in oss-fuzz: kimageformats:kimgio_hdr_fuzzer: Heap-buffer-overflow in Read_Old_Line


[crash-646a4364479f54278ff8c30c69b0c9665e5869af.hdr](/uploads/44760f0286cde4236feab8e352493556/crash-646a4364479f54278ff8c30c69b0c9665e5869af.hdr)

[crash-88c33e2b49e57e6d1d4ec6945476f605f00e714a.hdr](/uploads/e35bf53ee717134a796c4b17cfacca42/crash-88c33e2b49e57e6d1d4ec6945476f605f00e714a.hdr)
2023-09-07 16:22:33 +00:00
723f72930b xcf: fix crash (oss-fuzz issue 62075) 2023-09-07 16:13:18 +00:00
99bb24803a xcf: fix oss-fuzz issue
May fix possible crash on QPainter.
2023-09-05 10:17:22 +00:00
63a9de758f GIT_SILENT Upgrade ECM and KF version requirements for 5.110.0 release. 2023-09-03 07:35:19 +00:00
240e28aac5 QOI: Advertise write support in the desktop file 2023-08-30 14:56:55 +02:00
906ecce500 qoi: write support backported from master 2023-08-30 06:36:28 +00:00
b2b677b8a5 xcf: format v12 support (kf5)
The code is the same of MR !166 (master) but test cases are slightly different due to rounding.
2023-08-29 22:03:34 +00:00
bcec942cc9 Support libavif 1.0
Make cmake find libavif 1.0 and adapt to API changes (which is reverting to pre 0.9.2 API)

(cherry picked from commit 4badb3088e)
2023-08-29 11:05:53 +02:00
66cb8c91d0 No longer needed to explicitly include CMakeParseArguments
NO_CHANGELOG

(cherry picked from commit 4bd9d5baec)
2023-08-28 22:55:25 +02:00
899a2df42d exr: multiple fixes (kf5)
The code is the same of MR !170 (master) but test cases are slightly different due to rounding.
* Support for images with transparency
* Precise colorspace conversion using QT color spaces
* Set the correct resolution
* Set useful metadata
* Creates 16-bits images
* Speed improvements
2023-08-28 17:29:34 +00:00
47920ed63c Fix missing qoi.desktop 2023-08-19 08:53:00 +02:00
274f30e008 qoi: fix buffer overflow kf5
Backport of MR !167:
- fix buffer overflow with corrupted images without image data
- fix unable to read very small images (e.g. 1x1 px)
- new test cases added
- detect incomplete files by checking the end of streams as written in the specs
2023-08-18 15:11:41 +00:00
4348a09733 Renamed qoi.h to qoi_p.h
(cherry picked from commit 7a0d95af92)
2023-08-14 00:15:55 +02:00
dd4576a472 Minor improvements
(cherry picked from commit 4c3ade04dd)
2023-08-14 00:15:52 +02:00
9438540735 Add support for the QOI image format
This MR adds read-only QOI (https://qoiformat.org/) image format support for KImageFormats.

This format has received it's MIME type inclusion in shared-mime-info 4 months ago: ff51a0a9e3

The code is based on the reference QOI implementation at https://github.com/phoboslab/qoi/blob/master/qoi.h

Official test images: https://qoiformat.org/qoi_test_images.zip

![pngvsqoi](/uploads/e386aa5057641057106e21940c770d97/pngvsqoi.png)

Also: This is my first MR to KDE ;)
(cherry picked from commit b209e54b6f)
2023-08-11 22:47:48 +02:00
cf78907ff4 Set linear color space and round fix 2023-08-08 19:37:46 +00:00
bcb5308545 GIT_SILENT Upgrade ECM and KF version requirements for 5.109.0 release. 2023-08-05 12:37:26 +00:00
c3a91c3bc6 psd: Fix UB type punning
BUGS: 471829
2023-07-16 08:03:58 +00:00
034b8f331b Treat 3-channel MCH images as CMY images
(cherry picked from commit 6559bf8994)
2023-07-03 23:26:50 +02:00
ed6a3c520d Add explicit moc includes to sources for moc-covered headers
* speeds up incremental builds as changes to a header will not always
  need the full mocs_compilation.cpp for all the target's headers rebuild,
  while having a moc file sourced into a source file only adds minor
  extra costs, due to small own code and the used headers usually
  already covered by the source file, being for the same class/struct
* seems to not slow down clean builds, due to empty mocs_compilation.cpp
  resulting in those quickly processed, while the minor extra cost of the
  sourced moc files does not outweigh that in summary.
  Measured times actually improved by some percent points.
  (ideally CMake would just skip empty mocs_compilation.cpp & its object
  file one day)
* enables compiler to see all methods of a class in same compilation unit
  to do some sanity checks
* potentially more inlining in general, due to more in the compilation unit
* allows to keep using more forward declarations in the header, as with the
  moc code being sourced into the cpp file there definitions can be ensured
  and often are already for the needs of the normal class methods

(cherry picked from commit 34ed3bad27)
2023-07-02 11:55:33 +02:00
bf1c7e8508 GIT_SILENT Upgrade ECM and KF version requirements for 5.108.0 release. 2023-07-01 09:58:24 +00:00
3cb6519dcc jxl: add support for libjxl v0.9, drop support for old 0.6.1 2023-06-27 23:05:15 +00:00
6cbf7529ee Remove qt6 CI builds
The Qt6 builds for kf5 are no longer needed
2023-06-27 18:33:03 +02:00
55227815d5 GIT_SILENT Upgrade ECM and KF version requirements for 5.107.0 release. 2023-06-03 09:46:49 +00:00
64d51ed610 pcx: multiple fixes (2)
- 1-bit writer: checks where is black and use NOT operator only if needed
- Fix images with witdh == 65536(*)
- Checks result of disk writes and reads on all formats

(*) PCX formats support images with with of 65536 but only if the header field bytesPerLine is valid (no overflow). This means that the width 65536 is supported on 1bpp images only.
The previous version of the plugins wrote an image with width of 65536px in the wrong way and it was unable to read it (wrong image returned). I verified that Photoshop and Gimp weren't able to read the image either.

(cherry picked from commit d57ff91f8b)
2023-05-25 23:58:42 +02:00
2ca57c9c59 Avoid unnecessary conversions
(cherry picked from commit edd6adcbac)
2023-05-25 23:58:42 +02:00
f7fd14d418 RGB/SGI writer: fix alpha detection and image limit size
(cherry picked from commit d787c12727)
2023-05-25 23:58:42 +02:00
c9aa1ff629 TGA writer: fix alpha detection and performance improvements
(cherry picked from commit c9fec5e408)
2023-05-25 23:58:42 +02:00
91d3bd5227 pcx: multiple fixes
- Fix wrong RGB channel order if image format is other than (A)RGB32
- Write right resolution
- Set right resolution on image load
- Return false on write error
- Save images with depth greater than 24-bits

(cherry picked from commit e60dfd4968)
2023-05-25 23:58:42 +02:00
bb66367bc8 PCX: Fix reading of the extended palette
The VGA palette starts 769 bytes before the end of the file. There may be PADs between the end of the image and the start of the palette.

BUG: 463951
(cherry picked from commit 14742cb502)
2023-05-25 23:58:42 +02:00
14770318a3 GIT_SILENT Upgrade ECM and KF version requirements for 5.106.0 release. 2023-05-06 09:25:52 +00:00
9b1fafe29b Fix wrong alpha conversion
and use tif for image comparison in this particular one instead of png

BUG: 468288
2023-04-12 20:34:33 +00:00
fa673b5df8 GIT_SILENT Upgrade ECM and KF version requirements for 5.105.0 release. 2023-03-31 09:08:23 +00:00
e96b43aef5 psd: Fix alpha blending (KF5)
PSD files are saved with as alpha premultiplied. The problem is that alpha refers to white instead of black so it requires transformation formulas. Then, to conver PS premultiplied to QImage premultiplied you have to use the following formula:

* V = Alpha + Vps - Max (C, M, Y, K, R, G, B, Gray, L\* components)
* V = Vps + (Alpha - Max + 1) / 2 (a\*, b\* components)

Where Max is the maximum value depending on the image depth and Vps is the valued read from the file.

This is a port of MR !143 to KF5.
2023-03-29 17:58:09 +00:00
64f3303ef0 GIT_SILENT Upgrade ECM and KF version requirements for 5.104.0 release. 2023-03-04 10:04:16 +00:00
63056c52f9 GIT_SILENT Upgrade ECM and KF version requirements for 5.103.0 release. 2023-02-05 09:22:42 +00:00
2997f7ae8d psd: conversion speed improvements (kf5)
- Improved performance converting CMYK files by \~10% by replacing divisions with multiplications.
- Improved performance converting LAB files by \~50% by replacing std::pow with fastPow (approximated pow function).
2023-02-03 20:55:49 +00:00
0b4741f4b7 Fix writing TGA alpha depth flag
Correctly write alpha channel depth as 8-bit.

(cherry picked from commit 20cec27ae8)
2023-02-02 01:11:32 +01:00
bc52c03981 HDR support removed from RAW plugin 2023-01-30 21:59:09 +00:00
c1c57d9a11 heif: reject invalid files with zero size 2023-01-29 16:21:01 +01:00
81 changed files with 843 additions and 629 deletions

View File

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

View File

@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.16)
project(KImageFormats)
include(FeatureSummary)
find_package(ECM 5.240.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")
feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES)
@ -19,11 +19,11 @@ include(ECMDeprecationSettings)
include(CheckIncludeFiles)
include(FindPkgConfig)
set(REQUIRED_QT_VERSION 6.5.0)
find_package(Qt6Gui ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE)
set(REQUIRED_QT_VERSION 5.15.2)
find_package(Qt${QT_MAJOR_VERSION}Gui ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE)
find_package(KF6Archive)
set_package_properties(KF6Archive PROPERTIES
find_package(KF5Archive)
set_package_properties(KF5Archive PROPERTIES
TYPE OPTIONAL
PURPOSE "Required for the QImage plugin for Krita and OpenRaster images"
)
@ -32,12 +32,12 @@ set_package_properties(KF6Archive PROPERTIES
# this available in PATH
set(BUILD_EPS_PLUGIN FALSE)
if (UNIX)
find_package(Qt6PrintSupport ${REQUIRED_QT_VERSION} NO_MODULE)
set_package_properties(Qt6PrintSupport PROPERTIES
find_package(Qt${QT_MAJOR_VERSION}PrintSupport ${REQUIRED_QT_VERSION} NO_MODULE)
set_package_properties(Qt${QT_MAJOR_VERSION}PrintSupport PROPERTIES
PURPOSE "Required for the QImage plugin for EPS images"
TYPE OPTIONAL
)
if (TARGET Qt6::PrintSupport)
if (TARGET Qt${QT_MAJOR_VERSION}::PrintSupport)
set(BUILD_EPS_PLUGIN TRUE)
endif()
endif()
@ -81,8 +81,8 @@ set_package_properties(LibRaw PROPERTIES
)
ecm_set_disabled_deprecation_versions(
QT 6.4
KF 5.102
QT 5.15.2
KF 5.95
)
add_subdirectory(src)

View File

@ -16,24 +16,22 @@ The following image formats have read-only support:
- Animated Windows cursors (ani)
- Camera RAW images (arw, cr2, cr3, dcs, dng, ...)
- Gimp (xcf)
- Krita (kra)
- OpenEXR (exr)
- OpenRaster (ora)
- Photoshop documents (psd, psb, pdd, psdt)
- Radiance HDR (hdr)
- Sun Raster (im1, im8, im24, im32, ras, sun)
- Sun Raster (ras)
The following image formats have read and write support:
- AV1 Image File Format (AVIF)
- Encapsulated PostScript (eps)
- High Efficiency Image File Format (heif). Can be enabled with the KIMAGEFORMATS_HEIF build option.
- JPEG XL (jxl)
- Personal Computer Exchange (pcx)
- Quite OK Image format (qoi)
- SGI images (rgb, rgba, sgi, bw)
- Softimage PIC (pic)
- Targa (tga): supports more formats than Qt's version
- XView (xv)
## Contributing

View File

@ -1,3 +1,5 @@
#find_package(Qt5Test ${REQUIRED_QT_VERSION} NO_MODULE)
include(ECMMarkAsTest)
add_definitions(-DPLUGIN_DIR="${CMAKE_CURRENT_BINARY_DIR}/../bin")
@ -12,7 +14,7 @@ macro(kimageformats_read_tests)
if (NOT TARGET readtest)
add_executable(readtest readtest.cpp)
target_link_libraries(readtest Qt6::Gui)
target_link_libraries(readtest Qt${QT_MAJOR_VERSION}::Gui)
target_compile_definitions(readtest
PRIVATE IMAGEDIR="${CMAKE_CURRENT_SOURCE_DIR}/read")
ecm_mark_as_test(readtest)
@ -35,7 +37,7 @@ macro(kimageformats_write_tests)
if (NOT TARGET writetest)
add_executable(writetest writetest.cpp)
target_link_libraries(writetest Qt6::Gui)
target_link_libraries(writetest Qt${QT_MAJOR_VERSION}::Gui)
target_compile_definitions(writetest
PRIVATE IMAGEDIR="${CMAKE_CURRENT_SOURCE_DIR}/write")
ecm_mark_as_test(writetest)
@ -73,7 +75,7 @@ kimageformats_read_tests(
tga
)
if (KF6Archive_FOUND)
if (KF5Archive_FOUND)
kimageformats_read_tests(
kra
ora
@ -97,6 +99,12 @@ if (LibHeif_FOUND)
kimageformats_write_tests(FUZZ 1
heif-nodatacheck-lossless
)
if (LibHeif_VERSION VERSION_GREATER_EQUAL "1.17.0")
kimageformats_read_tests(FUZZ 1
hej2
)
endif()
endif()
if (LibJXL_FOUND AND LibJXLThreads_FOUND)
@ -146,19 +154,19 @@ if (LibRaw_FOUND)
)
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)
message(STATUS "Qt6Test not found, some autotests will not be built.")
if(NOT TARGET Qt${QT_MAJOR_VERSION}::Test)
message(STATUS "Qt${QT_MAJOR_VERSION}Test not found, some autotests will not be built.")
return()
endif()
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)
add_test(NAME kimageformats-pic COMMAND pictest)
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)
add_test(NAME kimageformats-ani COMMAND anitest)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 189 KiB

After

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 470 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 338 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 112 KiB

View File

@ -162,13 +162,13 @@ int main(int argc, char **argv)
if (!fi.suffix().compare("png", Qt::CaseInsensitive) || !fi.suffix().compare("tif", Qt::CaseInsensitive)) {
continue;
}
int suffixPos = fi.filePath().size() - suffix.size();
int suffixPos = fi.filePath().count() - suffix.count();
QString inputfile = fi.filePath();
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
fmt = QStringLiteral("tif");
expfile = fi.filePath().replace(suffixPos, suffix.size(), fmt);
expfile = fi.filePath().replace(suffixPos, suffix.count(), fmt);
}
QString expfilename = QFileInfo(expfile).fileName();

View File

@ -85,8 +85,8 @@ int main(int argc, char **argv)
if (parser.isSet(ignoreDataCheck)) {
pngfile = fi.filePath();
} else {
int suffixPos = fi.filePath().size() - suffix.size();
pngfile = fi.filePath().replace(suffixPos, suffix.size(), QStringLiteral("png"));
int suffixPos = fi.filePath().count() - suffix.count();
pngfile = fi.filePath().replace(suffixPos, suffix.count(), QStringLiteral("png"));
}
QString pngfilename = QFileInfo(pngfile).fileName();

View File

@ -11,10 +11,9 @@ function(kimageformats_add_plugin plugin)
message(FATAL_ERROR "kimageformats_add_plugin called without SOURCES parameter")
endif()
qt_add_plugin(${plugin} PLUGIN_TYPE imageformats)
target_sources(${plugin} PRIVATE ${KIF_ADD_PLUGIN_SOURCES})
set_target_properties(${plugin} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/imageformats)
target_link_libraries(${plugin} PRIVATE Qt6::Gui)
add_library(${plugin} MODULE ${KIF_ADD_PLUGIN_SOURCES})
set_target_properties(${plugin} PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/imageformats")
target_link_libraries(${plugin} Qt${QT_MAJOR_VERSION}::Gui)
install(TARGETS ${plugin} DESTINATION ${KDE_INSTALL_QTPLUGINDIR}/imageformats)
endfunction()
@ -22,105 +21,176 @@ endfunction()
kimageformats_add_plugin(kimg_ani SOURCES ani.cpp)
if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES ani.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
##################################
if (TARGET avif)
kimageformats_add_plugin(kimg_avif SOURCES "avif.cpp")
target_link_libraries(kimg_avif PRIVATE "avif")
target_link_libraries(kimg_avif "avif")
if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES avif.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
endif()
##################################
if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES dds-qt.desktop RENAME dds.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
##################################
if (BUILD_EPS_PLUGIN)
if (TARGET Qt6::PrintSupport)
if (TARGET Qt${QT_MAJOR_VERSION}::PrintSupport)
kimageformats_add_plugin(kimg_eps SOURCES eps.cpp)
target_link_libraries(kimg_eps PRIVATE Qt6::PrintSupport)
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)
kimageformats_add_plugin(kimg_exr SOURCES exr.cpp)
if(TARGET OpenEXR::OpenEXR)
target_link_libraries(kimg_exr PRIVATE OpenEXR::OpenEXR)
target_link_libraries(kimg_exr OpenEXR::OpenEXR)
else()
if(OpenEXR_VERSION_STRING VERSION_LESS 2.3.0)
# Older OpenEXR versions use dynamic exception specifications, so
# cannot use C++17 with them
set_target_properties(kimg_exr PROPERTIES CXX_STANDARD 14)
endif()
target_link_libraries(kimg_exr PRIVATE OpenEXR::IlmImf)
target_link_libraries(kimg_exr OpenEXR::IlmImf)
endif()
kde_target_enable_exceptions(kimg_exr PRIVATE)
if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES exr.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
endif()
##################################
kimageformats_add_plugin(kimg_hdr SOURCES hdr.cpp)
if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES hdr.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
##################################
if (LibHeif_FOUND)
kimageformats_add_plugin(kimg_heif SOURCES heif.cpp)
target_link_libraries(kimg_heif 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()
##################################
if (LibJXL_FOUND AND LibJXLThreads_FOUND)
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()
##################################
kimageformats_add_plugin(kimg_pcx SOURCES pcx.cpp)
if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES pcx.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
##################################
kimageformats_add_plugin(kimg_pic SOURCES pic.cpp)
if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES pic.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
##################################
kimageformats_add_plugin(kimg_psd SOURCES psd.cpp)
if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES psd.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
##################################
kimageformats_add_plugin(kimg_qoi SOURCES qoi.cpp scanlineconverter.cpp)
if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES qoi.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
##################################
kimageformats_add_plugin(kimg_ras SOURCES ras.cpp)
if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES ras.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
##################################
kimageformats_add_plugin(kimg_rgb SOURCES rgb.cpp)
if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES rgb.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
##################################
kimageformats_add_plugin(kimg_tga SOURCES tga.cpp)
if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES tga.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
##################################
kimageformats_add_plugin(kimg_xcf SOURCES xcf.cpp)
if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES xcf.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
##################################
if (LibRaw_FOUND)
kimageformats_add_plugin(kimg_raw SOURCES raw.cpp)
kde_enable_exceptions()
target_link_libraries(kimg_raw 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()
##################################
if (KF6Archive_FOUND)
if (KF5Archive_FOUND)
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)
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()

View File

@ -90,7 +90,7 @@ bool ANIHandler::read(QImage *outImage)
}
const QByteArray frameSizeData = device()->read(sizeof(quint32_le));
if (frameSizeData.size() != sizeof(quint32_le)) {
if (frameSizeData.count() != sizeof(quint32_le)) {
return false;
}
@ -384,7 +384,7 @@ bool ANIHandler::ensureScanned() const
// TODO should we check that the number of rate entries matches nSteps?
auto *dataPtr = data.data();
QVector<int> list;
for (int i = 0; i < data.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));
list.append(entry);
}

View 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

View File

@ -16,9 +16,34 @@
#include <cfloat>
/*
Quality range - compression/subsampling
100 - lossless RGB compression
< KIMG_AVIF_QUALITY_BEST, 100 ) - YUV444 color subsampling
< KIMG_AVIF_QUALITY_HIGH, KIMG_AVIF_QUALITY_BEST ) - YUV422 color subsampling
< 0, KIMG_AVIF_QUALITY_HIGH ) - YUV420 color subsampling
< 0, KIMG_AVIF_QUALITY_LOW ) - lossy compression of alpha channel
*/
#ifndef KIMG_AVIF_DEFAULT_QUALITY
#define KIMG_AVIF_DEFAULT_QUALITY 68
#endif
#ifndef KIMG_AVIF_QUALITY_BEST
#define KIMG_AVIF_QUALITY_BEST 90
#endif
#ifndef KIMG_AVIF_QUALITY_HIGH
#define KIMG_AVIF_QUALITY_HIGH 80
#endif
#ifndef KIMG_AVIF_QUALITY_LOW
#define KIMG_AVIF_QUALITY_LOW 51
#endif
QAVIFHandler::QAVIFHandler()
: m_parseState(ParseAvifNotParsed)
, m_quality(52)
, m_quality(KIMG_AVIF_DEFAULT_QUALITY)
, m_container_width(0)
, m_container_height(0)
, m_rawAvifData(AVIF_DATA_EMPTY)
@ -330,6 +355,10 @@ bool QAVIFHandler::decode_one_frame()
avifRGBImage rgb;
avifRGBImageSetDefaults(&rgb, m_decoder->image);
#if AVIF_VERSION >= 1000000
rgb.maxThreads = m_decoder->maxThreads;
#endif
if (m_decoder->image->depth > 8) {
rgb.depth = 16;
rgb.format = AVIF_RGB_FORMAT_RGBA;
@ -515,9 +544,17 @@ bool QAVIFHandler::write(const QImage &image)
}
}
if (m_quality > 100) {
m_quality = 100;
} else if (m_quality < 0) {
m_quality = KIMG_AVIF_DEFAULT_QUALITY;
}
#if AVIF_VERSION < 1000000
int maxQuantizer = AVIF_QUANTIZER_WORST_QUALITY * (100 - qBound(0, m_quality, 100)) / 100;
int minQuantizer = 0;
int maxQuantizerAlpha = 0;
#endif
avifResult res;
bool save_grayscale; // true - monochrome, false - colors
@ -563,13 +600,15 @@ bool QAVIFHandler::write(const QImage &image)
break;
}
// quality settings
#if AVIF_VERSION < 1000000
// deprecated quality settings
if (maxQuantizer > 20) {
minQuantizer = maxQuantizer - 20;
if (maxQuantizer > 40) { // we decrease quality of alpha channel here
maxQuantizerAlpha = maxQuantizer - 40;
}
}
#endif
if (save_grayscale && !image.hasAlphaChannel()) { // we are going to save grayscale image without alpha channel
if (save_depth > 8) {
@ -642,8 +681,8 @@ bool QAVIFHandler::write(const QImage &image)
QImage tmpcolorimage = image.convertToFormat(tmpformat);
avifPixelFormat pixel_format = AVIF_PIXEL_FORMAT_YUV420;
if (maxQuantizer < 20) {
if (maxQuantizer < 10) {
if (m_quality >= KIMG_AVIF_QUALITY_HIGH) {
if (m_quality >= KIMG_AVIF_QUALITY_BEST) {
pixel_format = AVIF_PIXEL_FORMAT_YUV444; // best quality
} else {
pixel_format = AVIF_PIXEL_FORMAT_YUV422; // high quality
@ -803,6 +842,8 @@ bool QAVIFHandler::write(const QImage &image)
avifRWData raw = AVIF_DATA_EMPTY;
avifEncoder *encoder = avifEncoderCreate();
encoder->maxThreads = qBound(1, QThread::idealThreadCount(), 64);
#if AVIF_VERSION < 1000000
encoder->minQuantizer = minQuantizer;
encoder->maxQuantizer = maxQuantizer;
@ -810,6 +851,17 @@ bool QAVIFHandler::write(const QImage &image)
encoder->minQuantizerAlpha = AVIF_QUANTIZER_LOSSLESS;
encoder->maxQuantizerAlpha = maxQuantizerAlpha;
}
#else
encoder->quality = m_quality;
if (image.hasAlphaChannel()) {
if (m_quality >= KIMG_AVIF_QUALITY_LOW) {
encoder->qualityAlpha = 100;
} else {
encoder->qualityAlpha = 100 - (KIMG_AVIF_QUALITY_LOW - m_quality) / 2;
}
}
#endif
encoder->speed = 6;
@ -866,7 +918,7 @@ void QAVIFHandler::setOption(ImageOption option, const QVariant &value)
if (m_quality > 100) {
m_quality = 100;
} else if (m_quality < 0) {
m_quality = 52;
m_quality = KIMG_AVIF_DEFAULT_QUALITY;
}
return;
default:
@ -1039,6 +1091,11 @@ int QAVIFHandler::loopCount() const
return 0;
}
#if AVIF_VERSION >= 1000000
if (m_decoder->repetitionCount >= 0) {
return m_decoder->repetitionCount;
}
#endif
// Endless loop to work around https://github.com/AOMediaCodec/libavif/issues/347
return -1;
}

View 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

View 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

View 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

View File

@ -54,7 +54,9 @@
#include <QImage>
#include <QImageIOPlugin>
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
#include <QTimeZone>
#endif
// Allow the code to works on all QT versions supported by KDE
// project (Qt 5.15 and Qt 6.x) to easy backports fixes.
@ -192,7 +194,11 @@ bool EXRHandler::read(QImage *outImage)
}
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));
}
}
@ -249,7 +255,7 @@ bool EXRHandler::read(QImage *outImage)
#endif // !EXR_ALLOW_LINEAR_COLORSPACE
#endif // !EXR_USE_LEGACY_CONVERSIONS
*outImage = std::move(image);
*outImage = image;
return true;
} catch (const std::exception &exc) {

View 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

View File

@ -11,18 +11,12 @@
#include <QColorSpace>
#include <QDataStream>
#include <QFloat16>
#include <QImage>
#include <QLoggingCategory>
#include <QRegularExpressionMatch>
#include <QDebug>
/* *** HDR_HALF_QUALITY ***
* If defined, a 16-bits float image is created, otherwise a 32-bits float ones (default).
*/
//#define HDR_HALF_QUALITY // default commented -> you should define it in your cmake file
typedef unsigned char uchar;
Q_LOGGING_CATEGORY(HDRPLUGIN, "kf.imageformats.plugins.hdr", QtWarningMsg)
@ -33,6 +27,12 @@ namespace // Private.
#define MINELEN 8 // minimum 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
// if 'first' is true the first byte is already read
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;
}
template<class float_T>
void RGBE_To_QRgbLine(uchar *image, float_T *scanline, int width)
static void RGBE_To_QRgbLine(uchar *image, QRgb *scanline, int width)
{
for (int j = 0; j < width; j++) {
// 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);
}
auto j4 = j * 4;
auto vn = v / 255.0f;
scanline[j4] = float_T(std::min(float(image[0]) * vn, 1.0f));
scanline[j4 + 1] = float_T(std::min(float(image[1]) * vn, 1.0f));
scanline[j4 + 2] = float_T(std::min(float(image[2]) * vn, 1.0f));
scanline[j4 + 3] = float_T(1.0f);
scanline[j] = qRgb(ClipToByte(float(image[0]) * v), ClipToByte(float(image[1]) * v), ClipToByte(float(image[2]) * v));
image += 4;
}
}
QImage::Format imageFormat()
{
#ifdef HDR_HALF_QUALITY
return QImage::Format_RGBX16FPx4;
#else
return QImage::Format_RGBX32FPx4;
#endif
}
// Load the HDR image.
static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &img)
{
@ -114,7 +100,7 @@ static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &i
uchar code;
// Create dst image.
img = imageAlloc(width, height, imageFormat());
img = imageAlloc(width, height, QImage::Format_RGB32);
if (img.isNull()) {
qCDebug(HDRPLUGIN) << "Couldn't create image with size" << width << height << "and format RGB32";
return false;
@ -122,14 +108,10 @@ static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &i
QByteArray lineArray;
lineArray.resize(4 * width);
uchar *image = reinterpret_cast<uchar *>(lineArray.data());
uchar *image = (uchar *)lineArray.data();
for (int cline = 0; cline < height; cline++) {
#ifdef HDR_HALF_QUALITY
auto scanline = reinterpret_cast<qfloat16 *>(img.scanLine(cline));
#else
auto scanline = reinterpret_cast<float *>(img.scanLine(cline));
#endif
QRgb *scanline = (QRgb *)img.scanLine(cline);
// determine scanline type
if ((width < MINELEN) || (MAXELEN < width)) {
@ -211,7 +193,9 @@ static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &i
return true;
}
static QSize readHeaderSize(QIODevice *device)
} // namespace
bool HDRHandler::read(QImage *outImage)
{
int len;
QByteArray line(MAXLINE + 1, Qt::Uninitialized);
@ -219,7 +203,7 @@ static QSize readHeaderSize(QIODevice *device)
// Parse header
do {
len = device->readLine(line.data(), MAXLINE);
len = device()->readLine(line.data(), MAXLINE);
if (line.startsWith("FORMAT=")) {
format = line.mid(7, len - 7 - 1 /*\n*/);
@ -229,10 +213,10 @@ static QSize readHeaderSize(QIODevice *device)
if (format != "32-bit_rle_rgbe") {
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);
/*
@ -252,30 +236,21 @@ static QSize readHeaderSize(QIODevice *device)
QRegularExpressionMatch match = resolutionRegExp.match(QString::fromLatin1(line));
if (!match.hasMatch()) {
qCDebug(HDRPLUGIN) << "Invalid HDR file, the first line after the header didn't have the expected format:" << line;
return QSize();
return false;
}
if ((match.captured(1).at(1) != u'Y') || (match.captured(3).at(1) != u'X')) {
qCDebug(HDRPLUGIN) << "Unsupported image orientation in HDR file.";
return QSize();
}
return QSize(match.captured(4).toInt(), match.captured(2).toInt());
}
} // namespace
bool HDRHandler::read(QImage *outImage)
{
QDataStream s(device());
QSize size = readHeaderSize(s.device());
if (!size.isValid()) {
return false;
}
const int width = match.captured(4).toInt();
const int height = match.captured(2).toInt();
QDataStream s(device());
QImage img;
if (!LoadHDR(s, size.width(), size.height(), img)) {
if (!LoadHDR(s, width, height, img)) {
// qDebug() << "Error loading HDR file.";
return false;
}
@ -283,44 +258,10 @@ bool HDRHandler::read(QImage *outImage)
// By setting the linear color space, programs that support profiles display HDR files as in GIMP and Photoshop.
img.setColorSpace(QColorSpace(QColorSpace::SRgbLinear));
*outImage = std::move(img);
*outImage = img;
return true;
}
bool HDRHandler::supportsOption(ImageOption option) const
{
if (option == QImageIOHandler::Size) {
return true;
}
if (option == QImageIOHandler::ImageFormat) {
return true;
}
return false;
}
QVariant HDRHandler::option(ImageOption option) const
{
QVariant v;
if (option == QImageIOHandler::Size) {
if (auto d = device()) {
// transactions works on both random and sequential devices
d->startTransaction();
auto size = readHeaderSize(d);
d->rollbackTransaction();
if (size.isValid()) {
v = QVariant::fromValue(size);
}
}
}
if (option == QImageIOHandler::ImageFormat) {
v = QVariant::fromValue(imageFormat());
}
return v;
}
HDRHandler::HDRHandler()
{
}
@ -341,20 +282,7 @@ bool HDRHandler::canRead(QIODevice *device)
return false;
}
// the .pic taken from official test cases does not start with this string but can be loaded.
if(device->peek(11) == "#?RADIANCE\n" || device->peek(7) == "#?RGBE\n") {
return true;
}
// allow to load offical test cases: https://radsite.lbl.gov/radiance/framed.html
device->startTransaction();
QSize size = readHeaderSize(device);
device->rollbackTransaction();
if (size.isValid()) {
return true;
}
return false;
return device->peek(11) == "#?RADIANCE\n" || device->peek(7) == "#?RGBE\n";
}
QImageIOPlugin::Capabilities HDRPlugin::capabilities(QIODevice *device, const QByteArray &format) const

View 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

View File

@ -18,9 +18,6 @@ public:
bool canRead() const 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);
};

View File

@ -22,6 +22,7 @@ size_t HEIFHandler::m_initialized_count = 0;
bool HEIFHandler::m_plugins_queried = false;
bool HEIFHandler::m_heif_decoder_available = false;
bool HEIFHandler::m_heif_encoder_available = false;
bool HEIFHandler::m_hej2_decoder_available = false;
extern "C" {
static struct heif_error heifhandler_write_callback(struct heif_context * /* ctx */, const void *data, size_t size, void *userdata)
@ -59,12 +60,25 @@ HEIFHandler::HEIFHandler()
bool HEIFHandler::canRead() const
{
if (m_parseState == ParseHeicNotParsed && !canRead(device())) {
if (m_parseState == ParseHeicNotParsed) {
QIODevice *dev = device();
if (dev) {
const QByteArray header = dev->peek(28);
if (HEIFHandler::isSupportedBMFFType(header)) {
setFormat("heif");
return true;
}
if (HEIFHandler::isSupportedHEJ2(header)) {
setFormat("hej2");
return true;
}
}
return false;
}
if (m_parseState != ParseHeicError) {
setFormat("heif");
return true;
}
return false;
@ -300,17 +314,6 @@ bool HEIFHandler::write_helper(const QImage &image)
return true;
}
bool HEIFHandler::canRead(QIODevice *device)
{
if (!device) {
qWarning("HEIFHandler::canRead() called with no device");
return false;
}
const QByteArray header = device->peek(28);
return HEIFHandler::isSupportedBMFFType(header);
}
bool HEIFHandler::isSupportedBMFFType(const QByteArray &header)
{
if (header.size() < 28) {
@ -350,6 +353,22 @@ bool HEIFHandler::isSupportedBMFFType(const QByteArray &header)
return false;
}
bool HEIFHandler::isSupportedHEJ2(const QByteArray &header)
{
if (header.size() < 28) {
return false;
}
const char *buffer = header.constData();
if (qstrncmp(buffer + 4, "ftyp", 4) == 0) {
if (qstrncmp(buffer + 8, "j2ki", 4) == 0) {
return true;
}
}
return false;
}
QVariant HEIFHandler::option(ImageOption option) const
{
if (option == Quality) {
@ -425,7 +444,7 @@ bool HEIFHandler::ensureDecoder()
}
const QByteArray buffer = device()->readAll();
if (!HEIFHandler::isSupportedBMFFType(buffer)) {
if (!HEIFHandler::isSupportedBMFFType(buffer) && !HEIFHandler::isSupportedHEJ2(buffer)) {
m_parseState = ParseHeicError;
return false;
}
@ -814,6 +833,9 @@ bool HEIFHandler::isHeifDecoderAvailable()
}
#endif
#if LIBHEIF_HAVE_VERSION(1, 13, 0)
m_hej2_decoder_available = heif_have_decoder_for_format(heif_compression_JPEG2000);
#endif
m_heif_encoder_available = heif_have_encoder_for_format(heif_compression_HEVC);
m_heif_decoder_available = heif_have_decoder_for_format(heif_compression_HEVC);
m_plugins_queried = true;
@ -839,6 +861,9 @@ bool HEIFHandler::isHeifEncoderAvailable()
}
#endif
#if LIBHEIF_HAVE_VERSION(1, 13, 0)
m_hej2_decoder_available = heif_have_decoder_for_format(heif_compression_JPEG2000);
#endif
m_heif_decoder_available = heif_have_decoder_for_format(heif_compression_HEVC);
m_heif_encoder_available = heif_have_encoder_for_format(heif_compression_HEVC);
m_plugins_queried = true;
@ -853,6 +878,34 @@ bool HEIFHandler::isHeifEncoderAvailable()
return m_heif_encoder_available;
}
bool HEIFHandler::isHej2DecoderAvailable()
{
QMutexLocker locker(&getHEIFHandlerMutex());
if (!m_plugins_queried) {
#if LIBHEIF_HAVE_VERSION(1, 13, 0)
if (m_initialized_count == 0) {
heif_init(nullptr);
}
#endif
m_heif_encoder_available = heif_have_encoder_for_format(heif_compression_HEVC);
m_heif_decoder_available = heif_have_decoder_for_format(heif_compression_HEVC);
#if LIBHEIF_HAVE_VERSION(1, 13, 0)
m_hej2_decoder_available = heif_have_decoder_for_format(heif_compression_JPEG2000);
#endif
m_plugins_queried = true;
#if LIBHEIF_HAVE_VERSION(1, 13, 0)
if (m_initialized_count == 0) {
heif_deinit();
}
#endif
}
return m_hej2_decoder_available;
}
void HEIFHandler::startHeifLib()
{
#if LIBHEIF_HAVE_VERSION(1, 13, 0)
@ -901,6 +954,15 @@ QImageIOPlugin::Capabilities HEIFPlugin::capabilities(QIODevice *device, const Q
}
return format_cap;
}
if (format == "hej2") {
Capabilities format_cap;
if (HEIFHandler::isHej2DecoderAvailable()) {
format_cap |= CanRead;
}
return format_cap;
}
if (!format.isEmpty()) {
return {};
}
@ -909,8 +971,16 @@ QImageIOPlugin::Capabilities HEIFPlugin::capabilities(QIODevice *device, const Q
}
Capabilities cap;
if (device->isReadable() && HEIFHandler::canRead(device) && HEIFHandler::isHeifDecoderAvailable()) {
cap |= CanRead;
if (device->isReadable()) {
const QByteArray header = device->peek(28);
if (HEIFHandler::isSupportedBMFFType(header) && HEIFHandler::isHeifDecoderAvailable()) {
cap |= CanRead;
}
if (HEIFHandler::isSupportedHEJ2(header) && HEIFHandler::isHej2DecoderAvailable()) {
cap |= CanRead;
}
}
if (device->isWritable() && HEIFHandler::isHeifEncoderAvailable()) {

View 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

View File

@ -1,4 +1,4 @@
{
"Keys": [ "heif", "heic" ],
"MimeTypes": [ "image/heif", "image/heif" ]
"Keys": [ "heif", "heic", "hej2" ],
"MimeTypes": [ "image/heif", "image/heif", "image/hej2k" ]
}

View File

@ -24,17 +24,18 @@ public:
bool read(QImage *image) override;
bool write(const QImage &image) override;
static bool canRead(QIODevice *device);
QVariant option(ImageOption option) const override;
void setOption(ImageOption option, const QVariant &value) override;
bool supportsOption(ImageOption option) const override;
static bool isHeifDecoderAvailable();
static bool isHeifEncoderAvailable();
static bool isHej2DecoderAvailable();
static bool isSupportedBMFFType(const QByteArray &header);
static bool isSupportedHEJ2(const QByteArray &header);
private:
static bool isSupportedBMFFType(const QByteArray &header);
bool ensureParsed() const;
bool ensureDecoder();
@ -57,6 +58,7 @@ private:
static bool m_plugins_queried;
static bool m_heif_decoder_available;
static bool m_heif_encoder_available;
static bool m_hej2_decoder_available;
static QMutex &getHEIFHandlerMutex();
};

View 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

View 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

View 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

View 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

View File

@ -383,7 +383,8 @@ static bool readImage8(QImage &img, QDataStream &s, const PCXHEADER &header)
while (flag != 12 && s.status() == QDataStream::Ok) {
s >> flag;
}
} else {
}
else {
device->seek(device->size() - 769);
s >> flag;
}
@ -612,7 +613,7 @@ static bool writeImage24(QImage &img, QDataStream &s, PCXHEADER &header)
QByteArray b_buf(header.width(), 0);
for (int y = 0; y < header.height(); ++y) {
auto p = (QRgb *)img.scanLine(y);
auto p = (QRgb*)img.scanLine(y);
for (int x = 0; x < header.width(); ++x) {
QRgb rgb = *p++;
@ -683,7 +684,7 @@ bool PCXHandler::read(QImage *outImage)
img.setDotsPerMeterX(qRound(header.HDpi / 25.4 * 1000));
img.setDotsPerMeterY(qRound(header.YDpi / 25.4 * 1000));
*outImage = std::move(img);
*outImage = img;
return true;
}

View 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

View File

@ -255,7 +255,7 @@ bool SoftimagePICHandler::read(QImage *image)
}
}
*image = std::move(img);
*image = img;
m_state = Ready;
return true;

View 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

View File

@ -20,13 +20,13 @@
/*
* Limitations of the current code:
* - Color spaces other than RGB/Grayscale cannot be read due to lack of QImage
* support. Where possible, a conversion to RGB is done:
* - 32-bit float image are converted to 16-bit integer image.
* - 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
* information (ICC profile).
* - 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
* color management engine (e.g. LittleCMS).
@ -733,9 +733,7 @@ static QImage::Format imageFormat(const PSDHeader &header, bool alpha)
auto format = QImage::Format_Invalid;
switch(header.color_mode) {
case CM_RGB:
if (header.depth == 32)
format = header.channel_count < 4 || !alpha ? QImage::Format_RGBX32FPx4 : QImage::Format_RGBA32FPx4_Premultiplied;
else if (header.depth == 16)
if (header.depth == 16 || header.depth == 32)
format = header.channel_count < 4 || !alpha ? QImage::Format_RGBX64 : QImage::Format_RGBA64_Premultiplied;
else
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;
}
inline quint8 xchg(quint8 v)
{
inline quint8 xchg(quint8 v) {
return v;
}
inline quint16 xchg(quint16 v)
{
inline quint16 xchg(quint16 v) {
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
return quint16( (v>>8) | (v<<8) );
#else
@ -805,8 +801,7 @@ inline quint16 xchg(quint16 v)
#endif
}
inline quint32 xchg(quint32 v)
{
inline quint32 xchg(quint32 v) {
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
return quint32( (v>>24) | ((v & 0x00FF0000)>>8) | ((v & 0x0000FF00)<<8) | (v<<24) );
#else
@ -863,9 +858,8 @@ enum class PremulConversion {
template<class T>
inline void premulConversion(char *stride, qint32 width, qint32 ac, qint32 cn, const PremulConversion &conv)
{
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>::is_integer ? std::numeric_limits<T>::max() : 1);
auto s = reinterpret_cast<T *>(stride);
auto max = qint64(std::numeric_limits<T>::max());
for (qint32 c = 0; c < ac; ++c) {
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);
*(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) {
auto xcn = x * cn;
auto alpha = *(s + xcn + ac);
if (alpha > 0)
*(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) {
auto xcn = x * cn;
auto alpha = *(s + xcn + ac);
@ -906,8 +898,8 @@ inline void monoInvert(uchar *target, const char* source, qint32 bytes)
template<class T>
inline void rawChannelsCopy(uchar *target, qint32 targetChannels, const char *source, qint32 sourceChannels, qint32 width)
{
auto s = reinterpret_cast<const T*>(source);
auto t = reinterpret_cast<T*>(target);
auto s = reinterpret_cast<const T *>(source);
auto t = reinterpret_cast<T *>(target);
for (qint32 c = 0, cs = std::min(targetChannels, sourceChannels); c < cs; ++c) {
for (qint32 x = 0; x < width; ++x) {
t[x * targetChannels + c] = s[x * sourceChannels + c];
@ -1139,15 +1131,15 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
// clang-format off
// checks the need of color conversion (that requires random access to the image)
auto randomAccess = (header.color_mode == CM_CMYK) ||
(header.color_mode == CM_LABCOLOR) ||
(header.color_mode == CM_MULTICHANNEL) ||
(header.color_mode != CM_INDEXED && img.hasAlphaChannel());
(header.color_mode == CM_LABCOLOR) ||
(header.color_mode == CM_MULTICHANNEL) ||
(header.color_mode != CM_INDEXED && img.hasAlphaChannel());
// clang-format on
if (randomAccess) {
// In order to make a colorspace transformation, we need all channels of a scanline
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 c = 0; c < header.channel_count; ++c) {
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());
if (header.depth == 8) {
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);
}
else if (header.depth == 32) {
planarToChunchy<float>(scanLine, rawStride.data(), header.width, c, header.channel_count);
} else if (header.depth == 32) {
planarToChunchyFloatToUInt16<float>(scanLine, rawStride.data(), header.width, c, header.channel_count);
}
}
// Convert premultiplied data to unassociated data
if (img.hasAlphaChannel()) {
auto scanLine = reinterpret_cast<char*>(psdScanline.data());
if (header.color_mode == CM_CMYK) {
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)
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.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)
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.depth == 8)
premulConversion<quint8>(scanLine, header.width, 3, header.channel_count, PremulConversion::PS2P);
else if (header.depth == 16)
premulConversion<quint16>(scanLine, header.width, 3, header.channel_count, PremulConversion::PS2P);
else if (header.depth == 32)
premulConversion<float>(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 || header.depth == 32)
premulConversion<quint16>(psdScanline.data(), 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.depth == 8)
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);
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
for (qint32 c = 0; c < channel_num; ++c) {
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);
if (header.depth == 1) { // Bitmap
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);
}
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);
}
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)
} else if (header.depth == 32) { // 32-bits float images: Grayscale, RGB/RGBA (coverted to equivalent integer 16-bits)
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
if (!setResolution(img, irs)) {
@ -1258,15 +1244,7 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
}
// ICC profile
if (header.color_mode == CM_LABCOLOR) {
// 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)) {
if (!setColorSpace(img, irs)) {
// qDebug() << "No colorspace info set!";
}
@ -1326,7 +1304,7 @@ bool PSDHandler::read(QImage *image)
return false;
}
*image = std::move(img);
*image = img;
return true;
}

View 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

View 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

View File

@ -3,7 +3,6 @@
SPDX-FileCopyrightText: 2003 Dominik Seichter <domseichter@web.de>
SPDX-FileCopyrightText: 2004 Ignacio Castaño <castano@ludicon.com>
SPDX-FileCopyrightText: 2010 Troy Unrau <troy@kde.org>
SPDX-FileCopyrightText: 2023 Mirco Miranda <mircomir@outlook.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
@ -40,14 +39,14 @@ enum RASColorMapType {
};
struct RasHeader {
quint32 MagicNumber = 0;
quint32 Width = 0;
quint32 Height = 0;
quint32 Depth = 0;
quint32 Length = 0;
quint32 Type = 0;
quint32 ColorMapType = 0;
quint32 ColorMapLength = 0;
quint32 MagicNumber;
quint32 Width;
quint32 Height;
quint32 Depth;
quint32 Length;
quint32 Type;
quint32 ColorMapType;
quint32 ColorMapLength;
enum {
SIZE = 32,
}; // 8 fields of four bytes each
@ -81,251 +80,152 @@ static bool IsSupported(const RasHeader &head)
return false;
}
// check for an appropriate depth
if (head.Depth != 1 && head.Depth != 8 && head.Depth != 24 && head.Depth != 32) {
return false;
}
if (head.Width == 0 || head.Height == 0) {
// we support 8bit+palette, 24bit and 32bit ONLY!
// TODO: add support for 1bit
if (!((head.Depth == 8 && head.ColorMapType == 1) || head.Depth == 24 || head.Depth == 32)) {
return false;
}
// 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!
// TODO: add support for Type 4,5: TIFF/IFF
if (!(head.Type == RAS_TYPE_STANDARD || head.Type == RAS_TYPE_RGB_FORMAT || head.Type == RAS_TYPE_BYTE_ENCODED)) {
// we support Type 1: Normal(BGR) and Type 3: Normal(RGB) ONLY!
// TODO: add support for Type 2: RLE(BGR) & Type 4,5: TIFF/IFF
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 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)
{
s.device()->seek(RasHeader::SIZE);
// The width of a scan line is always a multiple of 16 bits, padded when necessary.
auto rasLineSize = (qint64(ras.Width) * ras.Depth + 7) / 8;
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()) {
if (ras.ColorMapLength > kMaxQVectorSize) {
qWarning() << "LoadRAS() unsupported image color map length in file header" << ras.ColorMapLength;
return false;
}
// Read palette if needed.
if (ras.ColorMapType == RAS_COLOR_MAP_TYPE_RGB) {
QVector<quint8> palette(ras.ColorMapLength);
QVector<quint8> palette(ras.ColorMapLength);
if (ras.ColorMapType == 1) {
for (quint32 i = 0; i < ras.ColorMapLength; ++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;) {
colorTable << qRgb(255, 255, 255);
}
img.setColorTable(colorTable);
if (s.status() != QDataStream::Ok) {
return false;
i++;
}
// Allocate image
img = imageAlloc(ras.Width, ras.Height, QImage::Format_ARGB32);
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);
auto bytesPerLine = std::min(img.bytesPerLine(), qsizetype(rasLineSize));
for (quint32 y = 0; y < ras.Height; ++y) {
auto rasLine = dec.readLine(rasLineSize);
if (rasLine.size() != rasLineSize) {
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));
if (ras.ColorMapType == 0 && ras.Depth == 24 && (ras.Type == 1 || ras.Type == 2)) {
quint8 red;
quint8 green;
quint8 blue;
for (quint32 y = 0; y < ras.Height; y++) {
for (quint32 x = 0; x < ras.Width; x++) {
red = rasLine.at(x * 3 + 2);
green = rasLine.at(x * 3 + 1);
blue = rasLine.at(x * 3);
*(scanLine + x) = qRgb(red, green, blue);
red = input[y * 3 * ras.Width + x * 3 + 2];
green = input[y * 3 * ras.Width + x * 3 + 1];
blue = input[y * 3 * ras.Width + x * 3];
img.setPixel(x, y, qRgb(red, green, blue));
}
continue;
}
}
// RGB 24-bit
if (ras.ColorMapType == RAS_COLOR_MAP_TYPE_NONE && ras.Depth == 24 && ras.Type == RAS_TYPE_RGB_FORMAT) {
quint8 red;
quint8 green;
quint8 blue;
auto scanLine = reinterpret_cast<QRgb *>(img.scanLine(y));
if (ras.ColorMapType == 0 && ras.Depth == 24 && ras.Type == 3) {
quint8 red;
quint8 green;
quint8 blue;
for (quint32 y = 0; y < ras.Height; y++) {
for (quint32 x = 0; x < ras.Width; x++) {
red = rasLine.at(x * 3);
green = rasLine.at(x * 3 + 1);
blue = rasLine.at(x * 3 + 2);
*(scanLine + x) = qRgb(red, green, blue);
red = input[y * 3 * ras.Width + x * 3];
green = input[y * 3 * ras.Width + x * 3 + 1];
blue = input[y * 3 * ras.Width + x * 3 + 2];
img.setPixel(x, y, qRgb(red, green, blue));
}
continue;
}
}
// BGR 32-bit (not tested: test case missing)
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 green;
quint8 blue;
auto scanLine = reinterpret_cast<QRgb *>(img.scanLine(y));
if (ras.ColorMapType == 0 && ras.Depth == 32 && (ras.Type == 1 || ras.Type == 2)) {
quint8 red;
quint8 green;
quint8 blue;
for (quint32 y = 0; y < ras.Height; y++) {
for (quint32 x = 0; x < ras.Width; x++) {
red = rasLine.at(x * 4 + 3);
green = rasLine.at(x * 4 + 2);
blue = rasLine.at(x * 4 + 1);
*(scanLine + x) = qRgb(red, green, blue);
red = input[y * 4 * ras.Width + x * 4 + 3];
green = input[y * 4 * ras.Width + x * 4 + 2];
blue = input[y * 4 * ras.Width + x * 4 + 1];
img.setPixel(x, y, qRgb(red, green, blue));
}
continue;
}
}
// RGB 32-bit (tested: test case missing due to image too large)
if (ras.ColorMapType == RAS_COLOR_MAP_TYPE_NONE && ras.Depth == 32 && ras.Type == RAS_TYPE_RGB_FORMAT) {
quint8 red;
quint8 green;
quint8 blue;
auto scanLine = reinterpret_cast<QRgb *>(img.scanLine(y));
if (ras.ColorMapType == 0 && ras.Depth == 32 && ras.Type == 3) {
quint8 red;
quint8 green;
quint8 blue;
for (quint32 y = 0; y < ras.Height; y++) {
for (quint32 x = 0; x < ras.Width; x++) {
red = rasLine.at(x * 4 + 1);
green = rasLine.at(x * 4 + 2);
blue = rasLine.at(x * 4 + 3);
*(scanLine + x) = qRgb(red, green, blue);
red = input[y * 4 * ras.Width + x * 4 + 1];
green = input[y * 4 * ras.Width + x * 4 + 2];
blue = input[y * 4 * ras.Width + x * 4 + 3];
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;
@ -383,8 +283,16 @@ bool RASHandler::read(QImage *outImage)
RasHeader ras;
s >> ras;
if (ras.ColorMapLength > kMaxQVectorSize) {
qWarning() << "LoadRAS() unsupported image color map length in file header" << ras.ColorMapLength;
if (ras.ColorMapLength > std::numeric_limits<int>::max()) {
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;
}
@ -402,69 +310,13 @@ bool RASHandler::read(QImage *outImage)
return false;
}
*outImage = std::move(img);
*outImage = img;
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
{
if (format == "im1" || format == "im8" || format == "im24" || format == "im32" || format == "ras" || format == "sun") {
if (format == "ras") {
return Capabilities(CanRead);
}
if (!format.isEmpty()) {

View 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

View File

@ -1,4 +1,4 @@
{
"Keys": [ "im1", "im8", "im24", "im32", "ras", "sun" ],
"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" ]
"Keys": [ "ras" ],
"MimeTypes": [ "image/x-sun-raster" ]
}

View File

@ -19,9 +19,6 @@ public:
bool canRead() const 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);
};

View File

@ -14,7 +14,6 @@
#include <QDebug>
#include <QImage>
#include <QSet>
#include <QTimeZone>
#if defined(Q_OS_WINDOWS) && !defined(NOMINMAX)
#define NOMINMAX
@ -223,7 +222,7 @@ QString createTag(char *asciiz, 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) {
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_FC(a) (((a) & 0x1) << 19)
#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_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_FC(a) (((a) >> 19) & 0x1)
#define T_SR(a) (((a) >> 20) & 0x1)
#define T_FLAGS(a) (((a) >> 31) & 0x1)
#define T_PRESET(a) ((a) & 0xF)
// 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)
{
@ -444,7 +443,9 @@ void setParams(QImageIOHandler *handler, LibRaw *rawProcessor)
auto &&rawparams = rawProcessor->imgdata.rawparams;
#endif
// Select one raw image from input file (0 - first, ...)
rawparams.shot_select = handler->currentImageNumber();
if (handler->currentImageNumber() > -1) {
rawparams.shot_select = handler->currentImageNumber();
}
// *** Set processing parameters
@ -459,45 +460,47 @@ void setParams(QImageIOHandler *handler, LibRaw *rawProcessor)
if (handler->supportsOption(QImageIOHandler::Quality)) {
quality = handler->option(QImageIOHandler::Quality).toInt();
}
if (quality > -1) {
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) {
if (quality < 0) {
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 &&params = rawProcessor->imgdata.params;
@ -722,6 +725,7 @@ RAWHandler::RAWHandler()
: m_imageNumber(0)
, m_imageCount(0)
, m_quality(-1)
, m_startPos(-1)
{
}
@ -738,6 +742,15 @@ bool RAWHandler::read(QImage *image)
{
auto dev = device();
// set the image position after the first run.
if (!dev->isSequential()) {
if (m_startPos < 0) {
m_startPos = dev->pos();
} else {
dev->seek(m_startPos);
}
}
// Check image file format.
if (dev->atEnd()) {
return false;
@ -748,7 +761,7 @@ bool RAWHandler::read(QImage *image)
return false;
}
*image = std::move(img);
*image = img;
return true;
}
@ -819,7 +832,7 @@ bool RAWHandler::jumpToNextImage()
bool RAWHandler::jumpToImage(int imageNumber)
{
if (imageNumber >= imageCount()) {
if (imageNumber < 0 || imageNumber >= imageCount()) {
return false;
}
m_imageNumber = imageNumber;

View 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

View File

@ -40,13 +40,30 @@ private:
* Change the quality of the conversion. If -1, default quality is used.
* @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
* 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:
* _: 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)
* 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)
@ -57,23 +74,16 @@ private:
* N: FBDD noise reduction (0 - off, 1 - light, 2 - full)
* 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)
*
* @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;
/*!
* \brief m_startPos
* The initial device position to allow multi image load (cache value).
*/
qint64 m_startPos;
};
class RAWPlugin : public QImageIOPlugin

View File

@ -678,9 +678,9 @@ bool SGIImage::writeImage(const QImage &image)
}
if (hasAlpha && img.format() != QImage::Format_ARGB32) {
img.convertTo(QImage::Format_ARGB32);
img = img.convertToFormat(QImage::Format_ARGB32);
} else if (!hasAlpha && img.format() != QImage::Format_RGB32) {
img.convertTo(QImage::Format_RGB32);
img = img.convertToFormat(QImage::Format_RGB32);
}
if (img.isNull()) {
// qDebug() << "can't convert image to depth 32";

View 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

View File

@ -88,10 +88,6 @@ static QDataStream &operator>>(QDataStream &s, TgaHeader &head)
s >> head.height;
s >> head.pixel_size;
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;
}
@ -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) {
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;
}
@ -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)
{
// Create image.
img = imageAlloc(tga.width, tga.height, QImage::Format_RGB32);
img = imageAlloc(tga.width, tga.height, imageFormat(tga));
if (img.isNull()) {
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(tga.width, tga.height);
return false;
@ -181,21 +228,7 @@ static bool LoadTGA(QDataStream &s, const TgaHeader &tga, QImage &img)
TgaHeaderInfo info(tga);
// Bits 0-3 are the numbers of alpha bits (can be zero!)
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);
qint64 size = qint64(tga.width) * qint64(tga.height) * pixel_size;
@ -391,23 +424,25 @@ bool TGAHandler::read(QImage *outImage)
{
// qDebug() << "Loading TGA file!";
QDataStream s(device());
s.setByteOrder(QDataStream::LittleEndian);
// Read image header.
auto d = device();
TgaHeader tga;
s >> tga;
s.device()->seek(TgaHeader::SIZE + tga.id_length);
// Check image file format.
if (s.atEnd()) {
if (!peekHeader(d, tga) || !IsSupported(tga)) {
// qDebug() << "This TGA file is not valid.";
return false;
}
// Check supported file types.
if (!IsSupported(tga)) {
// qDebug() << "This TGA file is not supported.";
if (d->isSequential()) {
d->read(TgaHeader::SIZE + tga.id_length);
} 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;
}
@ -419,7 +454,7 @@ bool TGAHandler::read(QImage *outImage)
return false;
}
*outImage = std::move(img);
*outImage = img;
return true;
}
@ -468,6 +503,42 @@ bool TGAHandler::write(const QImage &image)
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)
{
if (!device) {
@ -491,10 +562,12 @@ bool TGAHandler::canRead(QIODevice *device)
return false;
}
QDataStream stream(head);
stream.setByteOrder(QDataStream::LittleEndian);
TgaHeader tga;
stream >> tga;
if (!peekHeader(device, tga)) {
qWarning("TGAHandler::canRead() error while reading the header");
return false;
}
return IsSupported(tga);
}

View 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

View File

@ -19,6 +19,9 @@ public:
bool read(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);
};

View File

@ -11,7 +11,10 @@
#include <limits>
#include <QImage>
#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;
@ -21,9 +24,13 @@ static constexpr int kMaxQVectorSize = std::numeric_limits<int>::max() - 32;
inline QImage imageAlloc(const QSize &size, const QImage::Format &format)
{
QImage img;
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
img = QImage(size, format);
#else
if (!QImageIOHandler::allocateImage(size, format, &img)) {
img = QImage(); // paranoia
}
#endif
return img;
}

View 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

View File

@ -5,7 +5,7 @@ include(ECMMarkAsTest)
macro(kimageformats_executable_tests)
foreach(_testname ${ARGN})
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})
endforeach(_testname)
endmacro()

View File

@ -103,7 +103,7 @@ int main(int argc, char **argv)
QTextStream(stderr) << "Unknown QImage data format " << parser.value(qimgformat) << '\n';
return 4;
}
img.convertTo(qformat);
img = img.convertToFormat(qformat);
}
qint64 written = output.write(reinterpret_cast<const char *>(img.bits()), img.sizeInBytes());
if (written != img.sizeInBytes()) {