Compare commits

...

70 Commits

Author SHA1 Message Date
Nicolas Fella
84f28e8bc9 Update dependency version to 6.26.0 2026-05-01 13:20:34 +02:00
Mirco Miranda
a936927ec1 JXR: check all library return codes 2026-04-26 06:22:30 +02:00
Mirco Miranda
191e5e6a69 EXIF improvements and bugfixes
- Fixes a writing issue for float values ​​less than 1
- Fixes a missing definition of `EXIF_DATETIMEDIGITIZED` tag
- Adds support for some common camera shot metadata
- Adds missing metadata to writing tests

The following plugins automatically gain support for the new metadata: AVIF, IFF, HEIF, JXL, JXR, PSD and TGA (V2E).

The new metadata added with this patch is usually saved by smartphones (e.g. iPhone or Google Pixel).
2026-04-26 06:08:25 +02:00
Mirco Miranda
51db11eefc JXR: fix Use-of-uninitialized-value 2026-04-23 14:44:45 +02:00
Mirco Miranda
bc398382ac EXR: fix Null-dereference READ in OpenEXR 2026-04-23 11:42:16 +02:00
Mirco Miranda
d5e5012cfb HDR: fix incorrect use of s.atEnd() 2026-04-20 12:49:35 +00:00
Mirco Miranda
1b3f32a332 JXR: minimal support for multichannel 3 and 4 2026-04-20 14:13:10 +02:00
Mirco Miranda
7cf60da031 JXR: fix memory leaks 2026-04-19 20:37:27 +02:00
Mirco Miranda
d160f268e7 Improved documentation for DDS, JP2 and PSD formats 2026-04-18 08:32:32 +00:00
Mirco Miranda
276338199a EXR: fix incorrect loading of EXR files saved by Photoshop 2026 2026-04-16 15:34:33 +02:00
Mirco Miranda
742b5097f6 Fix HOST Computer metadata 2026-04-14 10:47:54 +02:00
Mirco Miranda
2d2ee68cc0 Add more info about unsecure JXR plugin 2026-04-13 12:45:09 +02:00
Nicolas Fella
d15c3c679d Update version to 6.26.0 2026-04-03 19:49:32 +02:00
Nicolas Fella
eb896efea1 Update dependency version to 6.25.0 2026-04-03 18:34:25 +02:00
Mirco Miranda
142ec14c81 TIM: PlayStation graphics read only support 2026-03-24 08:38:08 +01:00
Albert Astals Cid
38b8b70304 Remove ifdef now that we depend on Qt >= 6.9.0 2026-03-23 22:57:17 +01:00
Albert Astals Cid
e28c48cfeb GIT_SILENT Upgrade CMake version requirement to 3.29.
See https://community.kde.org/Frameworks/Policies
2026-03-23 19:30:35 +01:00
Albert Astals Cid
3c08226aec GIT_SILENT Upgrade Qt6 version requirement to 6.9.0.
See https://community.kde.org/Frameworks/Policies
2026-03-23 19:13:47 +01:00
Mirco Miranda
7c86ccaefb IFF: fix Integer-overflow in IDATChunk::strideSize 2026-03-23 12:05:01 +01:00
Nicolas Fella
ec0610d5b0 Update version to 6.25.0 2026-03-07 22:30:10 +01:00
Nicolas Fella
ae279c55f4 Update dependency version to 6.24.0 2026-03-07 21:18:31 +01:00
Mirco Miranda
836e0a53bb JP2: fix possible Undefined-shift 2026-03-04 08:15:48 +01:00
Mirco Miranda
5eb09116b0 IFF: fix buffer read overflow 2026-02-24 08:57:45 +01:00
Mirco Miranda
92368ca58f Fix Heap-buffer-overflow WRITE 2026-02-22 18:27:34 +00:00
Mirco Miranda
a91c7ef72f Fixed excessively frequent warning messages 2026-02-22 14:56:21 +01:00
Daniel Novomeský
ea2a4aafab ossfuzz: update aom, libavif, openjpeg 2026-02-19 11:31:12 +01:00
Mirco Miranda
c254875780 ANI: fix possible QByteArray allocation exception 2026-02-16 10:12:05 +01:00
Daniel Novomeský
f3de2e77c1 jxl: adjust metadata size limits
Previously there was no limit for uncompressed metadata,
but when compressed metadata required buffer larger than 4MB,
image decoding stopped.

Now the plugin discards/skips metadata boxes larger than 8MB
without stopping image decoding.
If size of compressed box is above 8MB,
we do not attempt to decompress it.
File with compressed metadata could be rejected only in rare cases
when the decompression buffer grows above 32MB (four times 8M).
2026-02-13 15:44:39 +01:00
Mirco Miranda
1ef779f370 RGB: fix a possible exception on the new 2026-02-12 13:42:58 +01:00
Laurent Montel
169a874cba GIT_SILENT: Bump kf ecm_set_disabled_deprecation_versions. Make sure that it compiles fine without kf 6.23 deprecated methods 2026-02-10 07:52:45 +01:00
Mirco Miranda
ebf77ccdf5 TGA: fix Undefined-shift 2026-02-09 23:02:06 +00:00
Mirco Miranda
359cb039d2 PSD: improve conversion sanity checks 2026-02-09 22:50:51 +00:00
Mirco Miranda
f4b91d8a54 IFF: fix compilation warnings 2026-02-09 22:46:04 +00:00
Mirco Miranda
263b5a88e2 ANI: check for array allocation size 2026-02-09 08:41:40 +01:00
Nicolas Fella
8d07f7db1b Update version to 6.24.0 2026-02-06 13:31:01 +01:00
Nicolas Fella
1c2210c100 Update dependency version to 6.23.0 2026-02-06 13:01:56 +01:00
Azhar Momin
b7b438f903 Fix oss-fuzz AFL build (again) 2026-02-03 14:35:56 +05:30
Azhar Momin
336b8906aa Fix OSS-Fuzz AFL builds 2026-02-02 23:05:09 +00:00
Kai Uwe Broulik
2c8a1ad6ff GIT_SILENT anitest: Fix typo
text -> test
2026-01-30 17:36:12 +01:00
Mirco Miranda
e0f1ba640a IFF: add uncompressed RGFX support 2026-01-27 13:58:01 +01:00
Mirco Miranda
32773e5f0c IFF: add CD-i Rle7 support 2026-01-20 21:17:08 +00:00
Mirco Miranda
2410e45614 Decode Atari ST VDAT chunks 2026-01-20 10:20:32 +01:00
Mirco Miranda
99e4223393 IFF: add support for CD-i YUVS chunk (and minor code improvements) 2026-01-15 16:53:50 +01:00
Mirco Miranda
8224c0099d Add support for CD-I IFF images 2026-01-14 16:53:19 +01:00
Daniel Novomeský
8d7fb2c3fd Add JXL testfile which previously triggered crash 2026-01-11 15:25:03 +01:00
Daniel Novomeský
3353809906 jxl: fix crash on lossy gray images
There was a rare crash during decoding of some lossy gray images.
Problem was reported in https://github.com/libjxl/libjxl/issues/4549
This is a workaround which avoids JxlDecoderSetCms call.
2026-01-11 15:03:06 +01:00
Daniel Novomeský
abf4d32858 Add gray AVIF files with various transfer functions 2026-01-10 20:41:52 +00:00
Daniel Novomeský
6b1c52c55c avif: Improved color profiles support 2026-01-10 20:41:52 +00:00
Albert Astals Cid
e644ab997f GIT_SILENT Upgrade CMake version requirement to 3.27.
See https://community.kde.org/Frameworks/Policies
2026-01-10 00:13:59 +01:00
Nicolas Fella
1fb3363e7b Update version to 6.23.0 2026-01-02 18:59:54 +01:00
Nicolas Fella
19df8b03a8 Update dependency version to 6.22.0 2026-01-02 18:32:32 +01:00
Daniel Novomeský
3f1ee9f9d1 Add YCgCo-Re AVIF test
Disable AVIF read tests for libavif below 1.2.0,
where YCgCo-Re decoding is expected to fail.
2025-12-27 21:35:25 +01:00
Daniel Novomeský
8c9a7e88e5 avif: YCgCo-Re decoding fix 2025-12-26 00:47:46 +00:00
Mirco Miranda
30308f3541 HEIF tests skipped using kde-ci.yml 2025-12-24 17:56:44 +01:00
Laurent Montel
af4597f796 GIT_SILENT: Bump qt ecm_set_disabled_deprecation_versions. Make sure that it compiles fine without qt 6.11 deprecated methods 2025-12-24 06:57:32 +01:00
Laurent Montel
b8b9849268 GIT_SILENT: Bump kf ecm_set_disabled_deprecation_versions. Make sure that it compiles fine without kf 6.21 deprecated methods 2025-12-18 22:07:02 +01:00
Mirco Miranda
db1cb8de1f Add allocation limit test (0/256 MiB) 2025-12-09 15:55:35 +01:00
Albert Astals Cid
956b86c4de allocationLimit = 0 means no limit 2025-12-09 01:06:10 +01:00
Nicolas Fella
282c277204 Update version to 6.22.0 2025-12-05 15:14:46 +01:00
Nicolas Fella
237f25fd20 Update dependency version to 6.21.0 2025-12-05 14:07:10 +01:00
Kunda Ki
5140567853 Fix various typos in the codebase
Fixes mostly source documentation, comments, and some user-facing typos.
Found via `codespell -q 3 -S "*.eps,*.raw" -L ahd,siz,widthn`
2025-12-05 10:41:17 +00:00
Mirco Miranda
72a809bcfd XCF: initialize tiles and buffers 2025-11-22 12:45:46 +01:00
Mirco Miranda
731ac375bb HDR: fix uninitialized buffer 2025-11-22 09:41:25 +01:00
Mirco Miranda
8061500b79 PSD: add support for GrayA (8/16/32 bit) and Gray 32 bit 2025-11-21 06:24:53 +01:00
Mirco Miranda
472ff92b96 PSD: fix use of uninitialized tmp image 2025-11-21 01:21:18 +00:00
Mirco Miranda
847510f109 EXR: fix of uninitialized RGB read buffer 2025-11-20 07:36:49 +01:00
Mirco Miranda
a8ad7be1cd RGB: fix Use-of-uninitialized-value 2025-11-19 11:05:36 +00:00
Mirco Miranda
7202e77c74 HDR: readHeader() sanity checks 2025-11-18 16:56:46 +01:00
Mirco Miranda
379c3c1738 PSD: return error if a complete line is read/decompressed 2025-11-17 14:38:07 +01:00
Nicolas Fella
6519d2930e Update version to 6.21.0 2025-11-14 18:25:25 +01:00
109 changed files with 3777 additions and 322 deletions

View File

@@ -7,5 +7,5 @@ Dependencies:
Options:
test-before-installing: True
require-passing-tests-on: ['Linux', 'FreeBSD', 'Windows']
cmake-options: "-DKIMAGEFORMATS_DDS=ON -DKIMAGEFORMATS_JXR=ON -DKIMAGEFORMATS_HEIF=ON"
cmake-options: "-DKIMAGEFORMATS_WITH_KNOWN_CRASHES_JXR=ON -DKIMAGEFORMATS_HEIF=ON -DKIMAGEFORMATS_HEIF_TEST:STRING=OFF -DKIMAGEFORMATS_HEJ2_TEST:STRING=OFF -DKIMAGEFORMATS_AVCI_TEST:STRING=OFF"
per-test-timeout: 90

View File

@@ -1,11 +1,11 @@
cmake_minimum_required(VERSION 3.16)
cmake_minimum_required(VERSION 3.29)
set(KF_VERSION "6.20.0") # handled by release scripts
set(KF_DEP_VERSION "6.20.0") # handled by release scripts
set(KF_VERSION "6.26.0") # handled by release scripts
set(KF_DEP_VERSION "6.26.0") # handled by release scripts
project(KImageFormats VERSION ${KF_VERSION})
include(FeatureSummary)
find_package(ECM 6.20.0 NO_MODULE)
find_package(ECM 6.26.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)
@@ -21,7 +21,7 @@ include(ECMDeprecationSettings)
include(CheckIncludeFiles)
include(FindPkgConfig)
set(REQUIRED_QT_VERSION 6.8.0)
set(REQUIRED_QT_VERSION 6.9.0)
find_package(Qt6Gui ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE)
find_package(KF6Archive ${KF_DEP_VERSION})
@@ -65,6 +65,12 @@ set_package_properties(libavif PROPERTIES
option(KIMAGEFORMATS_DDS "Enable plugin for DDS format" ON)
option(KIMAGEFORMATS_HEIF "Enable plugin for HEIF format" OFF)
set(KIMAGEFORMATS_HEIF_TEST "ALL" CACHE STRING "Enable HEIF tests: OFF, READ_ONLY, ALL")
set_property(CACHE KIMAGEFORMATS_HEIF_TEST PROPERTY STRINGS "OFF" "READ_ONLY" "ALL")
set(KIMAGEFORMATS_HEJ2_TEST "ALL" CACHE STRING "Enable HEJ2 tests: OFF, READ_ONLY, ALL")
set_property(CACHE KIMAGEFORMATS_HEJ2_TEST PROPERTY STRINGS "OFF" "READ_ONLY" "ALL")
set(KIMAGEFORMATS_AVCI_TEST "ALL" CACHE STRING "Enable AVCI tests: OFF, ALL")
set_property(CACHE KIMAGEFORMATS_AVCI_TEST PROPERTY STRINGS "OFF" "ALL")
if(KIMAGEFORMATS_HEIF)
pkg_check_modules(LibHeif IMPORTED_TARGET libheif>=1.10.0)
endif()
@@ -91,16 +97,17 @@ set_package_properties(LibRaw PROPERTIES
PURPOSE "Required for the QImage plugin for RAW images"
)
# JXR plugin disabled by default due to security issues
option(KIMAGEFORMATS_JXR "Enable plugin for JPEG XR format" OFF)
if(KIMAGEFORMATS_JXR)
# JXR plugin disabled by default due to security issues.
# You should not enable it unless you know what you are doing.
option(KIMAGEFORMATS_WITH_KNOWN_CRASHES_JXR "Enable plugin for JPEG XR format" OFF)
if(KIMAGEFORMATS_WITH_KNOWN_CRASHES_JXR)
find_package(LibJXR)
endif()
add_feature_info(LibJXR LibJXR_FOUND "required for the QImage plugin for JPEG XR images")
ecm_set_disabled_deprecation_versions(
QT 6.10.0
KF 6.19.0
QT 6.11.0
KF 6.23.0
)
add_subdirectory(src)

View File

@@ -21,6 +21,7 @@ The following image formats have read-only support:
- Krita (kra)
- OpenRaster (ora)
- Pixar raster (pxr)
- PlayStation graphics (tim)
- Portable FloatMap/HalfMap (pfm, phm)
- Photoshop documents (psd, psb, pdd, psdt)
- Radiance HDR (hdr)
@@ -154,8 +155,31 @@ About the image:
- `Owner`: Name of the owner of the image.
- `Software`: Name and version number of the software package(s) used to
create the image.
- `Speed`: Floating-point number indicating the speed of GPS receiver
movement in Km/h (e.g. 30.2).
- `Title`: The title of the image.
About the shot:
- `DigitalZoomRatio`: Floating-point number indicating the digital zoom ratio
when the image was shot.
- `ExposureMode`: Integer number indicating the exposure mode set when the
image was shot as reported in the EXIF specifications.
- `ExposureProgram`: Integer number indicating the class of the program used
by the camera to set exposure when the picture is taken as reported in the
EXIF specifications.
- `ExposureTime`: Floating-point number indicating the exposure time,
given in seconds (s).
- `Flash`: Integer number indicating the status of flash when the image
was shot as reported in the EXIF specifications.
- `FNumber`: Floating-point number indicating the F number.
- `FocalLength`: Floating-point number indicating the actual focal length
of the lens, in millimeters (mm).
- `ISOSpeedRatings`: Integer number indicating the sensitivity of the camera
or input device when the image was shot as reported in the EXIF
specifications.
- `WhiteBalance`: Integer number indicating the white balance mode set when
the image was shot as reported in the EXIF specifications.
About the camera:
- `Manufacturer`: The manufacturer of the recording equipment.
- `Model`: The model name or model number of the recording equipment.
@@ -250,6 +274,7 @@ limit depends on the format encoding).
- RAW: 65,535 x 65,535 pixels
- RGB: 65,535 x 65,535 pixels
- SCT: 300,000 x 300,000 pixels
- TIM: 65,535 x 65,535 pixels
- TGA: 65,535 x 65,535 pixels
- XCF: 300,000 x 300,000 pixels
@@ -290,9 +315,14 @@ in your cmake options.**
The following defines can be defined in cmake to modify the behavior of the
plugin:
- `DDS_DISABLE_STRIDE_ALIGNMENT`: disable the stride aligment based on DDS
- `DDS_DISABLE_STRIDE_ALIGNMENT`: disable the stride alignment based on DDS
pitch: it is known that some writers do not set it correctly.
When writing, it is possible to set which pixel format to use by setting the
subtypes. The default is `Automatic` which chooses the most appropriate format
based on the image. For a complete list of subformats, please use the
appropriate [`QImageWriter`](https://doc.qt.io/qt-6/qimagewriter.html) APIs.
### The HEIF plugin
**This plugin is disabled by default. It can be enabled by settings
@@ -303,6 +333,15 @@ distributions. In particular, it is necessary that the HEIF library has
support for HEVC codec. If HEVC codec is not available the plugin
will compile but will fail the tests.
**If you are interested in compiling the plugin without running the tests,
also use the following string options:**
- `KIMAGEFORMATS_HEIF_TEST` to change the behaviour of HEIF tests. Set to
`"OFF"` (no test at all) or `"READ_ONLY"` (run read tests only).
- `KIMAGEFORMATS_HEJ2_TEST` to change the behaviour of HEJ2 tests. Set to
`"OFF"` (no test at all) or `"READ_ONLY"` (run read tests only)..
- `KIMAGEFORMATS_AVCI_TEST` to change the behaviour of AVCI tests. Set to
`"OFF"` (no test at all).
### The EXR plugin
The following defines can be defined in cmake to modify the behavior of the
@@ -313,6 +352,10 @@ plugin:
attribute named "xmp". Note that Gimp reads the "xmp" attribute and Darktable
writes it as well.
The plugin can set the following additional metadata:
- `EXRLayerName`: A string containing the name of the EXR layer used to decode
the image.
### The EPS plugin
The plugin uses `Ghostscript` to convert the raster image. When reading it
@@ -348,9 +391,16 @@ The plugin supports the following image data:
type 4.
- FORM PBM: PBM is a chunky version of IFF pictures. It supports 8-bit images
with color map only.
- FORM IMAG (Compact Disc-Interactive): It supports CLut4, CLut7, CLut8, Rle7
and DYuv formats.
- FORM RGFX: It supports uncompressed images only.
- FOR4 CIMG (Maya Image File Format): It supports 24/48-bit RGB and 32/64-bit
RGBA images.
> [!note]
> The plugin only supports the IFF, ILBM, and LBM file extensions. You'll
> need to rename files with different extensions to open them.
### The JP2 plugin
**This plugin can be disabled by setting `KIMAGEFORMATS_JP2` to `OFF`
@@ -361,6 +411,11 @@ JP2 plugin has the following limitations due to the lack of support by OpenJPEG:
- Image resolution is not supported.
- To write ICC profiles you need OpenJPEG V2.5.4 or higher
When writing, it is possible to set which format to use by setting the
following subtypes:
- `JP2` (default): Save data using the JP2 container.
- `J2K`: Save only the compressed codestream.
### The JXL plugin
**The current version of the plugin limits the image size to 256 megapixels
@@ -376,13 +431,18 @@ plugin:
### The JXR plugin
**This plugin is disabled by default. It can be enabled by settings
`KIMAGEFORMATS_JXR` to `ON` in your cmake options.**
`KIMAGEFORMATS_WITH_KNOWN_CRASHES_JXR` to `ON` in your cmake options.**
> [!caution]
> The plugin disabled by default due to security issues in [jxrlib](https://github.com/4creators/jxrlib):
> the upstream jxrlib is dead and there is no "hope" they will fix the issues.
> **You should not enable it unless you know what you are doing.**
The following defines can be defined in cmake to modify the behavior of the
plugin:
- `JXR_DENY_FLOAT_IMAGE`: disables the use of float images and consequently
any HDR data will be lost.
- `JXR_DISABLE_DEPTH_CONVERSION`: remove the neeeds of additional memory by
- `JXR_DISABLE_DEPTH_CONVERSION`: remove the needs of additional memory by
disabling the conversion between different color depths (e.g. RGBA64bpp to
RGBA32bpp) at the cost of reduced compatibility.
- `JXR_DISABLE_BGRA_HACK`: Windows displays and opens JXR files correctly out
@@ -414,6 +474,8 @@ PSD support has the following limitations:
- Multichannel images are treated as CMYK if they have 2 or more channels.
- Multichannel images are treated as Grayscale if they have 1 channel.
- Duotone images are treated as grayscale images.
- Grayscale images with alpha channel or at 32 bit depth are converted to
RGBA due to the lack of the appropriate Qt grayscale container.
- Extra channels other than alpha are discarded.
The following defines can be defined in cmake to modify the behavior of the
@@ -423,6 +485,13 @@ plugin:
- `PSD_NATIVE_CMYK_SUPPORT_DISABLED`: disable native support for CMYK images
when compiled with Qt 6.8+
The plugin can set the following additional metadata:
- `PSDDuotoneOptions`: Byte array in hexadecimal format of color data of the
duotone specification (the format of which is not documented). From the PSD
specification: *"Other applications that read Photoshop files can treat a
duotone image as a gray image, and just preserve the contents of the duotone
information when reading and writing the file."*
### The RAW plugin
Loading RAW images always requires a conversion. To allow the user to

View File

@@ -86,6 +86,7 @@ kimageformats_read_tests(
ras
rgb
sct
tim
tga
)
@@ -102,36 +103,52 @@ if (KF6Archive_FOUND)
endif()
if (TARGET avif)
kimageformats_read_tests(
avif
)
if(${libavif_VERSION} VERSION_GREATER_EQUAL "1.2.0")
kimageformats_read_tests(
avif
)
else()
message(WARNING "libavif ${libavif_VERSION} doesn't support decoding new AVIF files!\n"
"AVIF read tests are disabled, consider updating libavif.")
endif()
kimageformats_write_tests(
avif-nodatacheck-lossless
)
endif()
if (LibHeif_FOUND)
kimageformats_read_tests(
heif
)
# because the plug-ins use RGB->YUV conversion which sometimes results in 1 value difference.
kimageformats_write_tests(FUZZ 1
heif-nodatacheck-lossless
)
if (LibHeif_VERSION VERSION_GREATER_EQUAL "1.17.0")
kimageformats_read_tests(FUZZ 1
hej2
if (KIMAGEFORMATS_HEIF_TEST STREQUAL "ALL" OR KIMAGEFORMATS_HEIF_TEST STREQUAL "READ_ONLY")
kimageformats_read_tests(
heif
)
endif()
if (KIMAGEFORMATS_HEIF_TEST STREQUAL "ALL")
# because the plug-ins use RGB->YUV conversion which sometimes results in 1 value difference.
kimageformats_write_tests(FUZZ 1
hej2-nodatacheck-lossless
heif-nodatacheck-lossless
)
endif()
if (LibHeif_VERSION VERSION_GREATER_EQUAL "1.17.0")
if (KIMAGEFORMATS_HEJ2_TEST STREQUAL "ALL" OR KIMAGEFORMATS_HEJ2_TEST STREQUAL "READ_ONLY")
kimageformats_read_tests(FUZZ 1
hej2
)
endif()
if (KIMAGEFORMATS_HEJ2_TEST STREQUAL "ALL")
kimageformats_write_tests(FUZZ 1
hej2-nodatacheck-lossless
)
endif()
endif()
if (LibHeif_VERSION VERSION_GREATER_EQUAL "1.19.6")
kimageformats_read_tests(FUZZ 4
avci
)
if (KIMAGEFORMATS_AVCI_TEST STREQUAL "ALL")
kimageformats_read_tests(FUZZ 4
avci
)
endif()
endif()
endif()

View File

@@ -48,7 +48,7 @@ Depending on the format, you can specify the following additional options.
- `--help`: Displays help on commandline options.
- `--fuzz <max>`: The fuzziness. Used to add some deviation in ARGB data
(nornally used on lossy codec).
(normally used on lossy codec).
- `--perceptive-fuzz`: Used to scale dynamically the fuzziness based on
the alpha channel value. This is useful on images with pre-multiplied and
small alphas. Qt can use different roundings based on optimizations resulting

View File

@@ -48,7 +48,7 @@ private Q_SLOTS:
QCOMPARE(reader.text(QStringLiteral("Author")), QStringLiteral("KDE Community"));
}
void textRead()
void testRead()
{
QImageReader reader(QFINDTESTDATA("ani/test.ani"));
QVERIFY(reader.canRead());

View File

@@ -18,6 +18,11 @@
# limitations under the License.
#
################################################################################
LDFLAGS=""
if [[ $FUZZING_ENGINE == "afl" ]]; then
LDFLAGS="-fuse-ld=lld"
fi
export LDFLAGS
# build zstd
cd $SRC/zstd
@@ -175,6 +180,7 @@ HANDLER_TYPES="ANIHandler ani
RAWHandler raw
RGBHandler rgb
ScitexHandler sct
TIMHandler tim
TGAHandler tga
XCFHandler xcf"
@@ -185,7 +191,7 @@ echo "$HANDLER_TYPES" | while read class format; do
/usr/libexec/moc $SRC/kimageformats/src/imageformats/$format.cpp -o $format.moc
header=`ls $SRC/kimageformats/src/imageformats/$format*.h`
/usr/libexec/moc $header -o moc_`basename $header .h`.cpp
$CXX $CXXFLAGS -fPIC -DHANDLER=$class -std=c++17 autotests/ossfuzz/kimgio_fuzzer.cc $SRC/kimageformats/src/imageformats/$format.cpp $SRC/kimageformats/src/imageformats/scanlineconverter.cpp $SRC/kimageformats/src/imageformats/microexif.cpp $SRC/kimageformats/src/imageformats/chunks.cpp -o $OUT/$fuzz_target_name -DJXL_STATIC_DEFINE -DJXL_THREADS_STATIC_DEFINE -DJXL_CMS_STATIC_DEFINE -DINITGUID -I $SRC/kimageformats/src/imageformats/ -I $SRC/libavif/include/ -I $SRC/libjxl/build/lib/include/ -I $SRC/libjxl/lib/include/ -I /usr/local/include/OpenEXR/ -I /usr/local/include/KF6/KArchive/ -I /usr/local/include/openjpeg-2.5 -I /usr/local/include/Imath -I $SRC/jxrlib/common/include -I $SRC/jxrlib/jxrgluelib -I $SRC/jxrlib/image/sys -I /usr/include/QtCore/ -I /usr/include/QtGui/ -I . $SRC/libavif/build/libavif.a /usr/local/lib/libheif.a /usr/local/lib/libde265.a /usr/local/lib/libopenh264.a $SRC/aom/build.libavif/libaom.a $SRC/libjxl/build/lib/libjxl_threads.a $SRC/libjxl/build/lib/libjxl.a $SRC/libjxl/build/lib/libjxl_cms.a $SRC/libjxl/build/third_party/highway/libhwy.a $SRC/libjxl/build/third_party/brotli/libbrotlidec.a $SRC/libjxl/build/third_party/brotli/libbrotlienc.a $SRC/libjxl/build/third_party/brotli/libbrotlicommon.a -lQt6Gui -lQt6Core -lQt6BundledLibpng -lQt6BundledHarfbuzz -lm -lQt6BundledPcre2 -ldl -lpthread $LIB_FUZZING_ENGINE /usr/local/lib/libz.a -lKF6Archive /usr/local/lib/libz.a /usr/local/lib/libraw.a /usr/local/lib/libOpenEXR-3_3.a /usr/local/lib/libIex-3_3.a /usr/local/lib/libImath-3_1.a /usr/local/lib/libIlmThread-3_3.a /usr/local/lib/libOpenEXRCore-3_3.a /usr/local/lib/libOpenEXRUtil-3_3.a /usr/local/lib/libopenjp2.a /usr/local/lib/libzstd.a $SRC/jxrlib/build/libjxrglue.a $SRC/jxrlib/build/libjpegxr.a -llzma /usr/local/lib/libbz2.a -lclang_rt.builtins
$CXX $CXXFLAGS $LDFLAGS -fPIC -DHANDLER=$class -std=c++17 autotests/ossfuzz/kimgio_fuzzer.cc $SRC/kimageformats/src/imageformats/$format.cpp $SRC/kimageformats/src/imageformats/scanlineconverter.cpp $SRC/kimageformats/src/imageformats/microexif.cpp $SRC/kimageformats/src/imageformats/chunks.cpp -o $OUT/$fuzz_target_name -DJXL_STATIC_DEFINE -DJXL_THREADS_STATIC_DEFINE -DJXL_CMS_STATIC_DEFINE -DINITGUID -I $SRC/kimageformats/src/imageformats/ -I $SRC/libavif/include/ -I $SRC/libjxl/build/lib/include/ -I $SRC/libjxl/lib/include/ -I /usr/local/include/OpenEXR/ -I /usr/local/include/KF6/KArchive/ -I /usr/local/include/openjpeg-2.5 -I /usr/local/include/Imath -I $SRC/jxrlib/common/include -I $SRC/jxrlib/jxrgluelib -I $SRC/jxrlib/image/sys -I /usr/include/QtCore/ -I /usr/include/QtGui/ -I . $SRC/libavif/build/libavif.a /usr/local/lib/libheif.a /usr/local/lib/libde265.a /usr/local/lib/libopenh264.a $SRC/aom/build.libavif/libaom.a $SRC/libjxl/build/lib/libjxl_threads.a $SRC/libjxl/build/lib/libjxl.a $SRC/libjxl/build/lib/libjxl_cms.a $SRC/libjxl/build/third_party/highway/libhwy.a $SRC/libjxl/build/third_party/brotli/libbrotlidec.a $SRC/libjxl/build/third_party/brotli/libbrotlienc.a $SRC/libjxl/build/third_party/brotli/libbrotlicommon.a -lQt6Gui -lQt6Core -lQt6BundledLibpng -lQt6BundledHarfbuzz -lm -lQt6BundledPcre2 -ldl -lpthread $LIB_FUZZING_ENGINE /usr/local/lib/libz.a /usr/local/lib/x86_64-linux-gnu/libKF6Archive.a /usr/local/lib/libz.a /usr/local/lib/libraw.a /usr/local/lib/libOpenEXR-3_3.a /usr/local/lib/libIex-3_3.a /usr/local/lib/libImath-3_1.a /usr/local/lib/libIlmThread-3_3.a /usr/local/lib/libOpenEXRCore-3_3.a /usr/local/lib/libOpenEXRUtil-3_3.a /usr/local/lib/libopenjp2.a /usr/local/lib/libzstd.a $SRC/jxrlib/build/libjxrglue.a $SRC/jxrlib/build/libjpegxr.a /usr/local/lib/liblzma.a /usr/local/lib/libbz2.a -lclang_rt.builtins
# -lclang_rt.builtins in the previous line is a temporary workaround to avoid a linker error "undefined reference to __truncsfhf2". Investigate why this is needed here, but not anywhere else, and possibly remove it.

View File

@@ -23,7 +23,7 @@
Usage:
python infra/helper.py build_image kimageformats
python infra/helper.py build_fuzzers --sanitizer undefined|address|memory kimageformats
python infra/helper.py run_fuzzer kimageformats kimgio_[ani|avif|dds|exr|hdr|heif|iff|jp2|jxl|jxr|kra|ora|pcx|pfm|pic|psd|pxr|qoi|ras|raw|rgb|sct|tga|xcf]_fuzzer
python infra/helper.py run_fuzzer kimageformats kimgio_[ani|avif|dds|exr|hdr|heif|iff|jp2|jxl|jxr|kra|ora|pcx|pfm|pic|psd|pxr|qoi|ras|raw|rgb|sct|tim|tga|xcf]_fuzzer
*/
#include <QBuffer>
@@ -52,6 +52,7 @@
#include "raw_p.h"
#include "rgb_p.h"
#include "sct_p.h"
#include "tim_p.h"
#include "tga_p.h"
#include "xcf_p.h"

View File

@@ -33,10 +33,10 @@ git clone --depth 1 -b master https://invent.kde.org/frameworks/extra-cmake-modu
git clone --depth 1 --branch=dev git://code.qt.io/qt/qtbase.git
git clone --depth 1 --branch=dev git://code.qt.io/qt/qttools.git
git clone --depth 1 -b master https://invent.kde.org/frameworks/karchive.git
git clone --depth 1 -b v3.12.0 https://aomedia.googlesource.com/aom
git clone --depth 1 -b v1.2.1 https://github.com/AOMediaCodec/libavif.git
git clone --depth 1 -b v3.13.1 https://aomedia.googlesource.com/aom
git clone --depth 1 -b v1.3.0 https://github.com/AOMediaCodec/libavif.git
git clone --depth 1 https://github.com/strukturag/libde265.git
git clone --depth 1 -b v2.5.3 https://github.com/uclouvain/openjpeg.git
git clone --depth 1 -b v2.5.4 https://github.com/uclouvain/openjpeg.git
git clone --depth 1 https://github.com/strukturag/libheif.git
git clone --depth=1 --recursive --shallow-submodules https://github.com/libjxl/libjxl.git
git clone --depth 1 https://github.com/LibRaw/LibRaw

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 KiB

View File

@@ -0,0 +1,11 @@
[
{
"fileName" : "profile_gray.png",
"colorSpace" : {
"colorModel" : "Gray",
"primaries" : "Custom",
"transferFunction" : "SRgb",
"gamma" : 2.31
}
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 KiB

View File

@@ -0,0 +1,11 @@
[
{
"fileName" : "profile_gray_gamma22.png",
"colorSpace" : {
"colorModel" : "Gray",
"primaries" : "Custom",
"transferFunction" : "Gamma",
"gamma" : 2.2
}
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 KiB

View File

@@ -0,0 +1,11 @@
[
{
"fileName" : "profile_gray_gamma28.png",
"colorSpace" : {
"colorModel" : "Gray",
"primaries" : "Custom",
"transferFunction" : "Gamma",
"gamma" : 2.8
}
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

View File

@@ -0,0 +1,11 @@
[
{
"fileName" : "profile_gray_linear.png",
"colorSpace" : {
"colorModel" : "Gray",
"primaries" : "Custom",
"transferFunction" : "Linear",
"gamma" : 1
}
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 KiB

View File

@@ -0,0 +1,12 @@
[
{
"fileName" : "profile_gray_prophoto.png",
"colorSpace" : {
"description" : "grayscale D50 with ProPhoto TRC",
"colorModel" : "Gray",
"primaries" : "Custom",
"transferFunction" : "Custom",
"gamma" : 0
}
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 259 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

View File

@@ -0,0 +1,15 @@
[
{
"fileName" : "ps2026_testcard_rgb.png",
"colorSpace" : {
"description" : "sRGB build-in (Profilo RGB lineare)",
"primaries" : "SRgb",
"transferFunction" : "Linear",
"gamma" : 1
},
"resolution" : {
"dotsPerMeterX" : 3937,
"dotsPerMeterY" : 3937
}
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -2,7 +2,7 @@
{
"fileName" : "rgb-gimp.png",
"colorSpace" : {
"description" : "",
"description" : "Embedded RGB (linear)",
"primaries" : "Custom",
"transferFunction" : "Linear",
"gamma" : 1

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

View File

@@ -0,0 +1,9 @@
[
{
"fileName" : "cdi_dyuv_each.iff",
"resolution" : {
"dotsPerMeterX" : 3937,
"dotsPerMeterY" : 5249
}
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

View File

@@ -0,0 +1,37 @@
[
{
"fileName" : "sv5_rgba32_rgx.png",
"colorSpace" : {
"description" : "TIFF ICC Profile",
"primaries" : "SRgb",
"transferFunction" : "SRgb",
"gamma" : 0
},
"metadata" : [
{
"key" : "Author",
"value" : "KDE Project"
},
{
"key" : "Copyright",
"value" : "@2025 KDE Project"
},
{
"key" : "CreationDate",
"value" : "2025-01-14T10:34:51"
},
{
"key" : "Description",
"value" : "TV broadcast test image."
},
{
"key" : "Title",
"value" : "Test Card"
}
],
"resolution" : {
"dotsPerMeterX" : 2835,
"dotsPerMeterY" : 2835
}
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

View File

@@ -0,0 +1,14 @@
[
{
"fileName" : "gray_linear_lossy.png",
"fuzziness" : 1,
"description" : "Minimum fuzziness value to pass the test on all architectures.",
"colorSpace" : {
"description" : "Gra_D65_Rel_SRG",
"primaries" : "Custom",
"transferFunction" : "SRgb",
"gamma" : 0,
"colorModel" : "Gray"
}
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

View File

@@ -14,7 +14,7 @@
},
{
"key" : "Software" ,
"value" : "LIFE Pro 2.18.10"
"value" : "LIFE Pro 2.20.35"
},
{
"key" : "Altitude",
@@ -32,6 +32,10 @@
"key" : "Description",
"value" : "TV broadcast test image."
},
{
"key" : "HostComputer",
"value" : "Windows 11 Enterprise (25H2)"
},
{
"key" : "Latitude",
"value" : "44.6478"

View File

@@ -2,8 +2,8 @@
{
"fileName" : "32bit_grayscale.png",
"colorSpace" : {
"description" : "Linear Grayscale Profile",
"colorModel" : "Gray",
"description" : "RGB emulation of \"Linear Grayscale Profile\"",
"colorModel" : "Rgb",
"primaries" : "Custom",
"transferFunction" : "Linear",
"gamma" : 1

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

View File

@@ -0,0 +1,26 @@
[
{
"fileName" : "testcard_graya16.png",
"colorSpace" : {
"description" : "RGB emulation of \"Gray Gamma 2.2\"",
"colorModel" : "Rgb",
"primaries" : "SRgb",
"transferFunction" : "Gamma",
"gamma" : 2.19922
},
"metadata" : [
{
"key" : "ModificationDate",
"value" : "2025-11-17T07:27:47"
},
{
"key" : "Software" ,
"value" : "Adobe Photoshop 26.11 (Windows)"
}
],
"resolution" : {
"dotsPerMeterX" : 11811,
"dotsPerMeterY" : 11811
}
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

View File

@@ -0,0 +1,26 @@
[
{
"fileName" : "testcard_graya32.png",
"colorSpace" : {
"description" : "RGB emulation of \"Profilo scala di grigio lineare\"",
"colorModel" : "Rgb",
"primaries" : "Custom",
"transferFunction" : "Linear",
"gamma" : 1
},
"metadata" : [
{
"key" : "ModificationDate",
"value" : "2025-11-17T07:29:19"
},
{
"key" : "Software" ,
"value" : "Adobe Photoshop 26.11 (Windows)"
}
],
"resolution" : {
"dotsPerMeterX" : 11811,
"dotsPerMeterY" : 11811
}
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

View File

@@ -0,0 +1,28 @@
[
{
"fileName" : "testcard_graya8.png",
"fuzziness" : 1,
"perceptiveFuzziness" : true,
"colorSpace" : {
"description" : "RGB emulation of \"Gray Gamma 2.2\"",
"colorModel" : "Rgb",
"primaries" : "SRgb",
"transferFunction" : "Gamma",
"gamma" : 2.19922
},
"metadata" : [
{
"key" : "ModificationDate",
"value" : "2025-11-17T07:28:50"
},
{
"key" : "Software" ,
"value" : "Adobe Photoshop 26.11 (Windows)"
}
],
"resolution" : {
"dotsPerMeterX" : 11811,
"dotsPerMeterY" : 11811
}
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

View File

@@ -258,22 +258,26 @@ int main(int argc, char **argv)
});
QTextStream(stdout) << "QImageReader::supportedImageFormats: " << formatStrings.join(", ") << "\n";
// checks if the format has read capability
if (!formats.contains(format)) {
if (format == "avci" || format == "heif" || format == "hej2") {
QTextStream(stdout) << "WARNING : " << suffix << " is not supported with current libheif configuration!\n"
<< "********* "
<< "Finished basic read tests for " << suffix << " images *********\n";
return 0;
}
QTextStream(stdout) << "FAIL : current configuration is missing necessary decoder(s) for " << suffix << "!\n"
<< "********* "
<< "Finished basic read tests for " << suffix << " images *********\n";
return 1;
}
const QFileInfoList lstImgDir = imgdir.entryInfoList();
// Launch 2 runs for each test: first run on a random access device, second run on a sequential access device
for (int seq = 0; seq < 2; ++seq) {
// Launch 3 runs for each test:
// - first run on a random access device with allocation limit set to 256 MiB.
// - second run on a random access device with allocation limit set to 0 MiB.
// - third run on a sequential access device.
for (int run = 0; run < 3; ++run) {
QImageReader::setAllocationLimit(run == 1 ? 0 : 256);
bool seq = run == 2;
if (seq) {
QTextStream(stdout) << "* Run on SEQUENTIAL ACCESS device\n";
} else {
QTextStream(stdout) << "* Run on RANDOM ACCESS device\n";
QTextStream(stdout) << "* Run on RANDOM ACCESS device (allocation limit: " << QImageReader::allocationLimit() << " MiB)\n";
}
for (const QFileInfo &fi : lstImgDir) {
TemplateImage timg(fi);
@@ -338,12 +342,7 @@ int main(int argc, char **argv)
OptionTest optionTest;
if (!optionTest.store(&inputReader)) {
QTextStream(stdout) << "FAIL : " << fi.fileName() << ": error while reading options\n";
if (format == "heif") {
// libheif + ffmpeg decoder is unable to load all HEIF files.
++skipped;
} else {
++failed;
}
++failed;
continue;
}

View File

@@ -56,7 +56,7 @@ public:
/*!
* \brief skipSequentialDeviceTest
* \return tre it the sequential test should be skipped.
* \return True if the sequential test should be skipped.
*/
bool skipSequentialDeviceTest() const;

View File

@@ -13,14 +13,18 @@
"key" : "ModificationDate",
"value" : "2025-02-14T15:58:44+01:00"
},
{
"key" : "Software" ,
"value" : "Adobe Photoshop 26.2 (Windows)"
},
{
"key" : "Altitude",
"value" : "34"
},
{
"key" : "Title",
"value" : "A test"
},
{
"key" : "Software",
"value" : "KImageFormats write test"
},
{
"key" : "Author",
"value" : "KDE Project"
@@ -45,6 +49,10 @@
"key" : "LensModel",
"value" : "A1234"
},
{
"key" : "LensSerialNumber",
"value" : "S/N:1234567"
},
{
"key" : "Longitude",
"value" : "10.9254"
@@ -56,6 +64,50 @@
{
"key" : "Model",
"value" : "KImageFormats"
},
{
"key" : "SerialNumber",
"value" : "S/N:7654321"
},
{
"key" : "Speed",
"value" : "13.2"
},
{
"key" : "DigitalZoomRatio",
"value" : "3.4"
},
{
"key" : "ExposureMode",
"value" : "2"
},
{
"key" : "ExposureProgram",
"value" : "6"
},
{
"key" : "ExposureTime",
"value" : "0.004"
},
{
"key" : "Flash",
"value" : "16"
},
{
"key" : "FNumber",
"value" : "1.6"
},
{
"key" : "FocalLength",
"value" : "5.96"
},
{
"key" : "ISOSpeedRatings",
"value" : "50"
},
{
"key" : "WhiteBalance",
"value" : "1"
}
],
"resolution" : {

View File

@@ -41,6 +41,10 @@
"key" : "LensModel",
"value" : "A1234"
},
{
"key" : "LensSerialNumber",
"value" : "S/N:1234567"
},
{
"key" : "Longitude",
"value" : "10.9254"
@@ -52,6 +56,50 @@
{
"key" : "Model",
"value" : "KImageFormats"
},
{
"key" : "SerialNumber",
"value" : "S/N:7654321"
},
{
"key" : "Speed",
"value" : "13.2"
},
{
"key" : "DigitalZoomRatio",
"value" : "3.4"
},
{
"key" : "ExposureMode",
"value" : "2"
},
{
"key" : "ExposureProgram",
"value" : "6"
},
{
"key" : "ExposureTime",
"value" : "0.004"
},
{
"key" : "Flash",
"value" : "16"
},
{
"key" : "FNumber",
"value" : "1.6"
},
{
"key" : "FocalLength",
"value" : "5.96"
},
{
"key" : "ISOSpeedRatings",
"value" : "50"
},
{
"key" : "WhiteBalance",
"value" : "1"
}
],
"resolution" : {

View File

@@ -62,7 +62,7 @@ void setOptionalInfo(QImage &image, const QString &suffix)
// Set metadata
auto meta = obj.value("metadata").toArray();
for (auto jv : meta) {
for (auto &&jv : meta) {
auto obj = jv.toObject();
auto key = obj.value("key").toString();
auto val = obj.value("value").toString();
@@ -106,7 +106,7 @@ bool checkOptionalInfo(QImage &image, const QString &suffix)
// Test metadata
auto meta = obj.value("metadata").toArray();
for (auto jv : meta) {
for (auto &&jv : meta) {
auto obj = jv.toObject();
auto key = obj.value("key").toString();
auto val = obj.value("value").toString();
@@ -380,12 +380,7 @@ int formatTest(const QString &suffix, bool createTemplates)
QBuffer buffer(&ba);
auto writtenImage = QImageReader(&buffer, suffix.toLatin1()).read();
if (writtenImage.isNull()) {
if (suffix.toLatin1() == "heif") {
// libheif + ffmpeg decoder is unable to load all HEIF files.
++skipped;
} else {
++failed;
}
++failed;
QTextStream(stdout) << "FAIL : error while reading the image " << formatName << "\n";
continue;
}
@@ -639,23 +634,17 @@ int main(int argc, char **argv)
auto suffix = args.at(0);
// skip test if libheif configuration is obviously incomplete
// skip test if configuration is obviously incomplete
QByteArray format = suffix.toLatin1();
const QList<QByteArray> read_formats = QImageReader::supportedImageFormats();
const QList<QByteArray> write_formats = QImageWriter::supportedImageFormats();
if (!read_formats.contains(format)) {
if (format == "heif" || format == "hej2") {
QTextStream(stdout) << "WARNING : libheif configuration is missing necessary decoder(s)!\n";
return 0;
}
if (!read_formats.contains(format)) { // checks if the format has read capability
QTextStream(stdout) << "FAIL : current configuration is missing necessary decoder(s) for " << format << "!\n";
return 1;
}
if (!write_formats.contains(format)) {
if (format == "heif" || format == "hej2") {
QTextStream(stdout) << "WARNING : libheif configuration is missing necessary encoder(s)!\n";
return 0;
}
const QList<QByteArray> write_formats = QImageWriter::supportedImageFormats();
if (!write_formats.contains(format)) { // checks if the format has write capability
QTextStream(stdout) << "FAIL : libraries configuration is missing necessary encoder(s) for " << format << "!\n";
return 1;
}
// run test

View File

@@ -137,6 +137,10 @@ kimageformats_add_plugin(kimg_sct SOURCES sct.cpp)
##################################
kimageformats_add_plugin(kimg_tim SOURCES tim.cpp)
##################################
kimageformats_add_plugin(kimg_tga SOURCES tga.cpp microexif.cpp scanlineconverter.cpp)
##################################

View File

@@ -5,6 +5,7 @@
*/
#include "ani_p.h"
#include "util_p.h"
#include <QImage>
#include <QLoggingCategory>
@@ -101,7 +102,7 @@ bool ANIHandler::read(QImage *outImage)
}
const auto frameSize = *(reinterpret_cast<const quint32_le *>(frameSizeData.data()));
if (!frameSize) {
if (!frameSize || frameSize > quint32(kMaxQVectorSize)) {
return false;
}
@@ -417,6 +418,9 @@ bool ANIHandler::ensureScanned() const
// IART and INAM are technically inside LIST->INFO but "INFO" is supposedly optional
// so just handle those two attributes wherever we encounter them
} else if (chunkId == "INAM" || chunkId == "IART") {
if (chunkSize > kMaxQVectorSize) {
return false;
}
const QByteArray value = device()->read(chunkSize);
if (static_cast<quint32_le>(value.size()) != chunkSize) {

View File

@@ -265,9 +265,15 @@ bool QAVIFHandler::decode_one_frame()
}
}
uint32_t resultdepth = m_decoder->image->depth;
if (m_decoder->image->matrixCoefficients == 16 && m_decoder->image->yuvFormat == AVIF_PIXEL_FORMAT_YUV444 && resultdepth == 10) {
// 10-bit YCgCo-Re AVIF must be decoded to 8bit
resultdepth = 8;
}
QImage::Format resultformat;
if (m_decoder->image->depth > 8) {
if (resultdepth > 8) {
if (loadalpha) {
resultformat = QImage::Format_RGBA64;
} else {
@@ -382,6 +388,9 @@ bool QAVIFHandler::decode_one_frame()
case 2: /* AVIF_COLOR_PRIMARIES_UNSPECIFIED */
colorspace = QColorSpace(QColorSpace::Primaries::SRgb, q_trc, q_trc_gamma);
break;
case 9:
colorspace = QColorSpace(QColorSpace::Primaries::Bt2020, q_trc, q_trc_gamma);
break;
/* AVIF_COLOR_PRIMARIES_SMPTE432 */
case 12:
colorspace = QColorSpace(QColorSpace::Primaries::DciP3D65, q_trc, q_trc_gamma);
@@ -405,7 +414,7 @@ bool QAVIFHandler::decode_one_frame()
rgb.maxThreads = m_decoder->maxThreads;
#endif
if (m_decoder->image->depth > 8) {
if (resultdepth > 8) {
rgb.depth = 16;
rgb.format = AVIF_RGB_FORMAT_RGBA;
@@ -504,21 +513,12 @@ bool QAVIFHandler::decode_one_frame()
#else
switch (m_decoder->image->imir.axis) {
#endif
#if QT_VERSION < QT_VERSION_CHECK(6, 9, 0)
case 0: // top-to-bottom
result = result.mirrored(false, true);
break;
case 1: // left-to-right
result = result.mirrored(true, false);
break;
#else
case 0: // top-to-bottom
result = result.flipped(Qt::Vertical);
break;
case 1: // left-to-right
result = result.flipped(Qt::Horizontal);
break;
#endif
}
}
@@ -734,6 +734,15 @@ bool QAVIFHandler::write(const QImage &image)
/* AVIF_TRANSFER_CHARACTERISTICS_LINEAR */
avif->transferCharacteristics = (avifTransferCharacteristics)8;
break;
case QColorSpace::TransferFunction::Gamma:
if (qAbs(tmpgrayimage.colorSpace().gamma() - 2.2f) < 0.1f) {
/* AVIF_TRANSFER_CHARACTERISTICS_BT470M */
avif->transferCharacteristics = (avifTransferCharacteristics)4;
} else if (qAbs(tmpgrayimage.colorSpace().gamma() - 2.8f) < 0.1f) {
/* AVIF_TRANSFER_CHARACTERISTICS_BT470BG */
avif->transferCharacteristics = (avifTransferCharacteristics)5;
}
break;
case QColorSpace::TransferFunction::SRgb:
/* AVIF_TRANSFER_CHARACTERISTICS_SRGB */
avif->transferCharacteristics = (avifTransferCharacteristics)13;
@@ -748,6 +757,42 @@ bool QAVIFHandler::write(const QImage &image)
/* AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED */
break;
}
if (avif->transferCharacteristics == 2) { // in case TransferFunction was not identified yet
if (tmpgrayimage.colorSpace().colorModel() == QColorSpace::ColorModel::Gray && lossless) {
avif->colorPrimaries = (avifColorPrimaries)2;
avif->matrixCoefficients = (avifMatrixCoefficients)6;
QByteArray iccprofile_gray = tmpgrayimage.colorSpace().iccProfile();
if (iccprofile_gray.size() > 0) {
#if AVIF_VERSION >= 1000000
res = avifImageSetProfileICC(avif, reinterpret_cast<const uint8_t *>(iccprofile_gray.constData()), iccprofile_gray.size());
if (res != AVIF_RESULT_OK) {
qCWarning(LOG_AVIFPLUGIN, "ERROR in avifImageSetProfileICC: %s", avifResultToString(res));
return false;
}
#else
avifImageSetProfileICC(avif, reinterpret_cast<const uint8_t *>(iccprofile_gray.constData()), iccprofile_gray.size());
#endif
}
} else { // convert to grayscale with SRgb
tmpgrayimage.convertToColorSpace(QColorSpace(QPointF(0.3127f, 0.329f), QColorSpace::TransferFunction::SRgb), QImage::Format_Grayscale16);
switch (tmpgrayimage.format()) {
case QImage::Format_Grayscale8:
save_depth = 8;
break;
case QImage::Format_Grayscale16:
save_depth = 10;
avif->transferCharacteristics = (avifTransferCharacteristics)13;
break;
default:
qCWarning(LOG_AVIFPLUGIN, "Error saving Gray image");
return false;
break;
}
}
}
}
if (save_depth > 8) { // QImage::Format_Grayscale16
@@ -832,6 +877,10 @@ bool QAVIFHandler::write(const QImage &image)
/* AVIF_MATRIX_COEFFICIENTS_CHROMA_DERIVED_NCL */
matrix_to_save = (avifMatrixCoefficients)12;
break;
case QColorSpace::Primaries::Bt2020:
primaries_to_save = (avifColorPrimaries)9;
matrix_to_save = (avifMatrixCoefficients)12;
break;
default:
/* AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED */
primaries_to_save = (avifColorPrimaries)2;

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2025 Mirco Miranda <mircomir@outlook.com>
SPDX-FileCopyrightText: 2025-2026 Mirco Miranda <mircomir@outlook.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
@@ -10,6 +10,7 @@
* - https://wiki.amigaos.net/wiki/IFF_FORM_and_Chunk_Registry
* - https://www.fileformat.info/format/iff/egff.htm
* - https://download.autodesk.com/us/maya/2010help/index.html (Developer resources -> File formats -> Maya IFF)
* - https://aminet.net/package/dev/misc/IFF-RGFX
*/
#ifndef KIMG_CHUNKS_P_H
@@ -36,7 +37,7 @@ Q_DECLARE_LOGGING_CATEGORY(LOG_IFFPLUGIN)
#define LIST_CHUNK QByteArray("LIST")
#define PROP_CHUNK QByteArray("PROP")
// Main chuncks (Maya)
// Main chunks (Maya)
#define CAT4_CHUNK QByteArray("CAT4") // 4 byte alignment
#define FOR4_CHUNK QByteArray("FOR4")
#define LIS4_CHUNK QByteArray("LIS4")
@@ -55,7 +56,17 @@ Q_DECLARE_LOGGING_CATEGORY(LOG_IFFPLUGIN)
#define CMAP_CHUNK QByteArray("CMAP")
#define CMYK_CHUNK QByteArray("CMYK") // https://wiki.amigaos.net/wiki/ILBM_IFF_Interleaved_Bitmap#ILBM.CMYK
#define DPI__CHUNK QByteArray("DPI ")
#define IDAT_CHUNK QByteArray("IDAT")
#define IHDR_CHUNK QByteArray("IHDR")
#define IPAR_CHUNK QByteArray("IPAR")
#define PLTE_CHUNK QByteArray("PLTE")
#define RBOD_CHUNK QByteArray("RBOD")
#define RCOL_CHUNK QByteArray("RCOL")
#define RFLG_CHUNK QByteArray("RFLG")
#define RGHD_CHUNK QByteArray("RGHD")
#define RSCM_CHUNK QByteArray("RSCM")
#define XBMI_CHUNK QByteArray("XBMI")
#define YUVS_CHUNK QByteArray("YUVS")
// Different palette for scanline
#define BEAM_CHUNK QByteArray("BEAM")
@@ -79,14 +90,18 @@ Q_DECLARE_LOGGING_CATEGORY(LOG_IFFPLUGIN)
#define FVER_CHUNK QByteArray("FVER")
#define HIST_CHUNK QByteArray("HIST")
#define NAME_CHUNK QByteArray("NAME")
#define VDAT_CHUNK QByteArray("VDAT")
#define VERS_CHUNK QByteArray("VERS")
#define XMP0_CHUNK QByteArray("XMP0") // https://aminet.net/package/docs/misc/IFF-metadata
// FORM types
#define ACBM_FORM_TYPE QByteArray("ACBM")
#define ILBM_FORM_TYPE QByteArray("ILBM")
#define IMAG_FORM_TYPE QByteArray("IMAG")
#define PBM__FORM_TYPE QByteArray("PBM ")
#define RGB8_FORM_TYPE QByteArray("RGB8")
#define RGBN_FORM_TYPE QByteArray("RGBN")
#define RGFX_FORM_TYPE QByteArray("RGFX")
#define CIMG_FOR4_TYPE QByteArray("CIMG")
#define TBMP_FOR4_TYPE QByteArray("TBMP")
@@ -145,7 +160,7 @@ public:
/*!
* \brief bytes
* \return The size (in bytes) of the chunck data.
* \return The size (in bytes) of the chunk data.
*/
quint32 bytes() const;
@@ -424,7 +439,8 @@ public:
enum Compression {
Uncompressed = 0, /**< Image data are uncompressed. */
Rle = 1, /**< Image data are RLE compressed. */
RgbN8 = 4 /**< RGB8/RGBN compresson. */
Vdat = 2, /**< Image data are VDAT compressed. */
RgbN8 = 4 /**< Image data are RGB8/RGBN compressed. */
};
enum Masking {
None = 0, /**< Designates an opaque rectangular image. */
@@ -756,10 +772,11 @@ public:
/*!
* \brief readStride
* \param d The device.
* \param header The bitmap header.
* \param y The current scanline.
* \param header The bitmap header.
* \param camg The CAMG chunk (optional)
* \param cmap The CMAP chunk (optional)
* \param ipal The per-line palette chunk (optional)
* \param formType The type of the current form chunk.
* \return The scanline as requested for QImage.
* \warning Call resetStrideRead() once before this one.
@@ -776,8 +793,6 @@ public:
* \brief resetStrideRead
* Reset the stride read set the position at the beginning of the data and reset all buffers.
* \param d The device.
* \param header The BMHDChunk chunk (mandatory)
* \param camg The CAMG chunk (optional)
* \return True on success, otherwise false.
* \sa strideRead
* \note Must be called once before strideRead().
@@ -788,6 +803,7 @@ public:
* \brief safeModeId
* \param header The header.
* \param camg The CAMG chunk.
* \param cmap The CMAP chunk.
* \return The most likely ModeId if not explicitly specified.
*/
static CAMGChunk::ModeIds safeModeId(const BMHDChunk *header, const CAMGChunk *camg, const CMAPChunk *cmap = nullptr);
@@ -808,6 +824,8 @@ protected:
QByteArray rgbN(const QByteArray &planes, qint32 y, const BMHDChunk *header, const CAMGChunk *camg = nullptr, const CMAPChunk *cmap = nullptr, const IPALChunk *ipal = nullptr) const;
virtual bool innerReadStructure(QIODevice *d) override;
private:
mutable QByteArray _readBuffer;
};
@@ -870,7 +888,7 @@ public:
/*!
* \brief transformation
* \return The image transformation.
* \note The Default implentation returns the trasformation of EXIF chunk (if any).
* \note The Default implementation returns the transformation of EXIF chunk (if any).
*/
virtual QImageIOHandler::Transformation transformation() const;
@@ -921,6 +939,13 @@ public:
protected:
virtual bool innerReadStructure(QIODevice *d) override;
private:
QImage::Format iffFormat() const;
QImage::Format cdiFormat() const;
QImage::Format rgfxFormat() const;
};
@@ -1357,6 +1382,32 @@ protected:
virtual bool innerReadStructure(QIODevice *d) override;
};
/*!
* \brief The VDATChunk class
*/
class VDATChunk : public IFFChunk
{
public:
virtual ~VDATChunk() override;
VDATChunk();
VDATChunk(const VDATChunk& other) = default;
VDATChunk& operator =(const VDATChunk& other) = default;
virtual bool isValid() const override;
CHUNKID_DEFINE(VDAT_CHUNK)
const QByteArray &uncompressedData(const BMHDChunk *header) const;
protected:
virtual bool innerReadStructure(QIODevice *d) override;
private:
mutable QByteArray uncompressed;
};
/*!
* \brief The VERSChunk class
*/
@@ -1400,6 +1451,500 @@ protected:
virtual bool innerReadStructure(QIODevice *d) override;
};
/*!
* *** I-CD IFF CHUNKS ***
*/
/*!
* \brief The IHDRChunk class
* Image Header
*/
class IHDRChunk: public IFFChunk
{
public:
enum Model {
Invalid = 0, /**< Invalid model. */
Rgb888 = 1, /**< red, green, blue - 8 bits per color. */
Rgb555 = 2, /**< Green Book absolute RGB. */
DYuv = 3, /**< Green Book Delta YUV. */
CLut8 = 4, /**< Green Book 8 bit CLUT. */
CLut7 = 5, /**< Green Book 7 bit CLUT. */
CLut4 = 6, /**< Green Book 4 bit CLUT. */
CLut3 = 7, /**< Green Book 3 bit CLUT. */
Rle7 = 8, /**< Green Book runlength coded 7 bit CLUT. */
Rle3 = 9, /**< Green Book runlength coded 3 bit CLUT. */
PaletteOnly = 10 /**< color palette only. */
};
enum DYuvKind {
One = 0,
Each = 1
};
struct Yuv {
Yuv(quint8 y0 = 0, quint8 u0 = 0, quint8 v0 = 0) : y(y0), u(u0), v(v0) {}
quint8 y;
quint8 u;
quint8 v;
};
virtual ~IHDRChunk() override;
IHDRChunk();
IHDRChunk(const IHDRChunk& other) = default;
IHDRChunk& operator =(const IHDRChunk& other) = default;
virtual bool isValid() const override;
/*!
* \brief width
* \return Width of the bitmap in pixels.
*/
qint32 width() const;
/*!
* \brief height
* \return Height of the bitmap in pixels.
*/
qint32 height() const;
/*!
* \brief size
* \return Size in pixels.
*/
QSize size() const;
/*!
* \brief lineSize
* Physical width of image (number of bytes in each scan line, including any data required at
* the end of each scan line for padding [see description of each models IDAT chunk for padding
* rules]) This field is not used when model() = Rle7 or Rle3.
* When model() = Rgb555, this field defines the size of one scan line of the upper
* or lower portion of the pixel data, but not the size of them both together.
*/
qint32 lineSize() const;
/*!
* \brief model
* Image model (coding method)
*/
Model model() const;
/*!
* \brief depth
* Physical size of pixel (number of bits per pixel used in storing image data) When
* model() = Rle7 or Rle3, this value only represents the size of a
* single pixel; the size of a run of pixels is indeterminate.
*/
quint16 depth() const;
/*!
* \brief yuvKind
* if model() = DYuv, indicates whether there is one DYUV start value for all
* scan lines (in yuvStart()), or whether each scan line has its own start value in the
* YUVS chunk which follows.
*/
DYuvKind yuvKind() const;
/*!
* \brief yuvStart
* Start values for DYUV image if model() = DYuv and dYuvKind() = One
*/
Yuv yuvStart() const;
CHUNKID_DEFINE(IHDR_CHUNK)
protected:
virtual bool innerReadStructure(QIODevice *d) override;
};
/*!
* \brief The IHDRChunk class
*/
class IPARChunk: public IFFChunk
{
public:
struct Rgb {
Rgb(quint8 r0 = 0, quint8 g0 = 0, quint8 b0 = 0) : r(r0), g(g0), b(b0) {}
quint8 r;
quint8 g;
quint8 b;
};
virtual ~IPARChunk() override;
IPARChunk();
IPARChunk(const IPARChunk& other) = default;
IPARChunk& operator =(const IPARChunk& other) = default;
virtual bool isValid() const override;
/*!
* \brief xOffset
* X offset of origin in source image [0 < xOffset() < xPage()]
*/
qint32 xOffset() const;
/*!
* \brief yOffset
* \returnX offset of origin in source image [0 < yOffset() < yPage()]
*/
qint32 yOffset() const;
/*!
* \brief aspectRatio
* Aspect ratio of pixels in source image.
*/
double aspectRatio() const;
/*!
* \brief xPage
* X size of source image.
*/
qint32 xPage() const;
/*!
* \brief yPage
* Y size of source image.
*/
qint32 yPage() const;
/*!
* \brief xGrub
* X location of hot spot within image.
*/
qint32 xGrub() const;
/*!
* \brief yGrub
* Y location of hot spot within image.
*/
qint32 yGrub() const;
/*!
* \brief transparency
* Transparent color.
*/
Rgb transparency() const;
/*!
* \brief mask
* Mask color.
*/
Rgb mask() const;
CHUNKID_DEFINE(IPAR_CHUNK)
protected:
virtual bool innerReadStructure(QIODevice *d) override;
};
/*!
* \brief The PLTEChunk class
*/
class PLTEChunk : public CMAPChunk
{
public:
virtual ~PLTEChunk() override;
PLTEChunk();
PLTEChunk(const PLTEChunk& other) = default;
PLTEChunk& operator =(const PLTEChunk& other) = default;
virtual bool isValid() const override;
/*!
* \brief count
* \return The number of color in the palette.
*/
virtual qint32 count() const override;
CHUNKID_DEFINE(PLTE_CHUNK)
protected:
qint32 offset() const;
qint32 total() const;
virtual QList<QRgb> innerPalette() const override;
};
/*!
* \brief The YUVSChunk class
*/
class YUVSChunk : public IFFChunk
{
public:
virtual ~YUVSChunk() override;
YUVSChunk();
YUVSChunk(const YUVSChunk& other) = default;
YUVSChunk& operator =(const YUVSChunk& other) = default;
virtual bool isValid() const override;
qint32 count() const;
IHDRChunk::Yuv yuvStart(qint32 y) const;
CHUNKID_DEFINE(YUVS_CHUNK)
protected:
virtual bool innerReadStructure(QIODevice *d) override;
};
/*!
* \brief The IDATChunk class
*/
class IDATChunk : public IFFChunk
{
public:
virtual ~IDATChunk() override;
IDATChunk();
IDATChunk(const IDATChunk& other) = default;
IDATChunk& operator =(const IDATChunk& other) = default;
virtual bool isValid() const override;
CHUNKID_DEFINE(IDAT_CHUNK)
/*!
* \brief readStride
* \param d The device.
* \param y The current scanline.
* \param header The bitmap header.
* \param params The additional parameters (optional)
* \return The scanline as requested for QImage.
* \warning Call resetStrideRead() once before this one.
*/
QByteArray strideRead(QIODevice *d,
qint32 y,
const IHDRChunk *header,
const IPARChunk *params = nullptr,
const YUVSChunk *yuvs = nullptr) const;
/*!
* \brief resetStrideRead
* Reset the stride read set the position at the beginning of the data and reset all buffers.
* \param d The device.
* \return True on success, otherwise false.
* \sa strideRead
* \note Must be called once before strideRead().
*/
bool resetStrideRead(QIODevice *d) const;
protected:
quint32 strideSize(const IHDRChunk *header) const;
};
/*!
* *** RGFX IFF CHUNKS ***
*/
/*!
* \brief The RGHDChunk class
*/
class RGHDChunk : public IFFChunk
{
public:
enum Compression {
Uncompressed = 0,
Xpk = 1, /**< any XPK-packer */
Zip = 2 /**< libzip (LZ77/ZIP) compression */
};
enum BitmapType {
Planar8 = 0x0000, /**< unaligned planar 8 bit bitmap */
Chunky8 = 0x0001, /**< unaligned chunky 8 bit bitmap */
Rgb24 = 0x0002, /**< 3-byte 24 bit RGB triples */
Rgb32 = 0x0004, /**< 4-byte 32 bit ARGB quadruples */
Rgb15 = 0x0010, /**< 2-byte 15 bit RGB (x+3x5 bit integer) */
Rgb16 = 0x0020, /**< 2-byte 16 bit ARGB (1+3x5 bit integer) */
Rgb48 = 0x0040, /**< 6-byte 48 bit RGB (3x 16 bit integer) */
Rgb64 = 0x0080, /**< 8-byte 64 bit ARGB (4x 16 bit integer) */
Rgb96 = 0x0100, /**< 12-byte 96 bit RGB (3x 32 bit float) */
Rgb128 = 0x0200, /**< 16-byte 128 bit ARGB (4x 32 bit float) */
HasAlpha = (1 << 30), /**< set if A is meaningful */
HasInvAlpha = (1 << 31) /**< set if A is meaningful but inversed (A = 255 - alpha) */
};
Q_DECLARE_FLAGS(BitmapTypes, BitmapType)
virtual ~RGHDChunk() override;
RGHDChunk();
RGHDChunk(const RGHDChunk&) = default;
RGHDChunk& operator=(const RGHDChunk&) = default;
CHUNKID_DEFINE(RGHD_CHUNK)
virtual bool isValid() const override;
QSize size() const;
qint32 leftEdge() const;
qint32 topEdge() const;
qint32 width() const;
qint32 height() const;
qint32 pageWidth() const;
qint32 pageHeight() const;
quint32 depth() const;
quint32 pixelBits() const;
quint32 bytesPerLine() const;
Compression compression() const;
quint32 xAspect() const;
quint32 yAspect() const;
BitmapTypes bitmapType() const;
double aspectRatio() const;
protected:
virtual bool innerReadStructure(QIODevice *d) override;
};
/*!
* \brief The RCOLChunk class
*/
class RCOLChunk : public CMAPChunk
{
public:
virtual ~RCOLChunk() override;
RCOLChunk();
RCOLChunk(const RCOLChunk& other) = default;
RCOLChunk& operator =(const RCOLChunk& other) = default;
virtual bool isValid() const override;
virtual qint32 count() const override;
CHUNKID_DEFINE(RCOL_CHUNK)
protected:
virtual QList<QRgb> innerPalette() const override;
};
/*!
* \brief The RFLGChunk class
*/
class RFLGChunk : public IFFChunk
{
public:
enum class Flag : quint32 {
FromGray = 0x08, /**< created from 8/16 bit gray source so R==G==B */
From8Bit = 0x10, /**< created from 8 bit source, so (R,G,B)&0xFF00 == ... & 0x00FF */
From4Bit = 0x20, /**< created from 4 bit source, so (R,G,B)&0xF0 == ... & 0x0F */
From8BitAlpha = 0x40, /**< 16/32 bit alpha created from 8 bit alpha source */
From16BitAlpha = 0x80 /**< 32 bit alpha created from 16 bit alpha source */
};
Q_DECLARE_FLAGS(Flags, Flag)
virtual ~RFLGChunk() override;
RFLGChunk();
RFLGChunk(const RFLGChunk&) = default;
RFLGChunk& operator=(const RFLGChunk&) = default;
CHUNKID_DEFINE(RFLG_CHUNK)
virtual bool isValid() const override;
Flags flags() const;
protected:
virtual bool innerReadStructure(QIODevice *d) override;
};
/*!
* \brief The RSCMChunk class
*/
class RSCMChunk : public IFFChunk
{
public:
virtual ~RSCMChunk() override;
RSCMChunk();
RSCMChunk(const RSCMChunk&) = default;
RSCMChunk& operator=(const RSCMChunk&) = default;
CHUNKID_DEFINE(RSCM_CHUNK)
virtual bool isValid() const override;
/*!
* \brief viewMode Default screenmode
*
* Since HAM modes only can be identified by their ID (or DIPF) you have to make sure,
* that rcsm_ViewMode is OR'ed with HAM_KEY for these (same for EHB and EXTRAHALFBRITE_KEY).
*
* Specific RTG ViewModes will lose their meaning, as soon as graphics are transferred between
* different systems, which is why the two LocalVM entries are considered obsolete.
*
* Always set the obsolete entries to 0xFFFFFFFF and avoid interpreting them.
* \return default screenmode
*/
quint32 viewMode() const;
/*!
* \brief localVM0
* \obsolete obsolete local RTG
*/
quint32 localVM0() const;
/*!
* \brief localVM1
* \obsolete obsolete local RTG
*/
quint32 localVM1() const;
protected:
virtual bool innerReadStructure(QIODevice *d) override;
};
/*!
* \brief The RBODChunk class
*/
class RBODChunk : public IFFChunk
{
public:
virtual ~RBODChunk() override;
RBODChunk();
RBODChunk(const RBODChunk&) = default;
RBODChunk& operator=(const RBODChunk&) = default;
CHUNKID_DEFINE(RBOD_CHUNK)
virtual bool isValid() const override;
QByteArray strideRead(QIODevice *d,
qint32 y,
const RGHDChunk *header,
const RSCMChunk *rcsm = nullptr,
const RCOLChunk *rcol = nullptr) const;
bool resetStrideRead(QIODevice *d) const;
private:
QByteArray deinterleave(const QByteArray &planes, qint32 y, const RGHDChunk *header, const RSCMChunk *rcsm = nullptr, const RCOLChunk *rcol = nullptr) const;
quint32 strideSize(const RGHDChunk *header) const;
};
/*!
* *** UNDOCUMENTED CHUNKS ***

View File

@@ -20,7 +20,7 @@
#include <cmath>
#ifndef DDS_DISABLE_STRIDE_ALIGNMENT
// Disable the stride aligment based on DDS pitch: it is known that some writers do not set it correctly
// Disable the stride alignment based on DDS pitch: it is known that some writers do not set it correctly
// #define DDS_DISABLE_STRIDE_ALIGNMENT
#endif

View File

@@ -179,7 +179,7 @@ bool EPSHandler::read(QImage *image)
const QString gsExec = QStandardPaths::findExecutable(QStringLiteral("gs"));
if (gsExec.isEmpty()) {
qCWarning(EPSPLUGIN) << "Couldn't find gs exectuable (from GhostScript) in PATH.";
qCWarning(EPSPLUGIN) << "Couldn't find gs executable (from GhostScript) in PATH.";
return false;
}

View File

@@ -58,6 +58,7 @@
#include <ImathBox.h>
#include <ImfArray.h>
#include <ImfBoxAttribute.h>
#include <ImfOpaqueAttribute.h>
#include <ImfChannelListAttribute.h>
#include <ImfCompressionAttribute.h>
#include <ImfConvert.h>
@@ -227,25 +228,57 @@ static QImage::Format imageFormat(const Imf::RgbaInputFile &file)
/*!
* \brief viewList
* \param header
* \param header The image header.
* \return The list of available views.
* \note This plugin does not support compositing layers which are returned as single images.
*/
static QStringList viewList(const Imf::Header &h)
{
QStringList l;
if (auto views = h.findTypedAttribute<Imf::StringVectorAttribute>("multiView")) {
// Internally OpenEXR first checks if the multiView attribute is present:
// if present, I have no other layers.
for (auto &&v : views->value()) {
l << QString::fromStdString(v);
}
} else {
// Recent versions of Photoshop save images by setting the layer.
// Channels are named Layer 1.A, Layer 1.B, etc., so I have to set
// the layer or the images will appear black.
auto channels = h.channels();
for (auto i = channels.begin(); i != channels.end(); ++i) {
auto name = QString::fromLatin1(i.name(), -1);
auto idx = name.indexOf(QChar(u'.'));
if (idx > -1)
l << name.left(idx);
}
l.removeDuplicates();
}
return l;
}
static QString setLayerName(Imf::RgbaInputFile &file, qint32 imageNumber = -1)
{
// set the image to load
QString layerName;
auto &&header = file.header();
if (imageNumber > -1) {
auto views = viewList(header);
if (imageNumber < views.count())
layerName = views.at(imageNumber);
}
// set the layer name
if (!layerName.isEmpty()) {
file.setLayerName(layerName.toStdString());
}
return layerName;
}
#ifdef QT_DEBUG
static void printAttributes(const Imf::Header &h)
{
for (auto i = h.begin(); i != h.end(); ++i) {
qCDebug(LOG_EXRPLUGIN) << i.name();
qCDebug(LOG_EXRPLUGIN) << i.name() << i.attribute().typeName();
}
}
#endif
@@ -340,15 +373,29 @@ static void readColorSpace(const Imf::Header &header, QImage &image)
{
// final color operations
QColorSpace cs;
if (auto chroma = header.findTypedAttribute<Imf::ChromaticitiesAttribute>("chromaticities")) {
auto &&v = chroma->value();
cs = QColorSpace(QPointF(v.white.x, v.white.y),
QPointF(v.red.x, v.red.y),
QPointF(v.green.x, v.green.y),
QPointF(v.blue.x, v.blue.y),
QColorSpace::TransferFunction::Linear);
// Photoshop 2026 allow to save the ICC profile as "iccProfile" attribute
if (auto iccProfile = header.findTypedAttribute<Imf::OpaqueAttribute>("iccProfile")) {
auto &&v = iccProfile->data();
cs = QColorSpace::fromIccProfile(QByteArray::fromRawData(v, v.size()));
}
if (!cs.isValid()) {
// Creating the ICC profile from Chromaticities
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.setDescription(QStringLiteral("Embedded RGB (linear)"));
}
}
if (!cs.isValid()) {
// Use a linear profile
cs = QColorSpace(QColorSpace::SRgbLinear);
}
image.setColorSpace(cs);
@@ -377,12 +424,7 @@ bool EXRHandler::read(QImage *outImage)
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());
}
}
auto layerName = setLayerName(file, m_imageNumber);
// get image info
Imath::Box2i dw = file.dataWindow();
@@ -401,9 +443,13 @@ bool EXRHandler::read(QImage *outImage)
qCWarning(LOG_EXRPLUGIN) << "Failed to allocate image, invalid size?" << QSize(width, height);
return false;
}
if (!layerName.isEmpty()) {
image.setText(QStringLiteral("EXRLayerName"), layerName);
}
Imf::Array2D<Imf::Rgba> pixels;
pixels.resizeErase(EXR_LINES_PER_BLOCK, width);
std::memset(pixels[0], 0, sizeof(Imf::Rgba) * EXR_LINES_PER_BLOCK * width);
bool isRgba = image.hasAlphaChannel();
for (int y = 0, n = 0; y < height; y += n) {
@@ -687,12 +733,7 @@ QVariant EXRHandler::option(ImageOption option) const
try {
K_IStream istr(d);
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());
}
}
setLayerName(file, m_imageNumber);
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 &) {
@@ -712,6 +753,7 @@ QVariant EXRHandler::option(ImageOption option) const
try {
K_IStream istr(d);
Imf::RgbaInputFile file(istr);
setLayerName(file, m_imageNumber);
v = QVariant::fromValue(imageFormat(file));
} catch (const std::exception &) {
// broken file or unsupported version
@@ -786,12 +828,9 @@ bool EXRHandler::canRead(QIODevice *device)
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);

View File

@@ -86,6 +86,8 @@ private:
* - 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.
* - 10: High-Throughput JPEG2000 (HTJ2K), 256 scanlines (requires OpenEXR 3.4+).
* - 11: High-Throughput JPEG2000 (HTJ2K), 32 scanlines (requires OpenEXR 3.4+).
*/
qint32 m_compressionRatio;

View File

@@ -54,10 +54,10 @@ public:
{
return width() > 0 && height() > 0 && width() <= HDR_MAX_IMAGE_WIDTH && height() <= HDR_MAX_IMAGE_HEIGHT;
}
qint32 width() const { return(m_size.width()); }
qint32 height() const { return(m_size.height()); }
QString software() const { return(m_software); }
QImageIOHandler::Transformations transformation() const { return(m_transformation); }
qint32 width() const { return m_size.width(); }
qint32 height() const { return m_size.height(); }
QString software() const { return m_software; }
QImageIOHandler::Transformations transformation() const { return m_transformation; }
/*!
* \brief colorSpace
@@ -73,7 +73,7 @@ public:
* 0.600 0.150 0.060 0.333 0.333" for red, green, blue
* and white, respectively.
*/
QColorSpace colorSpace() const { return(m_colorSpace); }
QColorSpace colorSpace() const { return m_colorSpace; }
/*!
* \brief exposure
@@ -125,14 +125,16 @@ public:
Header h;
int cnt = 0;
int len;
QByteArray line(MAXLINE + 1, Qt::Uninitialized);
int len = 0;
QByteArray line(MAXLINE, char());
QByteArray format;
// Parse header
do {
len = device->readLine(line.data(), MAXLINE);
len = device->readLine(line.data(), line.size());
if (len < 0) {
break;
}
if (line.startsWith("FORMAT=")) {
format = line.mid(7, len - 7).trimmed();
}
@@ -173,7 +175,11 @@ public:
return h;
}
len = device->readLine(line.data(), MAXLINE);
len = device->readLine(line.data(), line.size());
if (len < 0) {
qCDebug(HDRPLUGIN) << "Invalid HDR file, error while reading the first line after the header";
return h;
}
line.resize(len);
/*
@@ -241,7 +247,7 @@ static bool Read_Old_Line(uchar *image, int width, QDataStream &s)
s >> image[2];
s >> image[3];
if (s.atEnd()) {
if (s.status() != QDataStream::Ok) {
return false;
}
@@ -322,8 +328,7 @@ static bool LoadHDR(QDataStream &s, const Header& h, QImage &img)
return false;
}
QByteArray lineArray;
lineArray.resize(4 * width);
QByteArray lineArray(4 * width, char());
uchar *image = reinterpret_cast<uchar *>(lineArray.data());
for (int cline = 0; cline < height; cline++) {
@@ -335,20 +340,24 @@ static bool LoadHDR(QDataStream &s, const Header& h, QImage &img)
// determine scanline type
if ((width < MINELEN) || (MAXELEN < width)) {
Read_Old_Line(image, width, s);
if (!Read_Old_Line(image, width, s)) {
return false;
}
RGBE_To_QRgbLine(image, scanline, h);
continue;
}
s >> val;
if (s.atEnd()) {
return true;
if (s.status() != QDataStream::Ok) {
return false;
}
if (val != 2) {
s.device()->ungetChar(val);
Read_Old_Line(image, width, s);
if (!Read_Old_Line(image, width, s)) {
return false;
}
RGBE_To_QRgbLine(image, scanline, h);
continue;
}
@@ -357,13 +366,15 @@ static bool LoadHDR(QDataStream &s, const Header& h, QImage &img)
s >> image[2];
s >> image[3];
if (s.atEnd()) {
return true;
if (s.status() != QDataStream::Ok) {
return false;
}
if ((image[1] != 2) || (image[2] & 128)) {
image[0] = 2;
Read_Old_Line(image + 4, width - 1, s);
if (!Read_Old_Line(image + 4, width - 1, s)) {
return false;
}
RGBE_To_QRgbLine(image, scanline, h);
continue;
}
@@ -377,7 +388,7 @@ static bool LoadHDR(QDataStream &s, const Header& h, QImage &img)
for (int i = 0, len = int(lineArray.size()); i < 4; i++) {
for (int j = 0; j < width;) {
s >> code;
if (s.atEnd()) {
if (s.status() != QDataStream::Ok) {
qCDebug(HDRPLUGIN) << "Truncated HDR file";
return false;
}
@@ -505,11 +516,11 @@ bool HDRHandler::canRead(QIODevice *device)
}
// 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") {
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
// allow to load official test cases: https://radsite.lbl.gov/radiance/framed.html
device->startTransaction();
auto h = HDRHandlerPrivate::readHeader(device);
device->rollbackTransaction();

View File

@@ -262,14 +262,26 @@ static void addMetadata(QImage &img, const IFOR_Chunk *form)
// if no explicit resolution was found, apply the aspect ratio to the default one
if (!resChanged) {
auto headers = IFFChunk::searchT<BMHDChunk>(form);
if (!headers.isEmpty()) {
auto xr = headers.first()->xAspectRatio();
auto yr = headers.first()->yAspectRatio();
if (xr > 0 && yr > 0 && xr > yr) {
img.setDotsPerMeterX(img.dotsPerMeterX() * yr / xr);
} else if (xr > 0 && yr > 0 && xr < yr) {
img.setDotsPerMeterY(img.dotsPerMeterY() * xr / yr);
if (form->formType() == IMAG_FORM_TYPE) {
auto params = IFFChunk::searchT<IPARChunk>(form);
if (!params.isEmpty()) {
img.setDotsPerMeterY(img.dotsPerMeterY() * params.first()->aspectRatio());
}
} else if (form->formType() == RGFX_FORM_TYPE) {
auto headers = IFFChunk::searchT<RGHDChunk>(form);
if (!headers.isEmpty()) {
img.setDotsPerMeterY(img.dotsPerMeterY() * headers.first()->aspectRatio());
}
} else {
auto headers = IFFChunk::searchT<BMHDChunk>(form);
if (!headers.isEmpty()) {
auto xr = headers.first()->xAspectRatio();
auto yr = headers.first()->yAspectRatio();
if (xr > 0 && yr > 0 && xr > yr) {
img.setDotsPerMeterX(img.dotsPerMeterX() * yr / xr);
} else if (xr > 0 && yr > 0 && xr < yr) {
img.setDotsPerMeterY(img.dotsPerMeterY() * xr / yr);
}
}
}
}
@@ -327,7 +339,6 @@ bool IFFHandler::readStandardImage(QImage *image)
// show the first one (I don't have a sample with many images)
auto headers = IFFChunk::searchT<BMHDChunk>(form);
if (headers.isEmpty()) {
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readStandardImage(): no supported image found";
return false;
}
@@ -426,7 +437,6 @@ bool IFFHandler::readMayaImage(QImage *image)
// show the first one (I don't have a sample with many images)
auto headers = IFFChunk::searchT<TBHDChunk>(form);
if (headers.isEmpty()) {
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readMayaImage(): no supported image found";
return false;
}
@@ -464,11 +474,133 @@ bool IFFHandler::readMayaImage(QImage *image)
painter.setCompositionMode(QPainter::CompositionMode_Source);
painter.drawImage(tp, ti);
}
#if QT_VERSION < QT_VERSION_CHECK(6, 9, 0)
img.mirror(false, true);
#else
img.flip(Qt::Orientation::Vertical);
#endif
addMetadata(img, form);
*image = img;
return true;
}
bool IFFHandler::readCDIImage(QImage *image)
{
auto forms = d->searchForms<FORMChunk>();
if (forms.isEmpty()) {
return false;
}
auto cin = qBound(0, currentImageNumber(), int(forms.size() - 1));
auto &&form = forms.at(cin);
// show the first one (I don't have a sample with many images)
auto headers = IFFChunk::searchT<IHDRChunk>(form);
if (headers.isEmpty()) {
return false;
}
// create the image
auto &&header = headers.first();
auto img = imageAlloc(header->size(), form->format());
if (img.isNull()) {
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readCDIImage(): error while allocating the image";
return false;
}
// set the palette
if (img.format() == QImage::Format_Indexed8) {
auto pltes = IFFChunk::searchT<PLTEChunk>(form);
if (!pltes.isEmpty()) {
img.setColorTable(pltes.first()->palette());
}
}
// decoding the image
auto bodies = IFFChunk::searchT<IDATChunk>(form);
if (bodies.isEmpty()) {
img.fill(0);
} else {
auto &&body = bodies.first();
if (!body->resetStrideRead(device())) {
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readCDIImage(): error while reading image data";
return false;
}
auto pars = IFFChunk::searchT<IPARChunk>(form);
auto yuvs = IFFChunk::searchT<YUVSChunk>(form);
for (auto y = 0, h = img.height(); y < h; ++y) {
auto line = reinterpret_cast<char*>(img.scanLine(y));
auto ba = body->strideRead(device(), y, header,
pars.isEmpty() ? nullptr : pars.first(),
yuvs.isEmpty() ? nullptr : yuvs.first());
if (ba.isEmpty()) {
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readCDIImage(): error while reading image scanline";
return false;
}
memcpy(line, ba.constData(), std::min(img.bytesPerLine(), ba.size()));
}
}
// set metadata (including image resolution)
addMetadata(img, form);
*image = img;
return true;
}
bool IFFHandler::readRGFXImage(QImage *image)
{
auto forms = d->searchForms<FORMChunk>();
if (forms.isEmpty()) {
return false;
}
auto cin = qBound(0, currentImageNumber(), int(forms.size() - 1));
auto &&form = forms.at(cin);
// show the first one (I don't have a sample with many images)
auto headers = IFFChunk::searchT<RGHDChunk>(form);
if (headers.isEmpty()) {
return false;
}
// create the image
auto &&header = headers.first();
auto img = imageAlloc(header->size(), form->format());
if (img.isNull()) {
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readRGFXImage(): error while allocating the image";
return false;
}
// set the palette
if (img.format() == QImage::Format_Indexed8) {
auto pltes = IFFChunk::searchT<RCOLChunk>(form);
if (!pltes.isEmpty()) {
img.setColorTable(pltes.first()->palette());
}
}
// decoding the image
auto bodies = IFFChunk::searchT<RBODChunk>(form);
if (bodies.isEmpty()) {
img.fill(0);
} else {
auto &&body = bodies.first();
if (!body->resetStrideRead(device())) {
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readRGFXImage(): error while reading image data";
return false;
}
auto rcsms = IFFChunk::searchT<RSCMChunk>(form);
auto rcols = IFFChunk::searchT<RCOLChunk>(form);
for (auto y = 0, h = img.height(); y < h; ++y) {
auto line = reinterpret_cast<char*>(img.scanLine(y));
auto ba = body->strideRead(device(), y, header,
rcsms.isEmpty() ? nullptr : rcsms.first(),
rcols.isEmpty() ? nullptr : rcols.first());
if (ba.isEmpty()) {
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readRGFXImage(): error while reading image scanline";
return false;
}
memcpy(line, ba.constData(), std::min(img.bytesPerLine(), ba.size()));
}
}
// set metadata (including image resolution)
addMetadata(img, form);
*image = img;
@@ -490,6 +622,14 @@ bool IFFHandler::read(QImage *image)
return true;
}
if (readCDIImage(image)) {
return true;
}
if (readRGFXImage(image)) {
return true;
}
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::read(): no supported image found";
return false;
}

View File

@@ -35,6 +35,10 @@ private:
bool readMayaImage(QImage *image);
bool readCDIImage(QImage *image);
bool readRGFXImage(QImage *image);
private:
const QScopedPointer<IFFHandlerPrivate> d;
};

View File

@@ -253,20 +253,27 @@ public:
bool jp2ToImage(QImage *img) const
{
Q_ASSERT(img->depth() == 8 * sizeof(T) || img->depth() == 32 * sizeof(T));
for (qint32 c = 0, cc = m_jp2_image->numcomps; c < cc; ++c) {
auto cs = cc == 1 ? 1 : 4;
if (img->width() < 1 || img->height() < 1) {
return false;
}
auto maxChannels = qint32(img->bytesPerLine() / sizeof(T) / img->width());
for (qint32 c = 0, cc = std::min(qint32(m_jp2_image->numcomps), maxChannels); c < cc; ++c) {
auto cs = std::min(cc == 1 ? 1 : 4, maxChannels);
auto &&jc = m_jp2_image->comps[c];
if (jc.data == nullptr)
if (jc.data == nullptr) {
return false;
if (qint32(jc.w) != img->width() || qint32(jc.h) != img->height())
}
if (qint32(jc.w) != img->width() || qint32(jc.h) != img->height()) {
return false;
}
// discriminate between int and float (avoid complicating things by creating classes with template specializations)
if (std::numeric_limits<T>::is_integer) {
auto divisor = 1;
if (jc.prec > sizeof(T) * 8) {
auto divisor = 1ull;
auto prec = std::min(size_t(jc.prec), sizeof(*jc.data) * 8);
if (prec > sizeof(T) * 8 && prec < 64) {
// convert to the wanted precision (e.g. 16-bit -> 8-bit: divisor = 65535 / 255 = 257)
divisor = std::max(1, int(((1ll << jc.prec) - 1) / ((1ll << (sizeof(T) * 8)) - 1)));
divisor = std::max(1ull, (((1ull << prec) - 1) / ((1ull << (sizeof(T) * 8)) - 1)));
}
for (qint32 y = 0, h = img->height(); y < h; ++y) {
auto ptr = reinterpret_cast<T *>(img->scanLine(y));
@@ -352,11 +359,15 @@ public:
}
// OpenJPEG uses a shadow copy @32-bit/channel so we need to do a check
auto maxBytes = qint64(QImageReader::allocationLimit()) * 1024 * 1024;
auto neededBytes = qint64(width) * height * nchannels * 4;
if (maxBytes > 0 && neededBytes > maxBytes) {
qCCritical(LOG_JP2PLUGIN) << "Allocation limit set to" << (maxBytes / 1024 / 1024) << "MiB but" << (neededBytes / 1024 / 1024) << "MiB are needed!";
return false;
const int allocationLimit = QImageReader::allocationLimit();
if (allocationLimit > 0) {
auto maxBytes = qint64(allocationLimit) * 1024 * 1024;
auto neededBytes = qint64(width) * height * nchannels * 4;
if (maxBytes > 0 && neededBytes > maxBytes) {
qCCritical(LOG_JP2PLUGIN) << "Allocation limit set to" << (maxBytes / 1024 / 1024) << "MiB but" << (neededBytes / 1024 / 1024)
<< "MiB are needed!";
return false;
}
}
return true;

View File

@@ -180,7 +180,7 @@ bool QJpegXLHandler::ensureDecoder()
int num_worker_threads = QThread::idealThreadCount();
if (!m_runner && num_worker_threads >= 4) {
/* use half of the threads because plug-in is usually used in environment
* where application performs another tasks in backround (pre-load other images) */
* where application performs other tasks in the background (pre-load other images) */
num_worker_threads = num_worker_threads / 2;
num_worker_threads = qBound(2, num_worker_threads, 64);
m_runner = JxlThreadParallelRunnerCreate(nullptr, num_worker_threads);
@@ -258,14 +258,16 @@ bool QJpegXLHandler::countALLFrames()
bool is_gray = m_basicinfo.num_color_channels == 1 && m_basicinfo.alpha_bits == 0;
JxlColorEncoding color_encoding;
if (m_basicinfo.uses_original_profile == JXL_FALSE && m_basicinfo.have_animation == JXL_FALSE) {
const JxlCmsInterface *jxlcms = JxlGetDefaultCms();
if (jxlcms) {
status = JxlDecoderSetCms(m_decoder, *jxlcms);
if (status != JXL_DEC_SUCCESS) {
qCWarning(LOG_JXLPLUGIN, "JxlDecoderSetCms ERROR");
if (!is_gray) {
const JxlCmsInterface *jxlcms = JxlGetDefaultCms();
if (jxlcms) {
status = JxlDecoderSetCms(m_decoder, *jxlcms);
if (status != JXL_DEC_SUCCESS) {
qCWarning(LOG_JXLPLUGIN, "JxlDecoderSetCms ERROR");
}
} else {
qCWarning(LOG_JXLPLUGIN, "No JPEG XL CMS Interface");
}
} else {
qCWarning(LOG_JXLPLUGIN, "No JPEG XL CMS Interface");
}
JxlColorEncodingSetToSRGB(&color_encoding, is_gray ? JXL_TRUE : JXL_FALSE);
@@ -853,7 +855,11 @@ bool QJpegXLHandler::write(const QImage &image)
size_t pixel_count = size_t(image.width()) * image.height();
if (MAX_IMAGE_PIXELS && pixel_count > MAX_IMAGE_PIXELS) {
qCWarning(LOG_JXLPLUGIN, "Image (%dx%d) will not be saved because it has more than %d megapixels!", image.width(), image.height(), MAX_IMAGE_PIXELS / 1024 / 1024);
qCWarning(LOG_JXLPLUGIN,
"Image (%dx%d) will not be saved because it has more than %d megapixels!",
image.width(),
image.height(),
MAX_IMAGE_PIXELS / 1024 / 1024);
return false;
}
@@ -1838,17 +1844,20 @@ bool QJpegXLHandler::rewind()
return false;
}
const JxlCmsInterface *jxlcms = JxlGetDefaultCms();
if (jxlcms) {
status = JxlDecoderSetCms(m_decoder, *jxlcms);
if (status != JXL_DEC_SUCCESS) {
qCWarning(LOG_JXLPLUGIN, "JxlDecoderSetCms ERROR");
bool is_gray = m_basicinfo.num_color_channels == 1 && m_basicinfo.alpha_bits == 0;
if (!is_gray) {
const JxlCmsInterface *jxlcms = JxlGetDefaultCms();
if (jxlcms) {
status = JxlDecoderSetCms(m_decoder, *jxlcms);
if (status != JXL_DEC_SUCCESS) {
qCWarning(LOG_JXLPLUGIN, "JxlDecoderSetCms ERROR");
}
} else {
qCWarning(LOG_JXLPLUGIN, "No JPEG XL CMS Interface");
}
} else {
qCWarning(LOG_JXLPLUGIN, "No JPEG XL CMS Interface");
}
bool is_gray = m_basicinfo.num_color_channels == 1 && m_basicinfo.alpha_bits == 0;
JxlColorEncoding color_encoding;
JxlColorEncodingSetToSRGB(&color_encoding, is_gray ? JXL_TRUE : JXL_FALSE);
JxlDecoderSetPreferredColorProfile(m_decoder, &color_encoding);
@@ -1999,6 +2008,11 @@ bool QJpegXLHandler::extractBox(QByteArray &output, size_t container_size)
return false;
}
if (rawboxsize > 8388608) { // 8MB limit
qCWarning(LOG_JXLPLUGIN, "Skipped decoding of big JXL metadata box");
return true;
}
output.resize(rawboxsize);
status = JxlDecoderSetBoxBuffer(m_decoder, reinterpret_cast<uint8_t *>(output.data()), output.size());
if (status != JXL_DEC_SUCCESS) {
@@ -2012,7 +2026,7 @@ bool QJpegXLHandler::extractBox(QByteArray &output, size_t container_size)
if (status == JXL_DEC_BOX_NEED_MORE_OUTPUT) {
size_t bytes_remains = JxlDecoderReleaseBoxBuffer(m_decoder);
if (output.size() > 4194304) { // approx. 4MB limit for decompressed metadata box
if (output.size() > 33554432) { // approx. 32MB (4*8) limit for decompressed metadata box
qCWarning(LOG_JXLPLUGIN, "JXL metadata box is too large");
m_parseState = ParseJpegXLError;
return false;

View File

@@ -35,17 +35,22 @@
#include <cstring>
Q_DECLARE_LOGGING_CATEGORY(LOG_JXRPLUGIN)
#ifdef QT_DEBUG
Q_LOGGING_CATEGORY(LOG_JXRPLUGIN, "kf.imageformats.plugins.jxr", QtDebugMsg)
#else
Q_LOGGING_CATEGORY(LOG_JXRPLUGIN, "kf.imageformats.plugins.jxr", QtWarningMsg)
#endif
/*!
* Support for float images
*
* NOTE: Float images have values greater than 1 so they need an additional in place conversion.
*/
// #define JXR_DENY_FLOAT_IMAGE
// #define JXR_DENY_FLOAT_IMAGE // default commented
/*!
* Remove the neeeds of additional memory by disabling the conversion between
* Remove the needs of additional memory by disabling the conversion between
* different color depths (e.g. RGBA64bpp to RGBA32bpp).
*
* NOTE: Leaving deptch conversion enabled (default) ensures maximum read compatibility.
@@ -112,28 +117,35 @@ public:
, m_transformations(QImageIOHandler::TransformationNone)
{
m_tempDir = QSharedPointer<QTemporaryDir>(new QTemporaryDir);
if (PKCreateFactory(&pFactory, PK_SDK_VERSION) == WMP_errSuccess) {
PKCreateCodecFactory(&pCodecFactory, WMP_SDK_VERSION);
}
if (pFactory == nullptr || pCodecFactory == nullptr) {
qCWarning(LOG_JXRPLUGIN) << "JXRHandlerPrivate::JXRHandlerPrivate() initialization error of JXR library!";
if (auto err = PKCreateFactory(&pFactory, PK_SDK_VERSION)) {
qCCritical(LOG_JXRPLUGIN) << "JXRHandlerPrivate::JXRHandlerPrivate() error while initializing the JXR factory:" << err;
} else if (auto err = PKCreateCodecFactory(&pCodecFactory, WMP_SDK_VERSION)) {
qCCritical(LOG_JXRPLUGIN) << "JXRHandlerPrivate::JXRHandlerPrivate() error while initializing the JXR codec factory:" << err;
}
}
JXRHandlerPrivate(const JXRHandlerPrivate &other) = default;
~JXRHandlerPrivate()
{
if (pCodecFactory) {
PKCreateCodecFactory_Release(&pCodecFactory);
}
if (pFactory) {
PKCreateFactory_Release(&pFactory);
}
if (pDecoder) {
PKImageDecode_Release(&pDecoder);
if (auto err = pDecoder->Release(&pDecoder)) {
qCWarning(LOG_JXRPLUGIN) << "JXRHandlerPrivate::JXRHandlerPrivate() error while releasing the decoder:" << err;
}
}
if (pEncoder) {
PKImageEncode_Release(&pEncoder);
if (auto err = pEncoder->Release(&pEncoder)) {
qCWarning(LOG_JXRPLUGIN) << "JXRHandlerPrivate::JXRHandlerPrivate() error while releasing the encoder:" << err;
}
}
if (pCodecFactory) {
if (auto err = pCodecFactory->Release(&pCodecFactory)) {
qCWarning(LOG_JXRPLUGIN) << "JXRHandlerPrivate::JXRHandlerPrivate() error while releasing the codec factory:" << err;
}
}
if (pFactory) {
if (auto err = pFactory->Release(&pFactory)) {
qCWarning(LOG_JXRPLUGIN) << "JXRHandlerPrivate::JXRHandlerPrivate() error while releasing the factory:" << err;
}
}
}
@@ -278,8 +290,12 @@ public:
PKPixelFormatGUID jxrFormat() const
{
PKPixelFormatGUID pixelFormatGUID = GUID_PKPixelFormatUndefined;
if (pDecoder) {
pDecoder->GetPixelFormat(pDecoder, &pixelFormatGUID);
if (pDecoder == nullptr) {
return pixelFormatGUID;
}
if (auto err = pDecoder->GetPixelFormat(pDecoder, &pixelFormatGUID)) {
qCCritical(LOG_JXRPLUGIN) << "JXRHandlerPrivate::jxrFormat() error while getting pixel format:" << err;
return GUID_PKPixelFormatUndefined;
}
return pixelFormatGUID;
}
@@ -303,6 +319,12 @@ public:
return qtFormat;
}
// *** MCH could be RGB, CMYK ***
qtFormat = multichannelFormat(jxrfmt, colorSpace());
if (qtFormat != QImage::Format_Invalid) {
return qtFormat;
}
// *** CONVERSION WITH THE SAME DEPTH ***
// IMPORTANT: For supported conversions see JXRGluePFC.c
@@ -393,17 +415,20 @@ public:
*/
QSize imageSize() const
{
if (pDecoder) {
qint32 w, h;
pDecoder->GetSize(pDecoder, &w, &h);
if (w > JXR_MAX_IMAGE_WIDTH || h > JXR_MAX_IMAGE_HEIGHT || w < 1 || h < 1) {
qCCritical(LOG_JXRPLUGIN) << "JXRHandlerPrivate::imageSize() Maximum image size is limited to" << JXR_MAX_IMAGE_WIDTH << "x"
<< JXR_MAX_IMAGE_HEIGHT << "pixels";
return {};
}
return QSize(w, h);
if (pDecoder == nullptr) {
return {};
}
return {};
qint32 w = 0, h = 0;
if (auto err = pDecoder->GetSize(pDecoder, &w, &h)) {
qCCritical(LOG_JXRPLUGIN) << "JXRHandlerPrivate::imageSize() error while getting the image size:" << err;
return {};
}
if (w > JXR_MAX_IMAGE_WIDTH || h > JXR_MAX_IMAGE_HEIGHT || w < 1 || h < 1) {
qCCritical(LOG_JXRPLUGIN) << "JXRHandlerPrivate::imageSize() Maximum image size is limited to" << JXR_MAX_IMAGE_WIDTH << "x"
<< JXR_MAX_IMAGE_HEIGHT << "pixels";
return {};
}
return QSize(w, h);
}
/*!
@@ -416,8 +441,8 @@ public:
if (pDecoder == nullptr) {
return cs;
}
quint32 size;
if (!pDecoder->GetColorContext(pDecoder, nullptr, &size) && size) {
quint32 size = 0;
if (!pDecoder->GetColorContext(pDecoder, nullptr, &size) && size > 0 && size < kMaxQVectorSize) {
QByteArray ba(size, 0);
if (!pDecoder->GetColorContext(pDecoder, reinterpret_cast<quint8 *>(ba.data()), &size)) {
cs = QColorSpace::fromIccProfile(ba);
@@ -437,7 +462,7 @@ public:
return xmp;
}
#ifdef JXR_ENABLE_ADVANCED_METADATA
quint32 size;
quint32 size = 0;
if (!PKImageDecode_GetXMPMetadata_WMP(pDecoder, nullptr, &size) && size > 0 && size < JXR_MAX_METADATA_SIZE) {
QByteArray ba(size, 0);
if (!PKImageDecode_GetXMPMetadata_WMP(pDecoder, reinterpret_cast<quint8 *>(ba.data()), &size)) {
@@ -498,7 +523,7 @@ public:
}
auto host = hostComputer();
if (!host.isEmpty()) {
image.setText(QStringLiteral(META_KEY_HOSTCOMPUTER), capt);
image.setText(QStringLiteral(META_KEY_HOSTCOMPUTER), host);
}
auto docn = documentName();
if (!docn.isEmpty()) {
@@ -556,7 +581,11 @@ public:
if (device == nullptr || pEncoder == nullptr) {
return false;
}
if (auto err = PKImageEncode_Release(&pEncoder)) {
if (auto err = pEncoder->Terminate(pEncoder)) {
qCWarning(LOG_JXRPLUGIN) << "JXRHandlerPrivate::finalizeWriting() error while terminating the encoder:" << err;
return false;
}
if (auto err = pEncoder->Release(&pEncoder)) {
qCWarning(LOG_JXRPLUGIN) << "JXRHandlerPrivate::finalizeWriting() error while releasing the encoder:" << err;
return false;
}
@@ -796,6 +825,47 @@ public:
return GUID_PKPixelFormatUndefined;
}
/*!
* \brief multichannelFormat
* I can only decide how to interpret multichannels by checking the color profile.
* If it's not present, I assume CMYK for 4 channels and RGB for 3 channels (like
* Windows does).
* \param jxrFormat Format to be converted.
* \param cs The color space of the image.
* \return A valid Qt format or QImage::Format_Invalid if there is no match
*/
static QImage::Format multichannelFormat(const PKPixelFormatGUID &jxrFormat, const QColorSpace& cs)
{
auto model = QColorSpace::ColorModel::Undefined;
if (cs.isValid()) {
model = cs.colorModel();
} else if (!cs.iccProfile().isEmpty()) {
model = QColorSpace::ColorModel::Gray; // means invalid
}
if (IsEqualGUID(GUID_PKPixelFormat24bpp3Channels, jxrFormat)) {
if (model == QColorSpace::ColorModel::Rgb || model == QColorSpace::ColorModel::Undefined)
return QImage::Format_RGB888;
}
if (IsEqualGUID(GUID_PKPixelFormat32bpp4Channels, jxrFormat)) {
if (model == QColorSpace::ColorModel::Cmyk || model == QColorSpace::ColorModel::Undefined)
return QImage::Format_CMYK8888;
}
if (IsEqualGUID(GUID_PKPixelFormat32bpp3ChannelsAlpha, jxrFormat)) {
if (model == QColorSpace::ColorModel::Rgb || model == QColorSpace::ColorModel::Undefined)
return QImage::Format_RGBA8888;
}
if (IsEqualGUID(GUID_PKPixelFormat64bpp3ChannelsAlpha, jxrFormat)) {
if (model == QColorSpace::ColorModel::Rgb || model == QColorSpace::ColorModel::Undefined)
return QImage::Format_RGBA64;
}
return QImage::Format_Invalid;
}
private:
static QList<std::pair<QImage::Format, PKPixelFormatGUID>> exactMatchingFormats()
{
@@ -967,7 +1037,7 @@ bool JXRHandler::read(QImage *outImage)
}
// resolution
float hres, vres;
float hres = 0, vres = 0;
if (auto err = d->pDecoder->GetResolution(d->pDecoder, &hres, &vres)) {
qCWarning(LOG_JXRPLUGIN) << "JXRHandler::read() error while reading resolution:" << err;
} else {
@@ -999,14 +1069,18 @@ bool JXRHandler::read(QImage *outImage)
return false;
}
if (auto err = pConverter->Initialize(pConverter, d->pDecoder, nullptr, convFmt)) {
PKFormatConverter_Release(&pConverter);
qCWarning(LOG_JXRPLUGIN) << "JXRHandler::read() unable to initialize the converter:" << err;
if (auto err = pConverter->Release(&pConverter)) {
qCWarning(LOG_JXRPLUGIN) << "JXRHandler::read() error while releasing the converter:" << err;
}
return false;
}
if (d->pDecoder->WMP.wmiI.cBitsPerUnit == size_t(img.depth())) { // in place conversion
if (auto err = pConverter->Copy(pConverter, &rect, img.bits(), img.bytesPerLine())) {
PKFormatConverter_Release(&pConverter);
qCWarning(LOG_JXRPLUGIN) << "JXRHandler::read() unable to copy converted data:" << err;
if (auto err = pConverter->Release(&pConverter)) {
qCWarning(LOG_JXRPLUGIN) << "JXRHandler::read() error while releasing the converter:" << err;
}
return false;
}
} else { // additional buffer needed
@@ -1015,19 +1089,26 @@ bool JXRHandler::read(QImage *outImage)
qint64 limit = QImageReader::allocationLimit();
if (limit && (buffSize + img.sizeInBytes()) > limit * 1024 * 1024) {
qCWarning(LOG_JXRPLUGIN) << "JXRHandler::read() unable to covert due to allocation limit set:" << limit << "MiB";
if (auto err = pConverter->Release(&pConverter)) {
qCWarning(LOG_JXRPLUGIN) << "JXRHandler::read() error while releasing the converter:" << err;
}
return false;
}
QVector<quint8> ba(buffSize);
if (auto err = pConverter->Copy(pConverter, &rect, ba.data(), convStrideSize)) {
PKFormatConverter_Release(&pConverter);
qCWarning(LOG_JXRPLUGIN) << "JXRHandler::read() unable to copy converted data:" << err;
if (auto err = pConverter->Release(&pConverter)) {
qCWarning(LOG_JXRPLUGIN) << "JXRHandler::read() error while releasing the converter:" << err;
}
return false;
}
for (qint32 y = 0, h = img.height(); y < h; ++y) {
std::memcpy(img.scanLine(y), ba.data() + convStrideSize * y, (std::min)(convStrideSize, qint64(img.bytesPerLine())));
}
}
PKFormatConverter_Release(&pConverter);
if (auto err = pConverter->Release(&pConverter)) {
qCWarning(LOG_JXRPLUGIN) << "JXRHandler::read() error while releasing the converter:" << err;
}
}
// Metadata (e.g.: icc profile, description, etc...)

View File

@@ -36,17 +36,26 @@
#define TIFF_VAL_URES_CENTIMETER 3
// EXIF 3 specs
#define EXIF_EXPOSURETIME 0x829A
#define EXIF_FNUMBER 0x829D
#define EXIF_EXIFIFD 0x8769
#define EXIF_EXPOSUREPROGRAM 0x8822
#define EXIF_GPSIFD 0x8825
#define EXIF_ISOSPEEDRATINGS 0x8827
#define EXIF_EXIFVERSION 0x9000
#define EXIF_DATETIMEORIGINAL 0x9003
#define EXIF_DATETIMEDIGITIZED 0x9004
#define EXIF_OFFSETTIME 0x9010
#define EXIF_OFFSETTIMEORIGINAL 0x9011
#define EXIF_OFFSETTIMEDIGITIZED 0x9012
#define EXIF_FLASH 0x9209
#define EXIF_FOCALLENGTH 0x920A
#define EXIF_COLORSPACE 0xA001
#define EXIF_PIXELXDIM 0xA002
#define EXIF_PIXELYDIM 0xA003
#define EXIF_EXPOSUREMODE 0xA402
#define EXIF_WHITEBALANCE 0xA403
#define EXIF_DIGITALZOOMRATIO 0xA404
#define EXIF_IMAGEUNIQUEID 0xA420
#define EXIF_BODYSERIALNUMBER 0xA431
#define EXIF_LENSMAKE 0xA433
@@ -64,6 +73,8 @@
#define GPS_LONGITUDE 4
#define GPS_ALTITUDEREF 5
#define GPS_ALTITUDE 6
#define GPS_SPEEDREF 12
#define GPS_SPEED 13
#define GPS_IMGDIRECTIONREF 16
#define GPS_IMGDIRECTION 17
#define EXIF_TAG_VALUE(n, byteSize) (((n) << 6) | ((byteSize) & 0x3F))
@@ -123,16 +134,25 @@ static const KnownTags staticTagTypes = {
TagInfo(TIFF_ARTIST, ExifTagType::Utf8),
TagInfo(TIFF_DATETIME, ExifTagType::Ascii),
TagInfo(TIFF_COPYRIGHT, ExifTagType::Utf8),
TagInfo(EXIF_EXPOSURETIME, ExifTagType::Rational),
TagInfo(EXIF_FNUMBER, ExifTagType::Rational),
TagInfo(EXIF_EXIFIFD, ExifTagType::Long),
TagInfo(EXIF_EXPOSUREPROGRAM, ExifTagType::Short),
TagInfo(EXIF_GPSIFD, ExifTagType::Long),
TagInfo(EXIF_ISOSPEEDRATINGS, ExifTagType::Short),
TagInfo(EXIF_DATETIMEORIGINAL, ExifTagType::Ascii),
TagInfo(EXIF_OFFSETTIMEDIGITIZED, ExifTagType::Ascii),
TagInfo(EXIF_DATETIMEDIGITIZED, ExifTagType::Ascii),
TagInfo(EXIF_OFFSETTIME, ExifTagType::Ascii),
TagInfo(EXIF_OFFSETTIMEORIGINAL, ExifTagType::Ascii),
TagInfo(EXIF_OFFSETTIMEDIGITIZED, ExifTagType::Ascii),
TagInfo(EXIF_FLASH, ExifTagType::Short),
TagInfo(EXIF_FOCALLENGTH, ExifTagType::Rational),
TagInfo(EXIF_COLORSPACE, ExifTagType::Short),
TagInfo(EXIF_PIXELXDIM, ExifTagType::Long),
TagInfo(EXIF_PIXELYDIM, ExifTagType::Long),
TagInfo(EXIF_EXPOSUREMODE, ExifTagType::Short),
TagInfo(EXIF_WHITEBALANCE, ExifTagType::Short),
TagInfo(EXIF_DIGITALZOOMRATIO, ExifTagType::Rational),
TagInfo(EXIF_IMAGEUNIQUEID, ExifTagType::Ascii),
TagInfo(EXIF_BODYSERIALNUMBER, ExifTagType::Ascii),
TagInfo(EXIF_LENSMAKE, ExifTagType::Utf8),
@@ -155,6 +175,8 @@ static const KnownTags staticGpsTagTypes = {
TagInfo(GPS_LONGITUDE, ExifTagType::Rational),
TagInfo(GPS_ALTITUDEREF, ExifTagType::Byte),
TagInfo(GPS_ALTITUDE, ExifTagType::Rational),
TagInfo(GPS_SPEEDREF, ExifTagType::Ascii),
TagInfo(GPS_SPEED, ExifTagType::Rational),
TagInfo(GPS_IMGDIRECTIONREF, ExifTagType::Ascii),
TagInfo(GPS_IMGDIRECTION, ExifTagType::Rational)
};
@@ -256,7 +278,7 @@ static bool checkHeader(QDataStream &ds)
/*!
* \brief updatePos
* Write the current stram position in \a pos position as uint32.
* Write the current stream position in \a pos position as uint32.
* \return True on success, otherwise false;
*/
static bool updatePos(QDataStream &ds, quint32 pos)
@@ -317,7 +339,7 @@ static void writeList(QDataStream &ds, const QVariant &value)
inline qint32 rationalPrecision(double v)
{
v = qAbs(v);
return 8 - qBound(0, v < 1 ? 8 : int(std::log10(v)), 8);
return v < 1 ? 8 : 8 - qBound(0, int(std::log10(v)), 8);
}
template<class T>
@@ -510,7 +532,7 @@ static QByteArray readBytes(QDataStream &ds, quint32 count, bool asciiz)
* \param pos The position of the IFD.
* \param knownTags List of known and supported tags.
* \param nextIfd The position of next IFD (0 if none).
* \return True on succes, otherwise false.
* \return True on success, otherwise false.
*/
static bool readIfd(QDataStream &ds, MicroExif::Tags &tags, quint32 pos = 0, const KnownTags &knownTags = staticTagTypes, quint32 *nextIfd = nullptr)
{
@@ -884,7 +906,7 @@ QDateTime MicroExif::dateTime() const
auto ofTag = exifString(EXIF_OFFSETTIME);
if (dt.isValid() && !ofTag.isEmpty())
dt.setTimeZone(QTimeZone::fromSecondsAheadOfUtc(timeOffset(ofTag) * 60));
return(dt);
return dt;
}
void MicroExif::setDateTime(const QDateTime &dt)
@@ -904,7 +926,7 @@ QDateTime MicroExif::dateTimeOriginal() const
auto ofTag = exifString(EXIF_OFFSETTIMEORIGINAL);
if (dt.isValid() && !ofTag.isEmpty())
dt.setTimeZone(QTimeZone::fromSecondsAheadOfUtc(timeOffset(ofTag) * 60));
return(dt);
return dt;
}
void MicroExif::setDateTimeOriginal(const QDateTime &dt)
@@ -924,7 +946,7 @@ QDateTime MicroExif::dateTimeDigitized() const
auto ofTag = exifString(EXIF_OFFSETTIMEDIGITIZED);
if (dt.isValid() && !ofTag.isEmpty())
dt.setTimeZone(QTimeZone::fromSecondsAheadOfUtc(timeOffset(ofTag) * 60));
return(dt);
return dt;
}
void MicroExif::setDateTimeDigitized(const QDateTime &dt)
@@ -966,6 +988,138 @@ void MicroExif::setUniqueId(const QUuid &uuid)
setExifString(EXIF_IMAGEUNIQUEID, uuid.toString(QUuid::WithoutBraces).replace(QStringLiteral("-"), QString()));
}
double MicroExif::digitalZoomRatio() const
{
if (!m_exifTags.contains(EXIF_DIGITALZOOMRATIO))
return qQNaN();
return m_exifTags.value(EXIF_DIGITALZOOMRATIO).toDouble();
}
void MicroExif::setDigitalZoomRatio(double zoom)
{
if (qIsNaN(zoom))
m_exifTags.remove(EXIF_DIGITALZOOMRATIO);
else
m_exifTags.insert(EXIF_DIGITALZOOMRATIO, zoom);
}
quint16 MicroExif::isoSpeedRatings() const
{
return quint16(m_exifTags.value(EXIF_ISOSPEEDRATINGS).toUInt());
}
void MicroExif::setIsoSpeedRatings(quint16 iso)
{
if (iso == 0)
m_exifTags.remove(EXIF_ISOSPEEDRATINGS);
else
m_exifTags.insert(EXIF_ISOSPEEDRATINGS, iso);
}
ExposureMode MicroExif::exposureMode() const
{
auto ok = false;
auto v = m_exifTags.value(EXIF_EXPOSUREMODE).toUInt(&ok);
return ok ? ExposureMode(v) : ExposureMode::NotSet;
}
void MicroExif::setExposureMode(const ExposureMode &em)
{
if (em == ExposureMode::NotSet)
m_exifTags.remove(EXIF_EXPOSUREMODE);
else
m_exifTags.insert(EXIF_EXPOSUREMODE, quint16(em));
}
ExposureProgram MicroExif::exposureProgram() const
{
auto ok = false;
auto v = m_exifTags.value(EXIF_EXPOSUREPROGRAM).toUInt(&ok);
return ok ? ExposureProgram(v) : ExposureProgram::NotSet;
}
void MicroExif::setExposureProgram(const ExposureProgram &ep)
{
if (ep == ExposureProgram::NotSet)
m_exifTags.remove(EXIF_EXPOSUREPROGRAM);
else
m_exifTags.insert(EXIF_EXPOSUREPROGRAM, quint16(ep));
}
double MicroExif::exposureTime() const
{
if (!m_exifTags.contains(EXIF_EXPOSURETIME))
return qQNaN();
return m_exifTags.value(EXIF_EXPOSURETIME).toDouble();
}
void MicroExif::setExposureTime(double et)
{
if (qIsNaN(et))
m_exifTags.remove(EXIF_EXPOSURETIME);
else
m_exifTags.insert(EXIF_EXPOSURETIME, et);
}
double MicroExif::fNumber() const
{
if (!m_exifTags.contains(EXIF_FNUMBER))
return qQNaN();
return m_exifTags.value(EXIF_FNUMBER).toDouble();
}
void MicroExif::setFNumber(double f)
{
if (qIsNaN(f))
m_exifTags.remove(EXIF_FNUMBER);
else
m_exifTags.insert(EXIF_FNUMBER, f);
}
double MicroExif::focalLength() const
{
if (!m_exifTags.contains(EXIF_FOCALLENGTH))
return qQNaN();
return m_exifTags.value(EXIF_FOCALLENGTH).toDouble();
}
void MicroExif::setFocalLength(double fl)
{
if (qIsNaN(fl))
m_exifTags.remove(EXIF_FOCALLENGTH);
else
m_exifTags.insert(EXIF_FOCALLENGTH, fl);
}
FlashFlags MicroExif::flash() const
{
return FlashFlags(m_exifTags.value(EXIF_FLASH).toUInt());
}
void MicroExif::setFlash(const FlashFlags &flash)
{
if (flash == Flash::NotSet)
m_exifTags.remove(EXIF_FLASH);
else
m_exifTags.insert(EXIF_FLASH, quint16(flash));
}
WhiteBalance MicroExif::whiteBalance() const
{
auto ok = false;
auto v = m_exifTags.value(EXIF_WHITEBALANCE).toUInt(&ok);
return ok ? WhiteBalance(v) : WhiteBalance::NotSet;
}
void MicroExif::setWhiteBalance(const WhiteBalance &wb)
{
if (wb == WhiteBalance::NotSet)
m_exifTags.remove(EXIF_WHITEBALANCE);
else
m_exifTags.insert(EXIF_WHITEBALANCE, quint16(wb));
}
double MicroExif::latitude() const
{
auto ref = gpsString(GPS_LATITUDEREF).toUpper();
@@ -1045,6 +1199,30 @@ void MicroExif::setAltitude(double meters)
m_gpsTags.insert(GPS_ALTITUDE, meters);
}
double MicroExif::imageSpeed() const
{
if (!m_gpsTags.contains(GPS_SPEED))
return qQNaN();
auto ref = gpsString(GPS_SPEEDREF).toUpper();
auto speed = m_gpsTags.value(GPS_SPEED).toDouble();
if (ref == QStringLiteral("M"))
speed *= 1.60934;
else if (ref == QStringLiteral("N"))
speed *= 1.852;
return speed;
}
void MicroExif::setImageSpeed(double kmh)
{
if (qIsNaN(kmh)) {
m_gpsTags.remove(GPS_SPEEDREF);
m_gpsTags.remove(GPS_SPEED);
return;
}
m_gpsTags.insert(GPS_SPEEDREF, QStringLiteral("K"));
m_gpsTags.insert(GPS_SPEED, kmh);
}
double MicroExif::imageDirection(bool *isMagnetic) const
{
auto tmp = false;
@@ -1191,6 +1369,58 @@ void MicroExif::updateImageMetadata(QImage &targetImage, bool replaceExisting) c
if (!qIsNaN(v))
targetImage.setText(QStringLiteral(META_KEY_DIRECTION), QStringLiteral("%1").arg(v, 0, 'g', 9));
}
if (replaceExisting || targetImage.text(QStringLiteral(META_KEY_SPEED)).isEmpty()) {
auto v = imageSpeed();
if (!qIsNaN(v))
targetImage.setText(QStringLiteral(META_KEY_SPEED), QStringLiteral("%1").arg(v, 0, 'g', 9));
}
// shot info
if (replaceExisting || targetImage.text(QStringLiteral(META_KEY_DIGITALZOOMRATIO)).isEmpty()) {
auto v = digitalZoomRatio();
if (!qIsNaN(v))
targetImage.setText(QStringLiteral(META_KEY_DIGITALZOOMRATIO), QStringLiteral("%1").arg(v, 0, 'g', 9));
}
if (replaceExisting || targetImage.text(QStringLiteral(META_KEY_EXPOSUREMODE)).isEmpty()) {
auto v = exposureMode();
if (v != ExposureMode::NotSet)
targetImage.setText(QStringLiteral(META_KEY_EXPOSUREMODE), QStringLiteral("%1").arg(quint16(v)));
}
if (replaceExisting || targetImage.text(QStringLiteral(META_KEY_EXPOSUREPROGRAM)).isEmpty()) {
auto v = exposureProgram();
if (v != ExposureProgram::NotSet)
targetImage.setText(QStringLiteral(META_KEY_EXPOSUREPROGRAM), QStringLiteral("%1").arg(quint16(v)));
}
if (replaceExisting || targetImage.text(QStringLiteral(META_KEY_EXPOSURETIME)).isEmpty()) {
auto v = exposureTime();
if (!qIsNaN(v))
targetImage.setText(QStringLiteral(META_KEY_EXPOSURETIME), QStringLiteral("%1").arg(v, 0, 'g', 9));
}
if (replaceExisting || targetImage.text(QStringLiteral(META_KEY_FLASH)).isEmpty()) {
auto v = flash();
if (v != Flash::NotSet)
targetImage.setText(QStringLiteral(META_KEY_FLASH), QStringLiteral("%1").arg(quint16(v)));
}
if (replaceExisting || targetImage.text(QStringLiteral(META_KEY_FNUMBER)).isEmpty()) {
auto v = fNumber();
if (!qIsNaN(v))
targetImage.setText(QStringLiteral(META_KEY_FNUMBER), QStringLiteral("%1").arg(v, 0, 'g', 9));
}
if (replaceExisting || targetImage.text(QStringLiteral(META_KEY_FOCALLENGTH)).isEmpty()) {
auto v = focalLength();
if (!qIsNaN(v))
targetImage.setText(QStringLiteral(META_KEY_FOCALLENGTH), QStringLiteral("%1").arg(v, 0, 'g', 9));
}
if (replaceExisting || targetImage.text(QStringLiteral(META_KEY_ISOSPEEDRATINGS)).isEmpty()) {
auto v = isoSpeedRatings();
if (v != 0)
targetImage.setText(QStringLiteral(META_KEY_ISOSPEEDRATINGS), QStringLiteral("%1").arg(v));
}
if (replaceExisting || targetImage.text(QStringLiteral(META_KEY_WHITEBALANCE)).isEmpty()) {
auto v = whiteBalance();
if (v != WhiteBalance::NotSet)
targetImage.setText(QStringLiteral(META_KEY_WHITEBALANCE), QStringLiteral("%1").arg(quint16(v)));
}
}
bool MicroExif::updateImageResolution(QImage &targetImage)
@@ -1217,7 +1447,7 @@ MicroExif MicroExif::fromByteArray(const QByteArray &ba, bool searchHeader)
idx = std::min(idxLE, idxBE);
else
idx = idxLE > -1 ? idxLE : idxBE;
if(idx > 0)
if (idx > 0)
ba0 = ba0.mid(idx);
}
QBuffer buf;
@@ -1308,7 +1538,7 @@ MicroExif MicroExif::fromImage(const QImage &image)
dt = QDateTime::currentDateTime();
exif.setDateTimeOriginal(dt);
// GPS Info
// GPS info
auto ok = false;
auto alt = image.text(QStringLiteral(META_KEY_ALTITUDE)).toDouble(&ok);
if (ok)
@@ -1322,6 +1552,38 @@ MicroExif MicroExif::fromImage(const QImage &image)
auto dir = image.text(QStringLiteral(META_KEY_DIRECTION)).toDouble(&ok);
if (ok)
exif.setImageDirection(dir);
auto spd = image.text(QStringLiteral(META_KEY_SPEED)).toDouble(&ok);
if (ok)
exif.setImageSpeed(spd);
// EXIF shot info
auto zoom = image.text(QStringLiteral(META_KEY_DIGITALZOOMRATIO)).toDouble(&ok);
if (ok)
exif.setDigitalZoomRatio(zoom);
auto expm = image.text(QStringLiteral(META_KEY_EXPOSUREMODE)).toUShort(&ok);
if (ok)
exif.setExposureMode(ExposureMode(expm));
auto expp = image.text(QStringLiteral(META_KEY_EXPOSUREPROGRAM)).toUShort(&ok);
if (ok)
exif.setExposureProgram(ExposureProgram(expp));
auto expt = image.text(QStringLiteral(META_KEY_EXPOSURETIME)).toDouble(&ok);
if (ok)
exif.setExposureTime(expt);
auto flsh = image.text(QStringLiteral(META_KEY_FLASH)).toUShort(&ok);
if (ok)
exif.setFlash(FlashFlags(flsh));
auto fnum = image.text(QStringLiteral(META_KEY_FNUMBER)).toDouble(&ok);
if (ok)
exif.setFNumber(fnum);
auto flen = image.text(QStringLiteral(META_KEY_FOCALLENGTH)).toDouble(&ok);
if (ok)
exif.setFocalLength(flen);
auto isos = image.text(QStringLiteral(META_KEY_ISOSPEEDRATINGS)).toUShort(&ok);
if (ok)
exif.setIsoSpeedRatings(isos);
auto whtb = image.text(QStringLiteral(META_KEY_WHITEBALANCE)).toUShort(&ok);
if (ok)
exif.setWhiteBalance(WhiteBalance(whtb));
return exif;
}

View File

@@ -24,6 +24,85 @@
#define EXIF_DEFAULT_BYTEORDER QDataStream::BigEndian
#endif
/*!
* \brief The Flash enum
*/
enum class Flash : quint16 {
NotSet = 0,
// Values for bit 0 indicating whether the flash fired.
// 0b = Flash did not fire.
// 1b = Flash fired.
Fired = 1,
// Values for bits 1 and 2 indicating the status of returned light.
// 00b = No strobe return detection function
// 01b = reserved
// 10b = Strobe return light not detected.
// 11b = Strobe return light detected.
ReturnLightNotDetected = 2 << 1,
ReturnLightDetected = 3 << 1,
// Values for bits 3 and 4 indicating the camera's flash mode.
// 00b = unknown
// 01b = Compulsory flash firing
// 10b = Compulsory flash suppression
// 11b = Auto mode
CompulsoryFiring = 1 << 3,
CompulsorySuppression = 2 << 3,
AutoMode = 3 << 3,
// Values for bit 5 indicating the presence of a flash function.
// 0b = Flash function present
// 1b = No flash function
FlashNotAvailable = 1 << 5,
// Values for bit 6 indicating the camera's red-eye mode.
// 0b = No red-eye reduction mode or unknown
// 1b = Red-eye reduction supported
RedEyeReductionSupported = 1 << 6,
};
Q_DECLARE_FLAGS(FlashFlags, Flash)
Q_DECLARE_OPERATORS_FOR_FLAGS(FlashFlags)
/*!
* \brief The ExposureMode enum
*/
enum class ExposureMode : quint16 {
Auto,
Manual,
AutoBracket,
NotSet = 65535
};
/*!
* \brief The ExposureProgram enum
*/
enum class ExposureProgram : quint16 {
NotDefined,
Manual,
Normal,
AperturePriority,
ShutterPriority,
Creative,
Action,
PortraitMode,
LandscapeMode,
NotSet = 65535
};
/*!
* \brief The WhiteBalance enum
*/
enum class WhiteBalance : quint16 {
Auto,
Manual,
NotSet = 65535
};
/*!
* \brief The MicroExif class
* Class to extract / write minimal EXIF data (e.g. resolution, rotation,
@@ -125,7 +204,7 @@ public:
/*!
* \brief transformation
* \return The orientation converted in the equvalent Qt transformation.
* \return The orientation converted in the equivalent Qt transformation.
* \sa orientation
*/
QImageIOHandler::Transformation transformation() const;
@@ -236,6 +315,69 @@ public:
QUuid uniqueId() const;
void setUniqueId(const QUuid &uuid);
/*!
* \brief digitalZoomRatio
* \return The digital zoom ratio when the image was shot or NaN if not set.
*/
double digitalZoomRatio() const;
void setDigitalZoomRatio(double zoom);
/*!
* \brief exposureMode
* \return The exposure mode set when the image was shot. In auto-bracketing mode, the camera shoots a series of frames of the same scene at different exposure settings.
*/
ExposureMode exposureMode() const;
void setExposureMode(const ExposureMode& em);
/*!
* \brief exposureProgram
* \return The class of the program used by the camera to set exposure when the picture is taken.
*/
ExposureProgram exposureProgram() const;
void setExposureProgram(const ExposureProgram& ep);
/*!
* \brief exposureTime
* \return Exposure time, given in seconds (sec) or NaN if not set.
*/
double exposureTime() const;
void setExposureTime(double et);
/*!
* \brief fNumber
* \return The F number or NaN if not set.
*/
double fNumber() const;
void setFNumber(double f);
/*!
* \brief focalLength
* \return The actual focal length of the lens, in mm.
*/
double focalLength() const;
void setFocalLength(double fl);
/*!
* \brief flash
* \return The status of flash when the image was shot.
*/
FlashFlags flash() const;
void setFlash(const FlashFlags& flash);
/*!
* \brief isoSpeedRatings
* \return The sensitivity of the camera or input device when the image was shot.
*/
quint16 isoSpeedRatings() const;
void setIsoSpeedRatings(quint16 iso);
/*!
* \brief whiteBalance
* \return The white balance mode set when the image was shot.
*/
WhiteBalance whiteBalance() const;
void setWhiteBalance(const WhiteBalance& wb);
/*!
* \brief latitude
* \return Floating-point number indicating the latitude in degrees north of the equator (e.g. 27.717) or NaN if not set.
@@ -258,6 +400,13 @@ public:
double altitude() const;
void setAltitude(double meters);
/*!
* \brief imageSpeed
* \return The speed in Km/h or NaN if not set.
*/
double imageSpeed() const;
void setImageSpeed(double kmh);
/*!
* \brief imageDirection
* \param isMagnetic Set to true if the direction is relative to magnetic north, false if it is relative to true north. Leave nullptr if is not of interest.

View File

@@ -546,8 +546,21 @@ static bool setColorSpace(QImage &img, const PSDImageResourceSection &irs)
auto cs = QColorSpace::fromIccProfile(irb.data);
if (!cs.isValid())
return false;
if (cs.colorModel() == QColorSpace::ColorModel::Gray && img.pixelFormat().colorModel() != QPixelFormat::Grayscale) {
// I created an RGB from a grayscale without using color profile conversion (fast).
// I'll try to create an RGB profile that looks the same.
if (cs.transferFunction() != QColorSpace::TransferFunction::Custom) {
auto tmp = QColorSpace(QColorSpace::Primaries::SRgb, cs.transferFunction(), cs.gamma());
tmp.setWhitePoint(cs.whitePoint());
tmp.setDescription(QStringLiteral("RGB emulation of \"%1\"").arg(cs.description()));
if (tmp.isValid())
cs = tmp;
}
}
img.setColorSpace(cs);
return true;
return img.colorSpace().isValid();
}
/*!
@@ -627,7 +640,7 @@ static bool setResolution(QImage &img, const PSDImageResourceSection &irs)
s.skipRawData(4); // Display data (not used here)
s >> i32; // Vertial resolution in pixels per inch.
s >> i32; // Vertical resolution in pixels per inch.
if (i32 <= 0)
return false;
auto vres = dpi2ppm(fixedPointToDouble(i32));
@@ -800,6 +813,14 @@ static QImage::Format imageFormat(const PSDHeader &header, bool alpha)
}
break;
case CM_GRAYSCALE:
if (header.depth == 32) {
format = !alpha ? QImage::Format_RGBX32FPx4 : QImage::Format_RGBA32FPx4_Premultiplied;
} else if (header.depth == 16) {
format = !alpha ? QImage::Format_Grayscale16 : QImage::Format_RGBA64_Premultiplied;
} else {
format = !alpha ? QImage::Format_Grayscale8 : QImage::Format_RGBA8888_Premultiplied;
}
break;
case CM_DUOTONE:
format = header.depth == 8 ? QImage::Format_Grayscale8 : QImage::Format_Grayscale16;
break;
@@ -996,7 +1017,7 @@ inline void rawChannelCopy(uchar *target, qint32 targetChannels, qint32 targetCh
template<class T>
inline void cmykToRgb(uchar *target, qint32 targetChannels, const char *source, qint32 sourceChannels, qint32 width, bool alpha = false)
inline bool cmykToRgb(uchar *target, qint32 targetChannels, const char *source, qint32 sourceChannels, qint32 width, bool alpha = false)
{
auto s = reinterpret_cast<const T*>(source);
auto t = reinterpret_cast<T*>(target);
@@ -1005,7 +1026,7 @@ inline void cmykToRgb(uchar *target, qint32 targetChannels, const char *source,
if (sourceChannels < 2) {
qCDebug(LOG_PSDPLUGIN) << "cmykToRgb: image is not a valid MCH/CMYK!";
return;
return false;
}
for (qint32 w = 0; w < width; ++w) {
@@ -1026,6 +1047,7 @@ inline void cmykToRgb(uchar *target, qint32 targetChannels, const char *source,
*(pt + 3) = std::numeric_limits<T>::max();
}
}
return true;
}
inline double finv(double v)
@@ -1045,7 +1067,7 @@ inline double gammaCorrection(double linear)
}
template<class T>
inline void labToRgb(uchar *target, qint32 targetChannels, const char *source, qint32 sourceChannels, qint32 width, bool alpha = false)
inline bool labToRgb(uchar *target, qint32 targetChannels, const char *source, qint32 sourceChannels, qint32 width, bool alpha = false)
{
auto s = reinterpret_cast<const T*>(source);
auto t = reinterpret_cast<T*>(target);
@@ -1054,7 +1076,7 @@ inline void labToRgb(uchar *target, qint32 targetChannels, const char *source, q
if (sourceChannels < 3) {
qCDebug(LOG_PSDPLUGIN) << "labToRgb: image is not a valid LAB!";
return;
return false;
}
for (qint32 w = 0; w < width; ++w) {
@@ -1089,6 +1111,7 @@ inline void labToRgb(uchar *target, qint32 targetChannels, const char *source, q
*(pt + 3) = std::numeric_limits<T>::max();
}
}
return true;
}
bool readChannel(QByteArray &target, QDataStream &stream, quint32 compressedSize, quint16 compression)
@@ -1102,7 +1125,7 @@ bool readChannel(QByteArray &target, QDataStream &stream, quint32 compressedSize
if (stream.readRawData(tmp.data(), tmp.size()) != tmp.size()) {
return false;
}
if (packbitsDecompress(tmp.data(), tmp.size(), target.data(), target.size()) < 0) {
if (packbitsDecompress(tmp.data(), tmp.size(), target.data(), target.size()) != target.size()) {
return false;
}
} else if (stream.readRawData(target.data(), target.size()) != target.size()) {
@@ -1309,7 +1332,6 @@ bool PSDHandler::read(QImage *image)
}
auto imgChannels = imageChannels(img.format());
auto channel_num = std::min(qint32(header.channel_count), imgChannels);
auto raw_count = qsizetype(header.width * header.depth + 7) / 8;
auto native_cmyk = img.format() == CMYK_FORMAT;
@@ -1360,8 +1382,10 @@ bool PSDHandler::read(QImage *image)
#if !defined(PSD_NATIVE_CMYK_SUPPORT_DISABLED)
if (header.color_mode == CM_CMYK && img.format() != QImage::Format_CMYK8888) {
auto tmpi = QImage(header.width, 1, QImage::Format_CMYK8888);
if (setColorSpace(tmpi, irs))
if (setColorSpace(tmpi, irs)) {
tmpi.fill(0);
tmpCmyk = tmpi;
}
iccConv.setTargetColorSpace(QColorSpace(QColorSpace::SRgb));
}
#endif
@@ -1415,15 +1439,26 @@ bool PSDHandler::read(QImage *image)
else if (header.depth == 32)
premulConversion<float>(scanLine, header.width, 3, header.channel_count, PremulConversion::PS2P);
}
if (header.color_mode == CM_GRAYSCALE) {
if (header.depth == 8)
premulConversion<quint8>(scanLine, header.width, 1, header.channel_count, PremulConversion::PS2P);
else if (header.depth == 16)
premulConversion<quint16>(scanLine, header.width, 1, header.channel_count, PremulConversion::PS2P);
else if (header.depth == 32)
premulConversion<float>(scanLine, header.width, 1, header.channel_count, PremulConversion::PS2P);
}
}
// Conversion to RGB
if (header.color_mode == CM_CMYK || header.color_mode == CM_MULTICHANNEL) {
if (tmpCmyk.isNull()) {
if (header.depth == 8)
cmykToRgb<quint8>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha);
else if (header.depth == 16)
cmykToRgb<quint16>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha);
if (header.depth == 8) {
if (!cmykToRgb<quint8>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha))
return false;
} else if (header.depth == 16) {
if (!cmykToRgb<quint16>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha))
return false;
}
} else if (header.depth == 8) {
rawChannelsCopyToCMYK<quint8>(tmpCmyk.bits(), 4, psdScanline.data(), header.channel_count, header.width);
if (auto rgbPtr = iccConv.convertedScanLine(tmpCmyk, 0))
@@ -1439,10 +1474,13 @@ bool PSDHandler::read(QImage *image)
}
}
if (header.color_mode == CM_LABCOLOR) {
if (header.depth == 8)
labToRgb<quint8>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha);
else if (header.depth == 16)
labToRgb<quint16>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha);
if (header.depth == 8) {
if (!labToRgb<quint8>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha))
return false;
} else if (header.depth == 16) {
if (!labToRgb<quint16>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha))
return false;
}
}
if (header.color_mode == CM_RGB) {
if (header.depth == 8)
@@ -1452,9 +1490,21 @@ bool PSDHandler::read(QImage *image)
else if (header.depth == 32)
rawChannelsCopy<float>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width);
}
if (header.color_mode == CM_GRAYSCALE) {
for (auto c = 0; c < imgChannels; ++c) { // GRAYA to RGBA
auto sc = qBound(0, c - 2, int(header.channel_count));
if (header.depth == 8)
rawChannelCopy<quint8>(img.scanLine(y), imgChannels, c, psdScanline.data(), header.channel_count, sc, header.width);
else if (header.depth == 16)
rawChannelCopy<quint16>(img.scanLine(y), imgChannels, c, psdScanline.data(), header.channel_count, sc, header.width);
else if (header.depth == 32)
rawChannelCopy<float>(img.scanLine(y), imgChannels, c, psdScanline.data(), header.channel_count, sc, header.width);
}
}
}
} else {
// Linear read (no position jumps): optimized code usable only for the colorspaces supported by QImage
auto channel_num = std::min(qint32(header.channel_count), header.color_mode == CM_GRAYSCALE ? 1 : imgChannels);
for (qint32 c = 0; c < channel_num; ++c) {
for (qint32 y = 0, h = header.height; y < h; ++y) {
auto&& strideSize = strides.at(c * qsizetype(h) + y);
@@ -1483,8 +1533,13 @@ bool PSDHandler::read(QImage *image)
// 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);
if (imgChannels >= 3) { // GRAY to RGB
planarToChunchy<float>(scanLine, rawStride.data(), header.width, 0, imgChannels);
planarToChunchy<float>(scanLine, rawStride.data(), header.width, 1, imgChannels);
planarToChunchy<float>(scanLine, rawStride.data(), header.width, 2, imgChannels);
} else { // 32-bits float images: Grayscale (converted to equivalent integer 16-bits)
planarToChunchyFloatToUInt16<float>(scanLine, rawStride.data(), header.width, c, imgChannels);
}
}
}
}
@@ -1620,6 +1675,9 @@ bool PSDHandler::canRead(QIODevice *device)
if (header.color_mode == CM_RGB && header.channel_count > 3) {
return false; // supposing extra channel as alpha
}
if (header.color_mode == CM_GRAYSCALE && (header.channel_count > 1 || header.depth == 32)) {
return false; // supposing extra channel as alpha
}
}
return IsSupported(header);

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