Compare commits

..

37 Commits

Author SHA1 Message Date
d332ad717b Update dependency version to 6.19.0 2025-10-03 15:04:37 +02:00
1ca7baed98 Fix assert on broken data
The nan eventually ends up in qRound inside Qt code.
That asserts because it doesn't know what to do with a nan
2025-10-02 01:02:08 +02:00
f1b0c9f0ec add const so we know that QImage is not being modified 2025-10-01 19:15:10 +02:00
b83f4c0231 Add color space check during read test 2025-09-27 11:12:50 +02:00
a457e5ddcb Limits the max RAW size to 300000x300000 pixels 2025-09-21 15:13:17 +02:00
f28cd98661 Limits the max DDS size to 300000x300000 pixels 2025-09-21 11:28:45 +02:00
05c3a1afe6 Fix compilation failure with Qt 6.7 2025-09-20 13:37:52 +08:00
fda751c641 Switch all plugins to QLoggingCategory 2025-09-19 10:00:26 +02:00
a4e18734bd Resolution calculations performed by functions
Added functions for dpi <-> ppm transformations and used in all plugins.
2025-09-17 12:22:44 +02:00
14286a6ab0 Add a CI job for oss-fuzz 2025-09-13 10:15:10 +00:00
945fd6f0ce GIT_SILENT: Bump kf ecm_set_disabled_deprecation_versions. Make sure that it compiles fine without kf 6.18 deprecated methods 2025-09-13 08:53:25 +02:00
c36b4e2350 Use std::lround instead of qRound
qRound will assert if the resulting integer is out of range, by using
lround we can ask if the rounding range failed
2025-09-11 13:50:50 +02:00
95f0d15e14 RAW: Disable broken stream protection
It seems that on BSD the definition introduced with MR !384 crashes Telegram.
2025-09-11 11:52:21 +02:00
56c8bc7323 Add checks on the seek return value 2025-09-09 22:20:34 +02:00
08e178f098 Fix Null-dereference READ 2025-09-09 21:15:19 +02:00
6881e3111b HDR: Limits the header to the first 128 lines
The HDR header is a set of text information spread across a few lines. 
According to the specifications I have, the parameters seem to be a total of 7 / 8 but there could be more. This patch limits the header to 128 lines of maximum 1024 bytes.

Closes #40
2025-09-09 13:59:14 +00:00
463da81fad IFF: support for PCHG chunk
Highlights:
- Adds support for a new palette changer chunk. Some test cases attached to #38 .
- Fixes the reading of ILBMs with the mask (test case: [cyclone.iff](/uploads/d8734d2155fd0d21f7b003b37e0d1259/cyclone.iff)).
- Adds support for HAM5 encoding.
- Adds more test cases created using [HAM Converter](http://mrsebe.bplaced.net/blog/wordpress/).
- Adds support for Atari STE RAST chunk outside FORM one (test case: [fish.iff](/uploads/c461cf4b6a1423cec60fbce645d9fd07/fish.iff)).

NOTE: I contacted Sebastiano Vigna, the author of the PCHG chunk specifications, and he provided me with:
- Some images to test the code (but I can't include them in the test cases).
- Permission to use [his code](https://vigna.di.unimi.it/amiga/PCHGLib.zip) without restrictions: Huffman decompression was achieved by converting `FastDecomp.a` via AI.

Closes #38
2025-09-08 15:39:50 +00:00
8036b1d032 Fix assert when read corrupted floats 2025-09-08 11:01:06 +02:00
71cc137254 Update version to 6.19.0 2025-09-08 10:23:10 +02:00
7858c4eeec Update dependency version to 6.18.0 2025-09-07 14:25:38 +02:00
eae41980b2 dds: Fix assert when reading broken data
oss-fuzz testcase 6027629841154048
2025-09-06 22:39:16 +00:00
3bf2281610 GIT_SILENT: Use Qt CamelCase includes 2025-09-04 08:32:52 +02:00
59089855fa Unified maximum pixel value for large image plugins 2025-08-30 09:17:58 +02:00
5a067130af Removed the conversion of the entire image to a compatible format when saving from the PCX, PIC, and RGB plugins.
For example, the PCX plugin converts all RGB images to RGB(A)32. So if you try to save a 1 GiB RGB888 image, it will be converted to RGB32 image so, you need additional 1.25GiB of ram. The conversion now occurs line by line, significantly saving memory.
2025-08-30 09:17:40 +02:00
9c6c0c01ae TGA: Support for TGA specification 2.0
Adds TGA 2.0 compliance:
- Support for Extension Area, Developer Area and Footer (metadata support)
- Support for 15-bit and 16-bit per pixel images (both RGB and Indexed)
- Full support for rotation on reading (we cannot use Qt transformations because only a subset is part of the TGA specification)
- When writing you can choose the supported version (subType)
- Improved writing speed (approximately 10 times) and removed whole image conversions (significant memory savings)

It pass the [TrueVision TGA 2.0 conformance suite](https://github.com/zigimg/test-suite/tree/master/fixtures/tga).

Test changes:
- Read test: added ability to skip a specific test on sequential devices (via JSON behavior file)
- Write test: added the ability to set the subType when writing (via JSON properties file)

Closes #37
2025-08-23 09:20:09 +00:00
f933cbe12d Fix possible buffer overflow on corrupted images 2025-08-22 08:08:17 +02:00
ebc366b3c5 TGA: memory optimizations and native grayscale support
- Removed temporary buffer of uncompressed image size when reading (loads images using half the memory)
- Added native support for Grayscale images (they are no longer converted to RGB(A) 32 when reading/writing)
- Fixes wrong X channel value on RGBX32 images (CCBUG: 499584)

It should also reduce loading times for corrupted images.

Closes #33
2025-08-20 21:53:00 +00:00
bc8b5b56b1 Move fuzz target and build script into KImageFormats repository 2025-08-20 22:57:06 +05:30
35a1ed0227 GIT_SILENT: Bump kf ecm_set_disabled_deprecation_versions. Make sure that it compiles fine without kf 6.17 deprecated methods 2025-08-15 08:29:20 +02:00
f63b082c85 IFF: add support for a different palette per line 2025-08-12 08:28:31 +02:00
68cc915132 IFF: Fix possible undefined-shift 2025-08-09 11:46:05 +02:00
e912a4ac9b RAW: Error out on malformed files
Internally, LibRaw often doesn't check the return values of various functions. It's not uncommon to find things like:

fseek(ifp, oAtom, SEEK_SET);
szAtom = get4();
... (more read operations without checking the result)

This means is up to us to error our properly when something went wrong. We do that by keeping an error counter and calling IOERROR once we've reached enough errors. IOERROR will throw an exception that will be internally caught by libraw itself.
2025-08-07 20:39:55 +00:00
480ea8dba0 IFF: add support for RGBN/RGB8 image data and CAT chunk
Add the following features:
- RGB8 image data support (test case added)
- RGBN image data support ([Clouds.iff](/uploads/9db869350f74421bf1813fa7d4332f4f/Clouds.iff))
- CAT chunk support: you can have more than one image for file (test case added)
- Image transformation support via EXIF data

[RGBN/RGB8](https://wiki.amigaos.net/wiki/RGBN_and_RGB8_IFF_Image_Data) files are used in Impulse's Turbo Silver and Imagine.

Closes #34
2025-08-07 20:17:09 +00:00
37e3c65a05 ILBM 64-bit extension support 2025-08-04 13:47:00 +02:00
4b44f8474f Fix oss-fuzz compilation error 2025-08-04 13:46:16 +02:00
c2a1d4b401 IFF: Fix halfbride detection, 1-bitplane colors and PBM line size calculation. It also ignore ZBuffer flag on Maya images (like Photoshop does) amd adds CMYK palette support. 2025-08-01 15:29:52 +02:00
cd39c36621 Update version to 6.18.0 2025-08-01 13:33:36 +02:00
121 changed files with 4988 additions and 1003 deletions

View File

@ -12,6 +12,7 @@ include:
- /gitlab-templates/windows-qt6.yml
- /gitlab-templates/xml-lint.yml
- /gitlab-templates/yaml-lint.yml
- /gitlab-templates/oss-fuzz.yml
image_json_validate:
stage: validate

View File

@ -1,11 +1,11 @@
cmake_minimum_required(VERSION 3.16)
set(KF_VERSION "6.17.0") # handled by release scripts
set(KF_DEP_VERSION "6.17.0") # handled by release scripts
set(KF_VERSION "6.19.0") # handled by release scripts
set(KF_DEP_VERSION "6.19.0") # handled by release scripts
project(KImageFormats VERSION ${KF_VERSION})
include(FeatureSummary)
find_package(ECM 6.17.0 NO_MODULE)
find_package(ECM 6.19.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)
@ -100,7 +100,7 @@ add_feature_info(LibJXR LibJXR_FOUND "required for the QImage plugin for JPEG XR
ecm_set_disabled_deprecation_versions(
QT 6.10.0
KF 6.16.0
KF 6.18.0
)
add_subdirectory(src)

202
LICENSES/Apache-2.0.txt Normal file
View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -56,11 +56,17 @@ submit the plugin directly to the Qt Project.
To be accepted, contributions must:
- Contain the test images needed to verify that the changes work correctly.
- Pass the tests successfully.
- Use Qt logging categories for Debug messages.
For more info about tests, see also [Autotests README](autotests/README.md).
## Duplicated Plugins
> [!important]
> To ensure you are using the correct plugin, the unwanted one should be
renamed or deleted. If several plugins support the same capability, Qt will
select one arbitrarily.
### The TGA plugin
The TGA plugin supports more formats than Qt's own TGA plugin;
@ -212,31 +218,36 @@ RGB.
Where possible, plugins support large images. By convention, many of the
large image plugins are limited to a maximum of 300,000 x 300,000 pixels.
Anyway, all plugins are also limited by the
`QImageIOReader::allocationLimit()`. Below are the maximum sizes for each
plugin ('n/a' means no limit, i.e. the limit depends on the format encoding).
Anyway, all plugins are also limited by the
`QImageIOReader::allocationLimit()`.
> [!note]
> You can change the maximum limit of 300000 pixels by setting the constant
> `KIF_LARGE_IMAGE_PIXEL_LIMIT` to the desired value in the cmake file.
Below are the maximum sizes for each plugin ('n/a' means no limit, i.e. the
limit depends on the format encoding).
- ANI: n/a
- AVIF: 32,768 x 32,768 pixels, in any case no larger than 256 megapixels
- DDS: n/a
- DDS: 300,000 x 300,000 pixels
- EXR: 300,000 x 300,000 pixels
- EPS: n/a
- HDR: n/a (large image)
- EPS: same size as Qt's JPG plugin
- HDR: 300,000 x 300,000 pixels
- HEIF: n/a
- IFF: 65,535 x 65,535 pixels
- JP2: 300,000 x 300,000 pixels, in any case no larger than 2 gigapixels
- JXL: 262,144 x 262,144 pixels, in any case no larger than 256 megapixels
- JXR: n/a, in any case no larger than 4 GB
- JXR: 300,000 x 300,000 pixels, in any case no larger than 4 GB
- KRA: same size as Qt's PNG plugin
- ORA: same size as Qt's PNG plugin
- PCX: 65,535 x 65,535 pixels
- PFM: n/a (large image)
- PFM: 300,000 x 300,000 pixels
- PIC: 65,535 x 65,535 pixels
- PSD: 300,000 x 300,000 pixels
- PXR: 65,535 x 65,535 pixels
- QOI: 300,000 x 300,000 pixels
- RAS: n/a (large image)
- RAW: n/a (depends on the RAW format loaded)
- RAS: 300,000 x 300,000 pixels
- RAW: 65,535 x 65,535 pixels
- RGB: 65,535 x 65,535 pixels
- SCT: 300,000 x 300,000 pixels
- TGA: 65,535 x 65,535 pixels
@ -317,6 +328,29 @@ plugin:
- `HDR_HALF_QUALITY`: on read, a 16-bit float image is returned instead of a
32-bit float one.
### The IFF plugin
Interchange File Format is a chunk-based format. Since the original 1985
version, various extensions have been created over time.
The plugin supports the following image data:
- FORM ILBM (Interleaved Bitmap): Electronic Arts IFF standard for
Interchange File Format (EA IFF 1985). ILBM is a format to handle raster
images, specifically an InterLeaved bitplane BitMap image with color map.
It supports from 1 to 8-bit indexed images with HAM, Halfbride, and normal
encoding. It also supports interleaved 24-bit RGB and 32-bit RGBA
extension without color map.
- FORM ILBM 64: ILBM extension to support 48-bit RGB and 64-bit RGBA encoding.
- FORM ACBM (Amiga Contiguous BitMap): It supports uncompressed ACBMs by
converting them to ILBMs at runtime.
- FORM RGBN / RGB8: It supports 13-bit and 25-bit RGB images with compression
type 4.
- FORM PBM: PBM is a chunky version of IFF pictures. It supports 8-bit images
with color map only.
- FOR4 CIMG (Maya Image File Format): It supports 24/48-bit RGB and 32/64-bit
RGBA images.
### The JP2 plugin
**This plugin can be disabled by setting `KIMAGEFORMATS_JP2` to `OFF`
@ -399,6 +433,25 @@ selectively change the conversion (see also [raw_p.h](./src/imageformats/raw_p.h
The default setting tries to balance quality and conversion speed.
### The TGA plugin
TGA plugin supports both version 1 and version 2 of TGA files. When writing,
it is possible to force which version to use by setting the following subtypes:
- `TGAv1`: force TGA v1.0. No metadata.
- `TGAv2` (default): force TGA v2.0 (strict). Adds the TGA Extension Area.
- `TGAv2E`: force TGA v2.0 (enhanced). Same as TGA v2.0 (strict) but with the
addition of the TGA v2.0 Developer Area with info like, for e.g., Exif data,
XMP packet and the ICC profile.
They are all TGA specs compliant. While for versions 1 and 2 (strict) it is
possible to decode all the information with the TGA specification alone, for
version 2 (enhanced) it is necessary to know how the additional data is
encoded.
The following defines can be defined in cmake to modify the behavior of the
plugin:
- `TGA_V2E_AS_DEFAULT`: change the default version of the plugin to `TGAv2E`.
### The XCF plugin
XCF support has the following limitations:

View File

@ -189,7 +189,7 @@ kimageformats_write_tests(
pic-lossless
qoi-lossless
rgb-lossless
tga # fixme: the alpha images appear not to be written properly
tga-nodatacheck
)
# EPS read tests depend on the vagaries of GhostScript

View File

@ -88,30 +88,37 @@ are iterated sequentially and the first object that matches the requirements
is used for testing (successes are ignored).
Supported values for JSON objects:
- `comment`: Type string. A string shown by the test when a condition occurs.
- `description`: Type string. A description of the object. Not used by the
- `colorSpace` : Type `object`. An object with the `description` (type
`string`), `primaries` (type `string`), `transferFunction` (type `string`),
`colorModel` (type `string`) and `gamma` (type `double`) values of the
image color space.
- `comment`: Type `string`. A string shown by the test when a condition occurs.
- `description`: Type `string`. A description of the object. Not used by the
test.
- `disableAutoTransform`: Type boolean. By default, tests are run with
- `disableAutoTransform`: Type `boolean`. By default, tests are run with
autotransform enabled (i.e. rotation is applied if the plugin supports it).
Set to `true` to disable autotransform.
- `filename`: Type string. Name of the template file to use. E.g.
- `fileName`: Type `string`. Name of the template file to use. E.g.
"testRGB_Qt_6_2.png".
- `fuzziness`: Type integer. Set the fuzziness only if not already set on the
- `fuzziness`: Type `integer`. Set the fuzziness only if not already set on the
command line. The value set on the command line wins over the one in the JSON
file.
- `maxQtVersion`: Type string. Maximum Qt version this object is compatible
- `maxQtVersion`: Type `string`. Maximum Qt version this object is compatible
with (if not set means all). E.g. "6.2.99".
- `metadata`: Type Array. An array of key/value objects (string type)
- `metadata`: Type `array`. An array of key/value objects (string type)
containing the image metadata as returned by `QImage::text`.
- `minQtVersion`: Type string. Minimum Qt version this object is compatible
- `minQtVersion`: Type `string`. Minimum Qt version this object is compatible
with (if not set means all). E.g. "6.2.0".
- `perceptiveFuzziness` Type boolean. Set the perceptive fuzziness only if not
- `perceptiveFuzziness` Type `boolean`. Set the perceptive fuzziness only if not
already set on the command line. The value set on the command line wins over
the one in the JSON file.
- `resolution`: Type object. An object with the `dotsPerMeterX` and
`dotsPerMeterY` (integer) values of the image.
- `seeAlso`: Type string. More info about the object. Normally used to point
- `resolution`: Type `object`. An object with the `dotsPerMeterX` (type `integer`)
and `dotsPerMeterY` (type `integer`) values of the image.
- `seeAlso`: Type `string`. More info about the object. Normally used to point
to bug reports. Not used by the test.
- `skipSequential`: Type `boolean`. Skip the test on sequential access device.
Some plugins may have limited functionality on sequential devices (e.g.,
not reading metadata).
- `unsupportedFormat`: Type `boolean`. When true, the test is skipped.
Some examples:
@ -120,6 +127,7 @@ Some examples:
- Example 3: [Metadata](read/psd/metadata.psd.json)
- Example 4: [Check Qt version, resolution and metadata](read/psd/mch-16bits.psd.json)
- Example 5: [Fuzziness setting](read/xcf/birthday16.xcf.json)
- Example 6: [Color space](read/dds/rgba_f16.dds.json)
These are just a few examples. More examples can be found in the test folders.
@ -169,11 +177,12 @@ See also [Add a test to CMakeLists.txt](#add-a-test-to-cmakeliststxt).
The properties file must be located in `write/basic` and must have the name
of the file format (e.g. jxl.json). It is a JSON object composed of the
following values:
- `format`: Type string. The format tested.
- `metadata`: Type Array. An array of key/value objects (string type)
- `format`: Type `string`. The format tested.
- `metadata`: Type `array`. An array of key/value objects (string type)
containing the image metadata as returned by `QImage::text`.
- `resolution`: Type object. An object with the `dotsPerMeterX` and `
- `resolution`: Type `object`. An object with the `dotsPerMeterX` and `
dotsPerMeterY` (integer) values of the image.
- `subType`: type `string`. The image writer subtype to set when testing.
[This is an example](write/basic/jxl.json) of property file.
@ -230,9 +239,7 @@ kimageformats_write_tests(FUZZ 1
Plugins are also tested with [OSS-Fuzz](https://google.github.io/oss-fuzz/)
project to identify possible security issues. When adding a new plugin it is
also necessary to add it to the test in the [kimageformats
project](https://github.com/google/oss-fuzz/tree/master/projects/kimageformats)
on OSS-Fuzz.
also necessary to add it to the test in the [ossfuzz](ossfuzz) directory.
## TODO

View File

@ -0,0 +1,194 @@
#!/bin/bash -eu
#
# SPDX-FileCopyrightText: 2020 Google LLC
# SPDX-License-Identifier: Apache-2.0
#
# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
################################################################################
# build zstd
cd $SRC/zstd
cmake -S build/cmake -DBUILD_SHARED_LIBS=OFF
make install -j$(nproc)
# Build zlib
cd $SRC/zlib
./configure --static
make install -j$(nproc)
# Build bzip2
# Inspired from ../bzip2/build
cd $SRC
tar xzf bzip2-*.tar.gz && rm -f bzip2-*.tar.gz
cd bzip2-*
SRCL=(blocksort.o huffman.o crctable.o randtable.o compress.o decompress.o bzlib.o)
for source in ${SRCL[@]}; do
name=$(basename $source .o)
$CC $CFLAGS -c ${name}.c
done
rm -f libbz2.a
ar cq libbz2.a ${SRCL[@]}
cp -f bzlib.h /usr/local/include
cp -f libbz2.a /usr/local/lib
# Build xz
export ORIG_CFLAGS="${CFLAGS}"
export ORIG_CXXFLAGS="${CXXFLAGS}"
unset CFLAGS
unset CXXFLAGS
cd $SRC/xz
./autogen.sh --no-po4a --no-doxygen
./configure --enable-static --disable-debug --disable-shared --disable-xz --disable-xzdec --disable-lzmainfo
make install -j$(nproc)
export CFLAGS="${ORIG_CFLAGS}"
export CXXFLAGS="${ORIG_CXXFLAGS}"
# Build qt
cd $SRC/qtbase
./configure -no-glib -qt-libpng -qt-pcre -opensource -confirm-license -static -no-opengl -no-icu -platform linux-clang-libc++ -debug -prefix /usr -no-feature-widgets -no-feature-sql -no-feature-network -no-feature-xml -no-feature-dbus -no-feature-printsupport
cmake --build . --parallel $(nproc)
cmake --install .
# Build extra-cmake-modules
cd $SRC/extra-cmake-modules
cmake . -DBUILD_TESTING=OFF
make install -j$(nproc)
cd $SRC/karchive
rm -rf poqm
cmake . -DBUILD_SHARED_LIBS=OFF -DBUILD_TESTING=OFF -DCMAKE_INSTALL_PREFIX=/usr/local
make install -j$(nproc)
# Build JXRlib
cd $SRC/jxrlib
make -j$(nproc)
# Build LibRaw
cd $SRC/LibRaw
TMP_CFLAGS=$CFLAGS
TMP_CXXFLAGS=$CXXFLAGS
CFLAGS="$CFLAGS -fno-sanitize=function,vptr"
CXXFLAGS="$CXXFLAGS -fno-sanitize=function,vptr"
autoreconf --install
./configure --disable-examples
make -j$(nproc)
make install -j$(nproc)
CFLAGS=$TMP_CFLAGS
CXXFLAGS=$TMP_CXXFLAGS
# Build aom
cd $SRC/aom
mkdir build.libavif
cd build.libavif
extra_libaom_flags='-DAOM_MAX_ALLOCABLE_MEMORY=536870912 -DDO_RANGE_CHECK_CLAMP=1'
cmake -DBUILD_SHARED_LIBS=0 -DENABLE_DOCS=0 -DENABLE_EXAMPLES=0 -DENABLE_TESTDATA=0 -DENABLE_TESTS=0 -DENABLE_TOOLS=0 -DCONFIG_PIC=1 -DAOM_TARGET_CPU=generic -DCONFIG_SIZE_LIMIT=1 -DDECODE_HEIGHT_LIMIT=12288 -DDECODE_WIDTH_LIMIT=12288 -DAOM_EXTRA_C_FLAGS="${extra_libaom_flags}" -DAOM_EXTRA_CXX_FLAGS="${extra_libaom_flags}" ..
make -j$(nproc)
make install -j$(nproc)
# Build libavif
cd $SRC/libavif
ln -s "$SRC/aom" "$SRC/libavif/ext/"
mkdir build
cd build
CFLAGS="$CFLAGS -fPIC" cmake -DBUILD_SHARED_LIBS=OFF -DAVIF_ENABLE_WERROR=OFF -DAVIF_CODEC_AOM=LOCAL -DAVIF_LIBYUV=OFF ..
make -j$(nproc)
# Build libde265
cd $SRC/libde265
cmake -DBUILD_SHARED_LIBS=OFF -DDISABLE_SSE=ON .
make -j$(nproc)
make install -j$(nproc)
# Build openjpeg
cd $SRC/openjpeg
mkdir build
cd build
cmake -DBUILD_SHARED_LIBS=OFF -DBUILD_STATIC_LIBS=ON -DBUILD_CODEC=OFF ..
make -j$(nproc)
make install -j$(nproc)
# build openh264
cd $SRC/openh264
make USE_ASM=No BUILDTYPE=Debug install-static -j$(nproc)
# Build openexr
cd $SRC/openexr
mkdir _build
cd _build
cmake -DBUILD_SHARED_LIBS=OFF ..
make -j$(nproc)
make install -j$(nproc)
# Build libheif
cd $SRC/libheif
#Reduce max width and height to avoid allocating too much memory
sed -i "s/static const int MAX_IMAGE_WIDTH = 32768;/static const int MAX_IMAGE_WIDTH = 8192;/g" libheif/security_limits.h
sed -i "s/static const int MAX_IMAGE_HEIGHT = 32768;/static const int MAX_IMAGE_HEIGHT = 8192;/g" libheif/security_limits.h
mkdir build
cd build
cmake -DBUILD_SHARED_LIBS=OFF -DENABLE_PLUGIN_LOADING=OFF -DWITH_DAV1D=OFF -DWITH_EXAMPLES=OFF -DWITH_LIBDE265=ON -DWITH_RAV1E=OFF -DWITH_RAV1E_PLUGIN=OFF -DWITH_SvtEnc=OFF -DWITH_SvtEnc_PLUGIN=OFF -DWITH_X265=OFF -DWITH_OpenJPEG_DECODER=ON -DWITH_OpenH264_DECODER=ON ..
make -j$(nproc)
make install -j$(nproc)
# Build libjxl
cd $SRC/libjxl
mkdir build
cd build
CXXFLAGS="$CXXFLAGS -DHWY_COMPILE_ONLY_SCALAR" cmake -DBUILD_SHARED_LIBS=OFF -DBUILD_TESTING=OFF -DJPEGXL_ENABLE_BENCHMARK=OFF -DJPEGXL_ENABLE_DOXYGEN=OFF -DJPEGXL_ENABLE_EXAMPLES=OFF -DJPEGXL_ENABLE_JNI=OFF -DJPEGXL_ENABLE_JPEGLI=OFF -DJPEGXL_ENABLE_JPEGLI_LIBJPEG=OFF -DJPEGXL_ENABLE_MANPAGES=OFF -DJPEGXL_ENABLE_OPENEXR=OFF -DJPEGXL_ENABLE_PLUGINS=OFF -DJPEGXL_ENABLE_SJPEG=OFF -DJPEGXL_ENABLE_SKCMS=ON -DJPEGXL_ENABLE_TCMALLOC=OFF -DJPEGXL_ENABLE_TOOLS=OFF -DJPEGXL_ENABLE_FUZZERS=OFF ..
make -j$(nproc) jxl jxl_cms jxl_threads
cd $SRC/kimageformats
HANDLER_TYPES="ANIHandler ani
QAVIFHandler avif
QDDSHandler dds
EXRHandler exr
HDRHandler hdr
HEIFHandler heif
IFFHandler iff
JP2Handler jp2
QJpegXLHandler jxl
JXRHandler jxr
KraHandler kra
OraHandler ora
PCXHandler pcx
PFMHandler pfm
SoftimagePICHandler pic
PSDHandler psd
PXRHandler pxr
QOIHandler qoi
RASHandler ras
RAWHandler raw
RGBHandler rgb
ScitexHandler sct
TGAHandler tga
XCFHandler xcf"
echo "$HANDLER_TYPES" | while read class format; do
(
fuzz_target_name=kimgio_${format}_fuzzer
/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
# -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.
find . -name "*.${format}" | zip -q $OUT/${fuzz_target_name}_seed_corpus.zip -@
)
done

View File

@ -0,0 +1,76 @@
/*
# SPDX-FileCopyrightText: 2018 Google Inc.
# SPDX-License-Identifier: Apache-2.0
#
# Copyright 2018 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
################################################################################
*/
/*
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
*/
#include <QBuffer>
#include <QCoreApplication>
#include <QImage>
#include "ani_p.h"
#include "avif_p.h"
#include "dds_p.h"
#include "exr_p.h"
#include "hdr_p.h"
#include "heif_p.h"
#include "iff_p.h"
#include "jp2_p.h"
#include "jxl_p.h"
#include "jxr_p.h"
#include "kra.h"
#include "ora.h"
#include "pcx_p.h"
#include "pfm_p.h"
#include "pic_p.h"
#include "psd_p.h"
#include "pxr_p.h"
#include "qoi_p.h"
#include "ras_p.h"
#include "raw_p.h"
#include "rgb_p.h"
#include "sct_p.h"
#include "tga_p.h"
#include "xcf_p.h"
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
{
int argc = 0;
QCoreApplication a(argc, nullptr);
QImageIOHandler* handler = new HANDLER();
QImage i;
QBuffer b;
b.setData((const char *)data, size);
b.open(QIODevice::ReadOnly);
handler->setDevice(&b);
handler->canRead();
handler->read(&i);
delete handler;
return 0;
}

View File

@ -0,0 +1,44 @@
#!/bin/bash -eu
#
# SPDX-FileCopyrightText: 2018 Google Inc.
# SPDX-License-Identifier: Apache-2.0
#
# Based on https://github.com/google/oss-fuzz/blob/33aab4a70dc4b5811143d214536584a8c8cb3924/projects/kimageformats/Dockerfile
# Copyright 2018 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
################################################################################
apt-get update && \
apt-get install -y cmake make autoconf automake autopoint libtool \
wget po4a ninja-build pkgconf
git clone --depth 1 https://github.com/madler/zlib.git
git clone --depth 1 -b v1.5.7 https://github.com/facebook/zstd.git
wget https://sourceware.org/pub/bzip2/bzip2-1.0.8.tar.gz
git clone https://github.com/tukaani-project/xz.git
git clone --depth 1 --branch=RB-3.3 https://github.com/AcademySoftwareFoundation/openexr.git
git clone --depth 1 -b master https://invent.kde.org/frameworks/extra-cmake-modules.git
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 https://github.com/strukturag/libde265.git
git clone --depth 1 -b v2.5.3 https://github.com/uclouvain/openjpeg.git
git clone --depth 1 https://github.com/strukturag/libheif.git
git clone --depth=1 --branch v0.11.x --recursive --shallow-submodules https://github.com/libjxl/libjxl.git
git clone --depth 1 https://github.com/LibRaw/LibRaw
git clone --depth 1 https://github.com/mircomir/jxrlib.git
git clone --depth 1 -b v2.6.0 https://github.com/cisco/openh264.git

View File

@ -1,6 +1,12 @@
[
{
"fileName" : "metadata.png",
"colorSpace" : {
"description" : "sRGB build-in",
"primaries" : "SRgb",
"transferFunction" : "SRgb",
"gamma" : 0
},
"metadata" : [
{
"key" : "ModificationDate",

View File

@ -2,6 +2,12 @@
{
"fileName" : "rgba_f16.png",
"fuzziness" : 1,
"description" : "Minimum fuzziness value to pass the test on all architectures."
"description" : "Minimum fuzziness value to pass the test on all architectures.",
"colorSpace" : {
"description" : "Linear sRGB",
"primaries" : "SRgb",
"transferFunction" : "Linear",
"gamma" : 1
}
}
]

View File

@ -1,6 +1,12 @@
[
{
"fileName" : "rgb-gimp.png",
"colorSpace" : {
"description" : "",
"primaries" : "Custom",
"transferFunction" : "Linear",
"gamma" : 1
},
"resolution" : {
"dotsPerMeterX" : 3937,
"dotsPerMeterY" : 3937

View File

@ -1,6 +1,12 @@
[
{
"fileName" : "orientation_all.png",
"colorSpace" : {
"description" : "Linear sRGB",
"primaries" : "SRgb",
"transferFunction" : "Linear",
"gamma" : 1
},
"metadata" : [
{
"key" : "Software" ,

View File

@ -1,6 +1,12 @@
[
{
"fileName" : "orientation_all.png",
"colorSpace" : {
"description" : "Linear sRGB",
"primaries" : "SRgb",
"transferFunction" : "Linear",
"gamma" : 1
},
"metadata" : [
{
"key" : "Software" ,

View File

@ -1,6 +1,12 @@
[
{
"fileName" : "orientation_all.png",
"colorSpace" : {
"description" : "Linear sRGB",
"primaries" : "SRgb",
"transferFunction" : "Linear",
"gamma" : 1
},
"metadata" : [
{
"key" : "Software" ,

View File

@ -1,6 +1,12 @@
[
{
"fileName" : "orientation_all.png",
"colorSpace" : {
"description" : "Linear sRGB",
"primaries" : "SRgb",
"transferFunction" : "Linear",
"gamma" : 1
},
"metadata" : [
{
"key" : "Software" ,

View File

@ -1,6 +1,12 @@
[
{
"fileName" : "orientation_all.png",
"colorSpace" : {
"description" : "Linear sRGB",
"primaries" : "SRgb",
"transferFunction" : "Linear",
"gamma" : 1
},
"metadata" : [
{
"key" : "Software" ,

View File

@ -1,6 +1,12 @@
[
{
"fileName" : "orientation_all.png",
"colorSpace" : {
"description" : "Linear sRGB",
"primaries" : "SRgb",
"transferFunction" : "Linear",
"gamma" : 1
},
"metadata" : [
{
"key" : "Software" ,

View File

@ -1,6 +1,12 @@
[
{
"fileName" : "orientation_all.png",
"colorSpace" : {
"description" : "Linear sRGB",
"primaries" : "SRgb",
"transferFunction" : "Linear",
"gamma" : 1
},
"metadata" : [
{
"key" : "Software" ,

View File

@ -1,6 +1,12 @@
[
{
"fileName" : "orientation_all.png",
"colorSpace" : {
"description" : "Linear sRGB",
"primaries" : "SRgb",
"transferFunction" : "Linear",
"gamma" : 1
},
"metadata" : [
{
"key" : "Software" ,

View File

@ -1,6 +1,12 @@
[
{
"fileName" : "metadata.png",
"colorSpace" : {
"description" : "sRGB build-in",
"primaries" : "SRgb",
"transferFunction" : "SRgb",
"gamma" : 0
},
"metadata" : [
{
"key" : "ModificationDate",

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

View File

@ -0,0 +1,5 @@
[
{
"fileName" : "ps_testcard_rgb_maya.png"
}
]

BIN
autotests/read/iff/ham5.iff Normal file

Binary file not shown.

BIN
autotests/read/iff/ham5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
autotests/read/iff/ham8.iff Normal file

Binary file not shown.

BIN
autotests/read/iff/ham8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

@ -1,6 +1,12 @@
[
{
"fileName" : "metadata.png",
"colorSpace" : {
"description" : "TICC ICC Profile",
"primaries" : "SRgb",
"transferFunction" : "SRgb",
"gamma" : 0
},
"metadata" : [
{
"key" : "Author",

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.4 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

View File

@ -0,0 +1,5 @@
[
{
"fileName" : "ps_testcard_rgb16_maya.png"
}
]

Binary file not shown.

View File

@ -0,0 +1,5 @@
[
{
"fileName" : "ps_testcard_rgb_maya.png"
}
]

Binary file not shown.

View File

@ -0,0 +1,5 @@
[
{
"fileName" : "ps_testcard_rgba16_maya.png"
}
]

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -1,6 +1,12 @@
[
{
"fileName" : "gimp_exif.png",
"colorSpace" : {
"description" : "sRGB build-in",
"primaries" : "SRgb",
"transferFunction" : "SRgb",
"gamma" : 0
},
"metadata" : [
{
"key" : "ModificationDate",

View File

@ -10,6 +10,12 @@
"minQtVersion" : "6.7.3",
"disableAutoTransform": true,
"fileName" : "orientation6_notranfs.png",
"colorSpace" : {
"description" : "GIMP built-in sRGB",
"primaries" : "SRgb",
"transferFunction" : "SRgb",
"gamma" : 0
},
"comment" : "Test with automatic transformation disabled."
},
{

View File

@ -11,6 +11,12 @@
},
{
"minQtVersion" : "6.6.2",
"colorSpace" : {
"description" : "sRGB built-in",
"primaries" : "SRgb",
"transferFunction" : "SRgb",
"gamma" : 0
},
"fileName" : "testcard_rgba_fp16.png"
},
{

View File

@ -11,6 +11,12 @@
},
{
"minQtVersion" : "6.6.2",
"colorSpace" : {
"description" : "sRGB built-in",
"primaries" : "SRgb",
"transferFunction" : "SRgb",
"gamma" : 0
},
"fileName" : "testcard_rgba_fp32.png"
},
{

View File

@ -1,6 +1,12 @@
[
{
"fileName" : "metadata.png",
"colorSpace" : {
"description" : "sRGB build-in",
"primaries" : "SRgb",
"transferFunction" : "SRgb",
"gamma" : 0
},
"metadata" : [
{
"key" : "CreationDate",

View File

@ -1,6 +1,12 @@
[
{
"fileName" : "orientation_all.png",
"colorSpace" : {
"description" : "GIMP built-in sRGB",
"primaries" : "SRgb",
"transferFunction" : "SRgb",
"gamma" : 0
},
"metadata" : [
{
"key" : "Software" ,

View File

@ -2,6 +2,13 @@
{
"minQtVersion" : "6.8.0",
"fileName" : "testcard_cmyk8.tif",
"colorSpace" : {
"description" : "U.S. Web Coated (SWOP) v2",
"colorModel" : "Cmyk",
"primaries" : "Custom",
"transferFunction" : "Custom",
"gamma" : 0
},
"resolution" : {
"dotsPerMeterX" : 3780,
"dotsPerMeterY" : 3780

View File

@ -1,5 +1,11 @@
[
{
"fileName" : "testcard_gray_half.png"
"fileName" : "testcard_gray_half.png",
"colorSpace" : {
"description" : "Linear sRGB",
"primaries" : "SRgb",
"transferFunction" : "Linear",
"gamma" : 1
}
}
]

View File

@ -0,0 +1,26 @@
[
{
"fileName" : "32bit_grayscale.png",
"colorSpace" : {
"description" : "Linear Grayscale Profile",
"colorModel" : "Gray",
"primaries" : "Custom",
"transferFunction" : "Linear",
"gamma" : 1
},
"metadata" : [
{
"key" : "ModificationDate",
"value" : "2022-03-28T12:46:04"
},
{
"key" : "Software" ,
"value" : "Adobe Photoshop 23.2 (Windows)"
}
],
"resolution" : {
"dotsPerMeterX" : 2232,
"dotsPerMeterY" : 2232
}
}
]

View File

@ -1,7 +1,14 @@
[
{
"minQtVersion" : "6.8.0",
"fileName" : "cmyk16_testcard_qt6_8.tif"
"fileName" : "cmyk16_testcard_qt6_8.tif",
"colorSpace" : {
"description" : "Coated FOGRA27 (ISO 12647-2:2004)",
"colorModel" : "Cmyk",
"primaries" : "Custom",
"transferFunction" : "Custom",
"gamma" : 0
}
},
{
"minQtVersion" : "6.0.0",

View File

@ -1,7 +1,14 @@
[
{
"minQtVersion" : "6.8.0",
"fileName" : "cmyk8_testcard_qt6_8.tif"
"fileName" : "cmyk8_testcard_qt6_8.tif",
"colorSpace" : {
"description" : "Coated FOGRA27 (ISO 12647-2:2004)",
"colorModel" : "Cmyk",
"primaries" : "Custom",
"transferFunction" : "Custom",
"gamma" : 0
}
},
{
"minQtVersion" : "6.0.0",

View File

@ -1,6 +1,12 @@
[
{
"fileName" : "metadata.png",
"colorSpace" : {
"description" : "sRGB build-in",
"primaries" : "SRgb",
"transferFunction" : "SRgb",
"gamma" : 0
},
"metadata" : [
{
"key" : "ModificationDate",

View File

@ -1,6 +1,12 @@
[
{
"fileName" : "orientation_all.png",
"colorSpace" : {
"description" : "GIMP built-in sRGB",
"primaries" : "SRgb",
"transferFunction" : "SRgb",
"gamma" : 0
},
"metadata" : [
{
"key" : "Software" ,

View File

@ -0,0 +1,11 @@
[
{
"fileName" : "testcard_rgba.qoi",
"colorSpace" : {
"description" : "sRGB",
"primaries" : "SRgb",
"transferFunction" : "SRgb",
"gamma" : 2.31
}
}
]

View File

@ -1,6 +1,12 @@
[
{
"fileName" : "RAW_KODAK_C330_FORMAT_NONE_YRGB.png",
"colorSpace" : {
"description" : "sRGB",
"primaries" : "SRgb",
"transferFunction" : "SRgb",
"gamma" : 2.31
},
"metadata" : [
{
"key" : "Manufacturer",

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 KiB

View File

@ -0,0 +1,5 @@
[
{
"fileName" : "orientation.png"
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 KiB

View File

@ -0,0 +1,5 @@
[
{
"fileName" : "orientation.png"
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

View File

@ -0,0 +1,62 @@
[
{
"fileName" : "extarea.png",
"skipSequential" : true,
"colorSpace" : {
"description" : "sRGB build-in",
"primaries" : "SRgb",
"transferFunction" : "SRgb",
"gamma" : 0
},
"metadata" : [
{
"key" : "Author",
"value" : "KDE Project"
},
{
"key" : "Comment",
"value" : "TV broadcast test image."
},
{
"key" : "Altitude",
"value" : "34"
},
{
"key" : "Copyright",
"value" : "@2025 KDE Project"
},
{
"key" : "Latitude",
"value" : "44.6478"
},
{
"key" : "LensManufacturer",
"value" : "KDE Glasses"
},
{
"key" : "LensModel",
"value" : "A1234"
},
{
"key" : "Longitude",
"value" : "10.9254"
},
{
"key" : "Manufacturer",
"value" : "KFramework"
},
{
"key" : "Model",
"value" : "KImageFormats"
},
{
"key" : "Software",
"value" : "LIFE Pro 2.18.30 (Linux)"
}
],
"resolution" : {
"dotsPerMeterX" : 11811,
"dotsPerMeterY" : 5906
}
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

View File

@ -0,0 +1,32 @@
[
{
"fileName" : "extarea.png",
"skipSequential" : true,
"metadata" : [
{
"key" : "Title",
"value" : "Test Card"
},
{
"key" : "Author",
"value" : "KDE Project"
},
{
"key" : "ModificationDate",
"value" : "2025-08-21T07:32:45"
},
{
"key" : "Comment",
"value" : "TV broadcast test image."
},
{
"key" : "Software",
"value" : "LIFE Pro 2.18.31 (Linux)"
}
],
"resolution" : {
"dotsPerMeterX" : 3937,
"dotsPerMeterY" : 3937
}
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 KiB

View File

@ -0,0 +1,5 @@
[
{
"fileName" : "orientation.png"
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 KiB

View File

@ -0,0 +1,5 @@
[
{
"fileName" : "orientation.png"
}
]

View File

@ -4,8 +4,8 @@
"seeAlso" : "https://bugreports.qt.io/browse/QTBUG-120614",
"fuzziness" : 1,
"resolution" : {
"dotsPerMeterX" : 2834,
"dotsPerMeterY" : 2834
"dotsPerMeterX" : 2835,
"dotsPerMeterY" : 2835
}
}
]

View File

@ -281,6 +281,12 @@ int main(int argc, char **argv)
continue;
}
if (seq && timg.skipSequentialDeviceTest()) {
QTextStream(stdout) << "SKIP : " << fi.fileName() << ": marked to be skipped on a sequential device (don't worry, it's ok)\n";
++skipped;
continue;
}
TemplateImage::TestFlags flags = TemplateImage::None;
QString comment;
QFileInfo expFileInfo = timg.compareImage(flags, comment);

View File

@ -6,10 +6,12 @@
#include "templateimage.h"
#include <QColorSpace>
#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QMetaEnum>
#include <QVersionNumber>
static QJsonObject searchObject(const QFileInfo& file)
@ -76,6 +78,15 @@ bool TemplateImage::isLicense() const
return !m_fi.suffix().compare(QStringLiteral("license"), Qt::CaseInsensitive);
}
bool TemplateImage::skipSequentialDeviceTest() const
{
auto obj = searchObject(m_fi);
if (obj.isEmpty()) {
return false;
}
return obj.value("skipSequential").toBool();
}
QFileInfo TemplateImage::compareImage(TestFlags &flags, QString& comment) const
{
auto fi = jsonImage(flags, comment);
@ -95,6 +106,47 @@ bool TemplateImage::checkOptionaInfo(const QImage& image, QString& error) const
return true;
}
// test color space (icc profile)
auto color = obj.value("colorSpace").toObject();
if (!color.isEmpty()) {
auto dsc = color.value("description").toString();
auto clm = color.value("colorModel").toString(QStringLiteral("Rgb"));
auto prm = color.value("primaries").toString();
auto trf = color.value("transferFunction").toString();
auto gmm = color.value("gamma").toDouble();
auto cs = image.colorSpace();
if (cs.description() != dsc) {
error = QStringLiteral("ColorSpace Description mismatch (current: %1, expected: %2)!").arg(cs.description(), dsc);
return false;
}
auto prmName = QString(QMetaEnum::fromType<QColorSpace::Primaries>().valueToKey(quint64(cs.primaries())));
if (prmName != prm) {
error = QStringLiteral("ColorSpace Primaries mismatch (current: %1, expected: %2)!").arg(prmName, prm);
return false;
}
auto trfName = QString(QMetaEnum::fromType<QColorSpace::TransferFunction>().valueToKey(quint64(cs.transferFunction())));
if (trfName != trf) {
error = QStringLiteral("ColorSpace TransferFunction mismatch (current: %1, expected: %2)!").arg(trfName, trf);
return false;
}
if (qAbs(cs.gamma() - gmm) > 0.01) {
error = QStringLiteral("ColorSpace Gamma mismatch (current: %1, expected: %2)!").arg(cs.gamma()).arg(gmm);
return false;
}
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
auto clmName = QString(QMetaEnum::fromType<QColorSpace::ColorModel>().valueToKey(quint64(cs.colorModel())));
if (clmName != clm) {
error = QStringLiteral("ColorSpace ColorModel mismatch (current: %1, expected: %2)!").arg(clmName, clm);
return false;
}
#endif
}
// Test resolution
auto res = obj.value("resolution").toObject();
if (!res.isEmpty()) {

View File

@ -54,6 +54,12 @@ public:
*/
bool isLicense() const;
/*!
* \brief skipSequentialDeviceTest
* \return tre it the sequential test should be skipped.
*/
bool skipSequentialDeviceTest() const;
/*!
* \brief compareImage
* \param flags Flags for modifying test behavior (e.g. image format not supported by current Qt version).

View File

@ -0,0 +1,62 @@
{
"format" : "tga",
"subType" : "TGAv2E",
"metadata" : [
{
"key" : "CreationDate",
"value" : "2025-01-14T13:53:32+01:00"
},
{
"key" : "Direction",
"value" : "123.7"
},
{
"key" : "Software" ,
"value" : "Adobe Photoshop 26.2 (Windows)"
},
{
"key" : "Altitude",
"value" : "34"
},
{
"key" : "Author",
"value" : "KDE Project"
},
{
"key" : "Copyright",
"value" : "@2025 KDE Project"
},
{
"key" : "Description",
"value" : "テレビ放送テスト映像。(TV broadcast test image.)"
},
{
"key" : "Latitude",
"value" : "44.6478"
},
{
"key" : "LensManufacturer",
"value" : "KDE Glasses"
},
{
"key" : "LensModel",
"value" : "A1234"
},
{
"key" : "Longitude",
"value" : "10.9254"
},
{
"key" : "Manufacturer",
"value" : "KFramework"
},
{
"key" : "Model",
"value" : "KImageFormats"
}
],
"resolution" : {
"dotsPerMeterX" : 11811,
"dotsPerMeterY" : 11812
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 37 KiB

View File

@ -70,6 +70,15 @@ void setOptionalInfo(QImage &image, const QString &suffix)
}
}
QByteArray readSubType(const QString &suffix)
{
auto obj = readOptionalInfo(suffix);
if (obj.isEmpty()) {
return {};
}
return obj.value("subType").toString().toLatin1();
}
bool checkOptionalInfo(QImage &image, const QString &suffix)
{
auto obj = readOptionalInfo(suffix);
@ -157,6 +166,9 @@ int basicTest(const QString &suffix, bool lossless, bool ignoreDataCheck, bool s
{
QBuffer buffer(&writtenData);
QImageWriter imgWriter(&buffer, format.constData());
auto subType = readSubType(suffix);
if (!subType.isEmpty())
imgWriter.setSubType(subType);
if (lossless) {
imgWriter.setQuality(100);
}

View File

@ -101,11 +101,11 @@ endif()
##################################
kimageformats_add_plugin(kimg_pcx SOURCES pcx.cpp)
kimageformats_add_plugin(kimg_pcx SOURCES pcx.cpp scanlineconverter.cpp)
##################################
kimageformats_add_plugin(kimg_pic SOURCES pic.cpp)
kimageformats_add_plugin(kimg_pic SOURCES pic.cpp scanlineconverter.cpp)
##################################
@ -129,7 +129,7 @@ kimageformats_add_plugin(kimg_ras SOURCES ras.cpp)
##################################
kimageformats_add_plugin(kimg_rgb SOURCES rgb.cpp)
kimageformats_add_plugin(kimg_rgb SOURCES rgb.cpp scanlineconverter.cpp)
##################################
@ -137,7 +137,7 @@ kimageformats_add_plugin(kimg_sct SOURCES sct.cpp)
##################################
kimageformats_add_plugin(kimg_tga SOURCES tga.cpp)
kimageformats_add_plugin(kimg_tga SOURCES tga.cpp microexif.cpp scanlineconverter.cpp)
##################################

View File

@ -6,14 +6,20 @@
#include "ani_p.h"
#include <QDebug>
#include <QImage>
#include <QLoggingCategory>
#include <QScopeGuard>
#include <QVariant>
#include <QtEndian>
#include <cstring>
#ifdef QT_DEBUG
Q_LOGGING_CATEGORY(LOG_ANIPLUGIN, "kf.imageformats.plugins.ani", QtDebugMsg)
#else
Q_LOGGING_CATEGORY(LOG_ANIPLUGIN, "kf.imageformats.plugins.ani", QtWarningMsg)
#endif
namespace
{
struct ChunkHeader {
@ -358,7 +364,7 @@ bool ANIHandler::ensureScanned() const
if (chunkId == "anih") {
if (chunkSize != sizeof(AniHeader)) {
qWarning() << "anih chunk size does not match ANIHEADER size";
qCWarning(LOG_ANIPLUGIN) << "anih chunk size does not match ANIHEADER size";
return false;
}
@ -494,22 +500,22 @@ bool ANIHandler::ensureScanned() const
}
if (m_imageCount != m_frameCount && m_imageSequence.isEmpty()) {
qWarning("ANIHandler: 'nSteps' is not equal to 'nFrames' but no 'seq' entries were provided");
qCWarning(LOG_ANIPLUGIN) << "ANIHandler: 'nSteps' is not equal to 'nFrames' but no 'seq' entries were provided";
return false;
}
if (!m_imageSequence.isEmpty() && m_imageSequence.count() != m_imageCount) {
qWarning("ANIHandler: count of entries in 'seq' does not match 'nSteps' in anih");
qCWarning(LOG_ANIPLUGIN) << "ANIHandler: count of entries in 'seq' does not match 'nSteps' in anih";
return false;
}
if (!m_displayRates.isEmpty() && m_displayRates.count() != m_imageCount) {
qWarning("ANIHandler: count of entries in 'rate' does not match 'nSteps' in anih");
qCWarning(LOG_ANIPLUGIN) << "ANIHandler: count of entries in 'rate' does not match 'nSteps' in anih";
return false;
}
if (!m_frameOffsets.isEmpty() && m_frameOffsets.count() - 1 != m_frameCount) {
qWarning("ANIHandler: number of actual frames does not match 'nFrames' in anih");
qCWarning(LOG_ANIPLUGIN) << "ANIHandler: number of actual frames does not match 'nFrames' in anih";
return false;
}
@ -520,7 +526,7 @@ bool ANIHandler::ensureScanned() const
bool ANIHandler::canRead(QIODevice *device)
{
if (!device) {
qWarning("ANIHandler::canRead() called with no device");
qCWarning(LOG_ANIPLUGIN) << "ANIHandler::canRead() called with no device";
return false;
}
if (device->isSequential()) {

View File

@ -10,6 +10,7 @@
#include <QtGlobal>
#include <QColorSpace>
#include <QLoggingCategory>
#include "avif_p.h"
#include "microexif_p.h"
@ -17,6 +18,12 @@
#include <cfloat>
#ifdef QT_DEBUG
Q_LOGGING_CATEGORY(LOG_AVIFPLUGIN, "kf.imageformats.plugins.avif", QtDebugMsg)
#else
Q_LOGGING_CATEGORY(LOG_AVIFPLUGIN, "kf.imageformats.plugins.avif", QtWarningMsg)
#endif
/*
Quality range - compression/subsampling
100 - lossless RGB compression
@ -168,7 +175,7 @@ bool QAVIFHandler::ensureDecoder()
decodeResult = avifDecoderSetIOMemory(m_decoder, m_rawAvifData.data, m_rawAvifData.size);
if (decodeResult != AVIF_RESULT_OK) {
qWarning("ERROR: avifDecoderSetIOMemory failed: %s", avifResultToString(decodeResult));
qCWarning(LOG_AVIFPLUGIN, "ERROR: avifDecoderSetIOMemory failed: %s", avifResultToString(decodeResult));
avifDecoderDestroy(m_decoder);
m_decoder = nullptr;
@ -178,7 +185,7 @@ bool QAVIFHandler::ensureDecoder()
decodeResult = avifDecoderParse(m_decoder);
if (decodeResult != AVIF_RESULT_OK) {
qWarning("ERROR: Failed to parse input: %s", avifResultToString(decodeResult));
qCWarning(LOG_AVIFPLUGIN, "ERROR: Failed to parse input: %s", avifResultToString(decodeResult));
avifDecoderDestroy(m_decoder);
m_decoder = nullptr;
@ -190,19 +197,19 @@ bool QAVIFHandler::ensureDecoder()
m_container_height = m_decoder->image->height;
if ((m_container_width > 65535) || (m_container_height > 65535)) {
qWarning("AVIF image (%dx%d) is too large!", m_container_width, m_container_height);
qCWarning(LOG_AVIFPLUGIN, "AVIF image (%dx%d) is too large!", m_container_width, m_container_height);
m_parseState = ParseAvifError;
return false;
}
if ((m_container_width == 0) || (m_container_height == 0)) {
qWarning("Empty image, nothing to decode");
qCWarning(LOG_AVIFPLUGIN, "Empty image, nothing to decode");
m_parseState = ParseAvifError;
return false;
}
if (m_container_width > ((16384 * 16384) / m_container_height)) {
qWarning("AVIF image (%dx%d) has more than 256 megapixels!", m_container_width, m_container_height);
qCWarning(LOG_AVIFPLUGIN, "AVIF image (%dx%d) has more than 256 megapixels!", m_container_width, m_container_height);
m_parseState = ParseAvifError;
return false;
}
@ -276,7 +283,7 @@ bool QAVIFHandler::decode_one_frame()
QImage result = imageAlloc(m_decoder->image->width, m_decoder->image->height, resultformat);
if (result.isNull()) {
qWarning("Memory cannot be allocated");
qCWarning(LOG_AVIFPLUGIN, "Memory cannot be allocated");
return false;
}
@ -285,12 +292,12 @@ bool QAVIFHandler::decode_one_frame()
const QByteArray icc_data(reinterpret_cast<const char *>(m_decoder->image->icc.data), m_decoder->image->icc.size);
colorspace = QColorSpace::fromIccProfile(icc_data);
if (!colorspace.isValid()) {
qWarning("AVIF image has Qt-unsupported or invalid ICC profile!");
qCWarning(LOG_AVIFPLUGIN, "AVIF image has Qt-unsupported or invalid ICC profile!");
}
#if (QT_VERSION >= QT_VERSION_CHECK(6, 8, 0))
else {
if (colorspace.colorModel() == QColorSpace::ColorModel::Cmyk) {
qWarning("CMYK ICC profile is not extected for AVIF, discarding the ICCprofile!");
qCWarning(LOG_AVIFPLUGIN, "CMYK ICC profile is not extected for AVIF, discarding the ICCprofile!");
colorspace = QColorSpace();
} else if (colorspace.colorModel() == QColorSpace::ColorModel::Rgb && loadgray) {
// Input is GRAY but ICC is RGB, we will return RGB image
@ -314,7 +321,7 @@ bool QAVIFHandler::decode_one_frame()
}
colorspace = QColorSpace(gray_whitePoint, redP, greenP, blueP, trc_new, gamma_new);
if (!colorspace.isValid()) {
qWarning("AVIF plugin created invalid QColorSpace!");
qCWarning(LOG_AVIFPLUGIN, "AVIF plugin created invalid QColorSpace!");
}
}
}
@ -362,7 +369,7 @@ bool QAVIFHandler::decode_one_frame()
break;
#endif
default:
qWarning("CICP colorPrimaries: %d, transferCharacteristics: %d\nThe colorspace is unsupported by this plug-in yet.",
qCWarning(LOG_AVIFPLUGIN, "CICP colorPrimaries: %d, transferCharacteristics: %d\nThe colorspace is unsupported by this plug-in yet.",
m_decoder->image->colorPrimaries,
m_decoder->image->transferCharacteristics);
q_trc = QColorSpace::TransferFunction::SRgb;
@ -396,7 +403,7 @@ bool QAVIFHandler::decode_one_frame()
}
if (!colorspace.isValid()) {
qWarning("AVIF plugin created invalid QColorSpace from NCLX/CICP!");
qCWarning(LOG_AVIFPLUGIN, "AVIF plugin created invalid QColorSpace from NCLX/CICP!");
}
}
@ -439,7 +446,7 @@ bool QAVIFHandler::decode_one_frame()
avifResult res = avifImageYUVToRGB(m_decoder->image, &rgb);
if (res != AVIF_RESULT_OK) {
qWarning("ERROR in avifImageYUVToRGB: %s", avifResultToString(res));
qCWarning(LOG_AVIFPLUGIN, "ERROR in avifImageYUVToRGB: %s", avifResultToString(res));
return false;
}
@ -478,7 +485,7 @@ bool QAVIFHandler::decode_one_frame()
}
else { // Zero values, we need to avoid 0 divide.
qWarning("ERROR: Wrong values in avifCleanApertureBox");
qCWarning(LOG_AVIFPLUGIN, "ERROR: Wrong values in avifCleanApertureBox");
}
}
@ -556,7 +563,7 @@ static void setMetadata(avifImage *avif, const QImage& image)
#if AVIF_VERSION >= 1000000
auto res = avifImageSetMetadataXMP(avif, reinterpret_cast<const uint8_t *>(xmp.constData()), xmp.size());
if (res != AVIF_RESULT_OK) {
qWarning("ERROR in avifImageSetMetadataXMP: %s", avifResultToString(res));
qCWarning(LOG_AVIFPLUGIN, "ERROR in avifImageSetMetadataXMP: %s", avifResultToString(res));
}
#else
avifImageSetMetadataXMP(avif, reinterpret_cast<const uint8_t *>(xmp.constData()), xmp.size());
@ -567,7 +574,7 @@ static void setMetadata(avifImage *avif, const QImage& image)
#if AVIF_VERSION >= 1000000
auto res = avifImageSetMetadataExif(avif, reinterpret_cast<const uint8_t *>(exif.constData()), exif.size());
if (res != AVIF_RESULT_OK) {
qWarning("ERROR in avifImageSetMetadataExif: %s", avifResultToString(res));
qCWarning(LOG_AVIFPLUGIN, "ERROR in avifImageSetMetadataExif: %s", avifResultToString(res));
}
#else
avifImageSetMetadataExif(avif, reinterpret_cast<const uint8_t *>(exif.constData()), exif.size());
@ -602,32 +609,32 @@ bool QAVIFHandler::read(QImage *image)
bool QAVIFHandler::write(const QImage &image)
{
if (image.format() == QImage::Format_Invalid) {
qWarning("No image data to save!");
qCWarning(LOG_AVIFPLUGIN, "No image data to save!");
return false;
}
if ((image.width() > 0) && (image.height() > 0)) {
if ((image.width() > 65535) || (image.height() > 65535)) {
qWarning("Image (%dx%d) is too large to save!", image.width(), image.height());
qCWarning(LOG_AVIFPLUGIN, "Image (%dx%d) is too large to save!", image.width(), image.height());
return false;
}
if (image.width() > ((16384 * 16384) / image.height())) {
qWarning("Image (%dx%d) will not be saved because it has more than 256 megapixels!", image.width(), image.height());
qCWarning(LOG_AVIFPLUGIN, "Image (%dx%d) will not be saved because it has more than 256 megapixels!", image.width(), image.height());
return false;
}
if ((image.width() > 32768) || (image.height() > 32768)) {
qWarning("Image (%dx%d) has a dimension above 32768 pixels, saved AVIF may not work in other software!", image.width(), image.height());
qCWarning(LOG_AVIFPLUGIN, "Image (%dx%d) has a dimension above 32768 pixels, saved AVIF may not work in other software!", image.width(), image.height());
}
} else {
qWarning("Image has zero dimension!");
qCWarning(LOG_AVIFPLUGIN, "Image has zero dimension!");
return false;
}
const char *encoder_name = avifCodecName(AVIF_CODEC_CHOICE_AUTO, AVIF_CODEC_FLAG_CAN_ENCODE);
if (!encoder_name) {
qWarning("Cannot save AVIF images because libavif was built without AV1 encoders!");
qCWarning(LOG_AVIFPLUGIN, "Cannot save AVIF images because libavif was built without AV1 encoders!");
return false;
}
@ -636,7 +643,7 @@ bool QAVIFHandler::write(const QImage &image)
if (avifCodecName(AVIF_CODEC_CHOICE_AOM, AVIF_CODEC_FLAG_CAN_ENCODE)) {
lossless = true;
} else {
qWarning("You are using %s encoder. It is recommended to enable libAOM encoder in libavif to use lossless compression.", encoder_name);
qCWarning(LOG_AVIFPLUGIN, "You are using %s encoder. It is recommended to enable libAOM encoder in libavif to use lossless compression.", encoder_name);
}
}
@ -718,7 +725,7 @@ bool QAVIFHandler::write(const QImage &image)
#if AVIF_VERSION >= 110000
res = avifImageAllocatePlanes(avif, AVIF_PLANES_YUV);
if (res != AVIF_RESULT_OK) {
qWarning("ERROR in avifImageAllocatePlanes: %s", avifResultToString(res));
qCWarning(LOG_AVIFPLUGIN, "ERROR in avifImageAllocatePlanes: %s", avifResultToString(res));
return false;
}
#else
@ -959,7 +966,7 @@ bool QAVIFHandler::write(const QImage &image)
#if AVIF_VERSION >= 1000000
res = avifImageSetProfileICC(avif, reinterpret_cast<const uint8_t *>(iccprofile.constData()), iccprofile.size());
if (res != AVIF_RESULT_OK) {
qWarning("ERROR in avifImageSetProfileICC: %s", avifResultToString(res));
qCWarning(LOG_AVIFPLUGIN, "ERROR in avifImageSetProfileICC: %s", avifResultToString(res));
return false;
}
#else
@ -992,7 +999,7 @@ bool QAVIFHandler::write(const QImage &image)
res = avifImageRGBToYUV(avif, &rgb);
if (res != AVIF_RESULT_OK) {
qWarning("ERROR in avifImageRGBToYUV: %s", avifResultToString(res));
qCWarning(LOG_AVIFPLUGIN, "ERROR in avifImageRGBToYUV: %s", avifResultToString(res));
return false;
}
}
@ -1034,11 +1041,11 @@ bool QAVIFHandler::write(const QImage &image)
if (status > 0) {
return true;
} else if (status == -1) {
qWarning("Write error: %s", qUtf8Printable(device()->errorString()));
qCWarning(LOG_AVIFPLUGIN, "Write error: %s", qUtf8Printable(device()->errorString()));
return false;
}
} else {
qWarning("ERROR: Failed to encode: %s", avifResultToString(res));
qCWarning(LOG_AVIFPLUGIN, "ERROR: Failed to encode: %s", avifResultToString(res));
}
return false;
@ -1140,7 +1147,7 @@ bool QAVIFHandler::jumpToNextImage()
if (m_decoder->imageIndex >= m_decoder->imageCount - 1) { // start from beginning
decodeResult = avifDecoderReset(m_decoder);
if (decodeResult != AVIF_RESULT_OK) {
qWarning("ERROR in avifDecoderReset: %s", avifResultToString(decodeResult));
qCWarning(LOG_AVIFPLUGIN, "ERROR in avifDecoderReset: %s", avifResultToString(decodeResult));
m_parseState = ParseAvifError;
return false;
}
@ -1150,13 +1157,13 @@ bool QAVIFHandler::jumpToNextImage()
decodeResult = avifDecoderNextImage(m_decoder);
if (decodeResult != AVIF_RESULT_OK) {
qWarning("ERROR: Failed to decode Next image in sequence: %s", avifResultToString(decodeResult));
qCWarning(LOG_AVIFPLUGIN, "ERROR: Failed to decode Next image in sequence: %s", avifResultToString(decodeResult));
m_parseState = ParseAvifError;
return false;
}
if ((m_container_width != m_decoder->image->width) || (m_container_height != m_decoder->image->height)) {
qWarning("Decoded image sequence size (%dx%d) do not match first image size (%dx%d)!",
qCWarning(LOG_AVIFPLUGIN, "Decoded image sequence size (%dx%d) do not match first image size (%dx%d)!",
m_decoder->image->width,
m_decoder->image->height,
m_container_width,
@ -1204,13 +1211,13 @@ bool QAVIFHandler::jumpToImage(int imageNumber)
avifResult decodeResult = avifDecoderNthImage(m_decoder, imageNumber);
if (decodeResult != AVIF_RESULT_OK) {
qWarning("ERROR: Failed to decode %d th Image in sequence: %s", imageNumber, avifResultToString(decodeResult));
qCWarning(LOG_AVIFPLUGIN, "ERROR: Failed to decode %d th Image in sequence: %s", imageNumber, avifResultToString(decodeResult));
m_parseState = ParseAvifError;
return false;
}
if ((m_container_width != m_decoder->image->width) || (m_container_height != m_decoder->image->height)) {
qWarning("Decoded image sequence size (%dx%d) do not match declared container size (%dx%d)!",
qCWarning(LOG_AVIFPLUGIN, "Decoded image sequence size (%dx%d) do not match declared container size (%dx%d)!",
m_decoder->image->width,
m_decoder->image->height,
m_container_width,

View File

@ -11,12 +11,12 @@
#include <QByteArray>
#include <QImage>
#include <QImageIOHandler>
#include <QImageIOPlugin>
#include <QPointF>
#include <QSize>
#include <QVariant>
#include <avif/avif.h>
#include <qimageiohandler.h>
class QAVIFHandler : public QImageIOHandler
{

File diff suppressed because it is too large Load Diff

View File

@ -17,14 +17,18 @@
#include <QByteArray>
#include <QDateTime>
#include <QHash>
#include <QImage>
#include <QIODevice>
#include <QLoggingCategory>
#include <QPoint>
#include <QSize>
#include <QSharedPointer>
#include "microexif_p.h"
Q_DECLARE_LOGGING_CATEGORY(LOG_IFFPLUGIN)
// Main chunks (Standard)
#define CAT__CHUNK QByteArray("CAT ")
#define FILL_CHUNK QByteArray(" ")
@ -49,10 +53,16 @@
#define BODY_CHUNK QByteArray("BODY")
#define CAMG_CHUNK QByteArray("CAMG")
#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 XBMI_CHUNK QByteArray("XBMI")
#define CTBL_CHUNK QByteArray("CTBL") // undocumented
#define SHAM_CHUNK QByteArray("SHAM") // undocumented
// Different palette for scanline
#define BEAM_CHUNK QByteArray("BEAM")
#define CTBL_CHUNK QByteArray("CTBL") // same as BEAM
#define PCHG_CHUNK QByteArray("PCHG") // encoded in a unknown way (to be investigated)
#define RAST_CHUNK QByteArray("RAST") // Atari ST(E)
#define SHAM_CHUNK QByteArray("SHAM")
// FOR4 CIMG IFF (Maya)
#define RGBA_CHUNK QByteArray("RGBA")
@ -64,6 +74,7 @@
#define COPY_CHUNK QByteArray("(c) ")
#define DATE_CHUNK QByteArray("DATE")
#define EXIF_CHUNK QByteArray("EXIF") // https://aminet.net/package/docs/misc/IFF-metadata
#define ICCN_CHUNK QByteArray("ICCN") // https://aminet.net/package/docs/misc/IFF-metadata
#define ICCP_CHUNK QByteArray("ICCP") // https://aminet.net/package/docs/misc/IFF-metadata
#define FVER_CHUNK QByteArray("FVER")
#define HIST_CHUNK QByteArray("HIST")
@ -74,16 +85,28 @@
#define ACBM_FORM_TYPE QByteArray("ACBM")
#define ILBM_FORM_TYPE QByteArray("ILBM")
#define PBM__FORM_TYPE QByteArray("PBM ")
#define RGB8_FORM_TYPE QByteArray("RGB8")
#define RGBN_FORM_TYPE QByteArray("RGBN")
#define CIMG_FOR4_TYPE QByteArray("CIMG")
#define TBMP_FOR4_TYPE QByteArray("TBMP")
#define CHUNKID_DEFINE(a) static QByteArray defaultChunkId() { return a; }
// The 8-bit RGB format must be consistent. If you change it here, you have also to use the same
// when converting an image with BEAM/CTBL/SHAM chunks otherwise the option(QImageIOHandler::ImageFormat)
// could returns a wrong value.
// Warning: Changing it requires changing the algorithms. Se, don't touch! :)
#define FORMAT_RGB_8BIT QImage::Format_RGB888 // default one
#define FORMAT_RGBA_8BIT QImage::Format_RGBA8888 // used by PCHG chunk
/*!
* \brief The IFFChunk class
*/
class IFFChunk
{
friend class IFFHandlerPrivate;
public:
using ChunkList = QList<QSharedPointer<IFFChunk>>;
@ -300,18 +323,30 @@ protected:
inline quint16 ui16(quint8 c1, quint8 c2) const {
return (quint16(c2) << 8) | quint16(c1);
}
inline quint16 ui16(const QByteArray &data, qint32 pos) const {
return ui16(data.at(pos + 1), data.at(pos));
}
inline qint16 i16(quint8 c1, quint8 c2) const {
return qint32(ui16(c1, c2));
}
inline qint16 i16(const QByteArray &data, qint32 pos) const {
return i16(data.at(pos + 1), data.at(pos));
}
inline quint32 ui32(quint8 c1, quint8 c2, quint8 c3, quint8 c4) const {
return (quint32(c4) << 24) | (quint32(c3) << 16) | (quint32(c2) << 8) | quint32(c1);
}
inline quint32 ui32(const QByteArray &data, qint32 pos) const {
return ui32(data.at(pos + 3), data.at(pos + 2), data.at(pos + 1), data.at(pos));
}
inline qint32 i32(quint8 c1, quint8 c2, quint8 c3, quint8 c4) const {
return qint32(ui32(c1, c2, c3, c4));
}
inline qint32 i32(const QByteArray &data, qint32 pos) const {
return i32(data.at(pos + 3), data.at(pos + 2), data.at(pos + 1), data.at(pos));
}
static ChunkList innerFromDevice(QIODevice *d, bool *ok, IFFChunk *parent = nullptr);
@ -331,6 +366,48 @@ private:
qint32 _recursionCnt;
};
/*!
* \brief The IPALChunk class
* Interface for additional per-line palette.
*/
class IPALChunk : public IFFChunk
{
public:
virtual ~IPALChunk() override {}
IPALChunk() : IFFChunk() {}
IPALChunk(const IPALChunk& other) = default;
IPALChunk& operator =(const IPALChunk& other) = default;
/*!
* \brief hasAlpha
* \return True it the palette supports the alpha channel.
*/
virtual bool hasAlpha() const { return false; }
/*!
* \brief clone
* \return A new instance of the class with all data.
*/
virtual IPALChunk *clone() const = 0;
/*!
* \brief palette
* \param y The scanline.
* \return The modified palette.
*/
virtual QList<QRgb> palette(qint32 y) const = 0;
/*!
* \brief initialize
* Initialize the palette changer.
* \param cmapPalette The palette as stored in the CMAP chunk.
* \param height The image height.
* \return True on success, otherwise false.
*/
virtual bool initialize(const QList<QRgb>& cmapPalette, qint32 height) = 0;
};
/*!
* \brief The BMHDChunk class
* Bitmap Header
@ -340,11 +417,22 @@ class BMHDChunk: public IFFChunk
public:
enum Compression {
Uncompressed = 0, /**< Image data are uncompressed. */
Rle = 1 /**< Image data are RLE compressed. */
Rle = 1, /**< Image data are RLE compressed. */
RgbN8 = 4 /**< RGB8/RGBN compresson. */
};
enum Masking {
None = 0, /**< Designates an opaque rectangular image. */
HasMask = 1, /**< A mask plane is interleaved with the bitplanes in the BODY chunk. */
HasMask = 1, /**< A "mask" is an optional "plane" of data the same size (w, h) as a bitplane.
It tells how to "cut out" part of the image when painting it onto another
image. "One" bits in the mask mean "copy the corresponding pixel to the
destination". "Zero" mask bits mean "leave this destination pixel alone". In
other words, "zero" bits designate transparent pixels.
The rows of the different bitplanes and mask are interleaved in the file.
This localizes all the information pertinent to each scan line. It
makes it much easier to transform the data while reading it to adjust the
image size or depth. It also makes it possible to scroll a big image by
swapping rows directly from the file without the need for random-access to
all the bitplanes. */
HasTransparentColor = 2, /**< Pixels in the source planes matching transparentColor
are to be considered “transparent”. (Actually, transparentColor
isnt a “color number” since its matched with numbers formed
@ -353,7 +441,7 @@ public:
one of the color registers. */
Lasso = 3 /**< The reader may construct a mask by lassoing the image as in MacPaint.
To do this, put a 1 pixel border of transparentColor around the image rectangle.
Then do a seed fill from this border. Filled pixels are to be transparent. */
Then do a seed fill from this border. Filled pixels are to be transparent. */
};
virtual ~BMHDChunk() override;
@ -467,14 +555,64 @@ public:
virtual bool isValid() const override;
QList<QRgb> palette() const;
/*!
* \brief count
* \return The number of color in the palette.
*/
virtual qint32 count() const;
/*!
* \brief palette
* \param halfbride When True, the new palette values are appended using the halfbride method.
* \return The color palette.
* \note If \a halfbride is true, the returned palette size is count() * 2.
*/
QList<QRgb> palette(bool halfbride = false) const;
CHUNKID_DEFINE(CMAP_CHUNK)
protected:
virtual QList<QRgb> innerPalette() const;
virtual bool innerReadStructure(QIODevice *d) override;
};
/*!
* \brief The CMYKChunk class
*
* This chunk would allow color specification in terms of Cyan,
* Magenta, Yellow, and Black as opposed to the current CMAP which uses RGB.
* The format would be the same as the CMAP chunk with the exception that this
* chunk uses four color components as opposed to three. The number of colors
* contained within would be chunk length/4. This chunk would be used in addition
* to the CMAP chunk.
*/
class CMYKChunk : public CMAPChunk
{
public:
virtual ~CMYKChunk() override;
CMYKChunk();
CMYKChunk(const CMYKChunk& other) = default;
CMYKChunk& operator =(const CMYKChunk& other) = default;
virtual bool isValid() const override;
/*!
* \brief count
* \return The number of color in the palette.
*/
virtual qint32 count() const override;
CHUNKID_DEFINE(CMYK_CHUNK)
protected:
/*!
* \brief palette
* \return The CMYK color palette converted to RGB one.
*/
virtual QList<QRgb> innerPalette() const override;
};
/*!
* \brief The CAMGChunk class
*/
@ -523,13 +661,13 @@ public:
* \brief dpiX
* \return The horizontal resolution in DPI.
*/
quint16 dpiX() const;
virtual quint16 dpiX() const;
/*!
* \brief dpiY
* \return The vertical resolution in DPI.
*/
quint16 dpiY() const;
virtual quint16 dpiY() const;
/*!
* \brief dotsPerMeterX
@ -549,6 +687,50 @@ protected:
virtual bool innerReadStructure(QIODevice *d) override;
};
/*!
* \brief The XBMIChunk class
*/
class XBMIChunk : public DPIChunk
{
public:
enum PictureType : quint16 {
Indexed = 0,
Grayscale = 1,
Rgb = 2,
RgbA = 3,
Cmyk = 4,
CmykA = 5,
Bitmap = 6
};
virtual ~XBMIChunk() override;
XBMIChunk();
XBMIChunk(const XBMIChunk& other) = default;
XBMIChunk& operator =(const XBMIChunk& other) = default;
virtual bool isValid() const override;
/*!
* \brief dpiX
* \return The horizontal resolution in DPI.
*/
virtual quint16 dpiX() const override;
/*!
* \brief dpiY
* \return The vertical resolution in DPI.
*/
virtual quint16 dpiY() const override;
/*!
* \brief pictureType
* \return The picture type
*/
PictureType pictureType() const;
CHUNKID_DEFINE(XBMI_CHUNK)
};
/*!
* \brief The BODYChunk class
@ -569,13 +751,20 @@ public:
* \brief readStride
* \param d The device.
* \param header The bitmap header.
* \param y The current scanline.
* \param camg The CAMG chunk (optional)
* \param cmap The CMAP chunk (optional)
* \param isPbm Set to true if the formType() == "PBM "
* \param formType The type of the current form chunk.
* \return The scanline as requested for QImage.
* \warning Call resetStrideRead() once before this one.
*/
virtual QByteArray strideRead(QIODevice *d, const BMHDChunk *header, const CAMGChunk *camg = nullptr, const CMAPChunk *cmap = nullptr, bool isPbm = false) const;
virtual QByteArray strideRead(QIODevice *d,
qint32 y,
const BMHDChunk *header,
const CAMGChunk *camg = nullptr,
const CMAPChunk *cmap = nullptr,
const IPALChunk *ipal = nullptr,
const QByteArray& formType = ILBM_FORM_TYPE) const;
/*!
* \brief resetStrideRead
@ -589,15 +778,29 @@ public:
*/
virtual bool resetStrideRead(QIODevice *d) const;
/*!
* \brief safeModeId
* \param header The header.
* \param camg The CAMG chunk.
* \return The most likely ModeId if not explicitly specified.
*/
static CAMGChunk::ModeIds safeModeId(const BMHDChunk *header, const CAMGChunk *camg, const CMAPChunk *cmap = nullptr);
protected:
/*!
* \brief strideSize
* \param isPbm Set true if the image is PBM.
* \param formType The type of the current form chunk.
* \return The size of data to have to decode an image row.
*/
quint32 strideSize(const BMHDChunk *header, bool isPbm) const;
quint32 strideSize(const BMHDChunk *header, const QByteArray& formType) const;
QByteArray deinterleave(const QByteArray &planes, const BMHDChunk *header, const CAMGChunk *camg = nullptr, const CMAPChunk *cmap = nullptr, bool isPbm = false) const;
QByteArray deinterleave(const QByteArray &planes, qint32 y, const BMHDChunk *header, const CAMGChunk *camg = nullptr, const CMAPChunk *cmap = nullptr, const IPALChunk *ipal = nullptr) const;
QByteArray pbm(const QByteArray &planes, qint32 y, const BMHDChunk *header, const CAMGChunk *camg = nullptr, const CMAPChunk *cmap = nullptr, const IPALChunk *ipal = nullptr) const;
QByteArray rgb8(const QByteArray &planes, qint32 y, const BMHDChunk *header, const CAMGChunk *camg = nullptr, const CMAPChunk *cmap = nullptr, const IPALChunk *ipal = nullptr) const;
QByteArray rgbN(const QByteArray &planes, qint32 y, const BMHDChunk *header, const CAMGChunk *camg = nullptr, const CMAPChunk *cmap = nullptr, const IPALChunk *ipal = nullptr) const;
private:
mutable QByteArray _readBuffer;
@ -619,19 +822,76 @@ public:
CHUNKID_DEFINE(ABIT_CHUNK)
virtual QByteArray strideRead(QIODevice *d, const BMHDChunk *header, const CAMGChunk *camg = nullptr, const CMAPChunk *cmap = nullptr, bool isPbm = false) const override;
virtual QByteArray strideRead(QIODevice *d,
qint32 y,
const BMHDChunk *header,
const CAMGChunk *camg = nullptr,
const CMAPChunk *cmap = nullptr,
const IPALChunk *ipal = nullptr,
const QByteArray& formType = ACBM_FORM_TYPE) const override;
virtual bool resetStrideRead(QIODevice *d) const override;
private:
mutable qint32 _y;
};
/*!
* \brief The IFOR_Chunk class
* Interface for FORM chunks.
*/
class IFOR_Chunk : public IFFChunk
{
public:
virtual ~IFOR_Chunk() override;
IFOR_Chunk();
/*!
* \brief isSupported
* \return True if the form is supported by the plugin.
*/
virtual bool isSupported() const = 0;
/*!
* \brief formType
* \return The type of image data contained in the form.
*/
virtual QByteArray formType() const = 0;
/*!
* \brief format
* \return The Qt image format the form is converted to.
*/
virtual QImage::Format format() const = 0;
/*!
* \brief transformation
* \return The image transformation.
* \note The Default implentation returns the trasformation of EXIF chunk (if any).
*/
virtual QImageIOHandler::Transformation transformation() const;
/*!
* \brief size
* \return The image size in pixels.
*/
virtual QSize size() const = 0;
/*!
* \brief optionformat
* \return The format retuned by the plugin after all conversions.
*/
QImage::Format optionformat() const;
/*!
* \brief searchIPal
* Search the palett per line chunk.
* \return The per line palette (BEAM, CTBL, SHAM, etc....).
*/
const IPALChunk *searchIPal() const;
};
/*!
* \brief The FORMChunk class
*/
class FORMChunk : public IFFChunk
class FORMChunk : public IFOR_Chunk
{
QByteArray _type;
@ -643,13 +903,13 @@ public:
virtual bool isValid() const override;
bool isSupported() const;
virtual bool isSupported() const override;
QByteArray formType() const;
virtual QByteArray formType() const override;
QImage::Format format() const;
virtual QImage::Format format() const override;
QSize size() const;
virtual QSize size() const override;
CHUNKID_DEFINE(FORM_CHUNK)
@ -661,7 +921,7 @@ protected:
/*!
* \brief The FOR4Chunk class
*/
class FOR4Chunk : public IFFChunk
class FOR4Chunk : public IFOR_Chunk
{
QByteArray _type;
@ -675,13 +935,13 @@ public:
virtual qint32 alignBytes() const override;
bool isSupported() const;
virtual bool isSupported() const override;
QByteArray formType() const;
virtual QByteArray formType() const override;
QImage::Format format() const;
virtual QImage::Format format() const override;
QSize size() const;
virtual QSize size() const override;
CHUNKID_DEFINE(FOR4_CHUNK)
@ -689,6 +949,29 @@ protected:
virtual bool innerReadStructure(QIODevice *d) override;
};
/*!
* \brief The CATChunk class
*/
class CATChunk : public IFFChunk
{
QByteArray _type;
public:
virtual ~CATChunk() override;
CATChunk();
CATChunk(const CATChunk& other) = default;
CATChunk& operator =(const CATChunk& other) = default;
virtual bool isValid() const override;
QByteArray catType() const;
CHUNKID_DEFINE(CAT__CHUNK)
protected:
virtual bool innerReadStructure(QIODevice *d) override;
};
/*!
* \brief The TBHDChunk class
*/
@ -959,6 +1242,28 @@ protected:
virtual bool innerReadStructure(QIODevice *d) override;
};
/*!
* \brief The NAMEChunk class
*/
class ICCNChunk : public IFFChunk
{
public:
virtual ~ICCNChunk() override;
ICCNChunk();
ICCNChunk(const ICCNChunk& other) = default;
ICCNChunk& operator =(const ICCNChunk& other) = default;
virtual bool isValid() const override;
QString value() const;
CHUNKID_DEFINE(ICCN_CHUNK)
protected:
virtual bool innerReadStructure(QIODevice *d) override;
};
/*!
* \brief The ICCPChunk class
*/
@ -1089,4 +1394,175 @@ protected:
virtual bool innerReadStructure(QIODevice *d) override;
};
/*!
* *** UNDOCUMENTED CHUNKS ***
*/
/*!
* \brief The BEAMChunk class
*/
class BEAMChunk : public IPALChunk
{
public:
virtual ~BEAMChunk() override;
BEAMChunk();
BEAMChunk(const BEAMChunk& other) = default;
BEAMChunk& operator =(const BEAMChunk& other) = default;
virtual bool isValid() const override;
virtual IPALChunk *clone() const override;
virtual QList<QRgb> palette(qint32 y) const override;
virtual bool initialize(const QList<QRgb>& cmapPalette, qint32 height) override;
CHUNKID_DEFINE(BEAM_CHUNK)
protected:
virtual bool innerReadStructure(QIODevice *d) override;
private:
qint32 _height;
};
/*!
* \brief The CTBLChunk class
*/
class CTBLChunk : public BEAMChunk
{
public:
virtual ~CTBLChunk() override;
CTBLChunk();
CTBLChunk(const CTBLChunk& other) = default;
CTBLChunk& operator =(const CTBLChunk& other) = default;
virtual bool isValid() const override;
CHUNKID_DEFINE(CTBL_CHUNK)
};
/*!
* \brief The SHAMChunk class
*/
class SHAMChunk : public IPALChunk
{
public:
virtual ~SHAMChunk() override;
SHAMChunk();
SHAMChunk(const SHAMChunk& other) = default;
SHAMChunk& operator =(const SHAMChunk& other) = default;
virtual bool isValid() const override;
virtual IPALChunk *clone() const override;
virtual QList<QRgb> palette(qint32 y) const override;
virtual bool initialize(const QList<QRgb>& cmapPalette, qint32 height) override;
CHUNKID_DEFINE(SHAM_CHUNK)
protected:
virtual bool innerReadStructure(QIODevice *d) override;
private:
qint32 _height;
};
/*!
* \brief The RASTChunk class
* \note I found an Atari STE image with the RAST chunk outside
* the form chunk (Fish.neo.iff). To support it the IFF parser
* should be changed so, this kind of IFFs are shown wrong.
*/
class RASTChunk : public IPALChunk
{
public:
virtual ~RASTChunk() override;
RASTChunk();
RASTChunk(const RASTChunk& other) = default;
RASTChunk& operator =(const RASTChunk& other) = default;
virtual bool isValid() const override;
virtual IPALChunk *clone() const override;
virtual QList<QRgb> palette(qint32 y) const override;
virtual bool initialize(const QList<QRgb>& cmapPalette, qint32 height) override;
CHUNKID_DEFINE(RAST_CHUNK)
protected:
virtual bool innerReadStructure(QIODevice *d) override;
private:
qint32 _height;
};
/*!
* \brief The PCHGChunk class
*/
class PCHGChunk : public IPALChunk
{
public:
enum Compression {
Uncompressed,
Huffman
};
enum Flag {
None = 0x00,
F12Bit = 0x01,
F32Bit = 0x02,
UseAlpha = 0x04
};
Q_DECLARE_FLAGS(Flags, Flag)
virtual ~PCHGChunk() override;
PCHGChunk();
PCHGChunk(const PCHGChunk& other) = default;
PCHGChunk& operator =(const PCHGChunk& other) = default;
Compression compression() const;
Flags flags() const;
qint16 startLine() const;
quint16 lineCount() const;
quint16 changedLines() const;
quint16 minReg() const;
quint16 maxReg() const;
quint16 maxChanges() const;
quint32 totalChanges() const;
virtual bool hasAlpha() const override;
virtual bool isValid() const override;
virtual IPALChunk *clone() const override;
virtual QList<QRgb> palette(qint32 y) const override;
virtual bool initialize(const QList<QRgb>& cmapPalette, qint32 height) override;
CHUNKID_DEFINE(PCHG_CHUNK)
protected:
virtual bool innerReadStructure(QIODevice *d) override;
private:
QHash<qint32, QHash<quint16, QRgb>> _paletteChanges;
QHash<qint32, QList<QRgb>> _palettes;
};
#endif // KIMG_CHUNKS_P_H

View File

@ -15,7 +15,7 @@
#include <QColorSpace>
#include <QDataStream>
#include <QDebug>
#include <QLoggingCategory>
#include <cmath>
@ -24,6 +24,22 @@
// #define DDS_DISABLE_STRIDE_ALIGNMENT
#endif
/* *** DDS_MAX_IMAGE_WIDTH and DDS_MAX_IMAGE_HEIGHT ***
* The maximum size in pixel allowed by the plugin.
*/
#ifndef DDS_MAX_IMAGE_WIDTH
#define DDS_MAX_IMAGE_WIDTH KIF_LARGE_IMAGE_PIXEL_LIMIT
#endif
#ifndef DDS_MAX_IMAGE_HEIGHT
#define DDS_MAX_IMAGE_HEIGHT DDS_MAX_IMAGE_WIDTH
#endif
#ifdef QT_DEBUG
Q_LOGGING_CATEGORY(LOG_DDSPLUGIN, "kf.imageformats.plugins.dds", QtDebugMsg)
#else
Q_LOGGING_CATEGORY(LOG_DDSPLUGIN, "kf.imageformats.plugins.dds", QtWarningMsg)
#endif
enum Format {
FormatUnknown = 0,
@ -1030,20 +1046,32 @@ static QImage readUnsignedImage(QDataStream &s, const DDSHeader &dds, quint32 wi
static qfloat16 readFloat16(QDataStream &s)
{
qfloat16 f16;
qfloat16 f16 = 0;
if (s.status() != QDataStream::Ok) {
return f16;
}
s >> f16;
if (qIsNaN(f16)) {
s.setStatus(QDataStream::ReadCorruptData);
}
return f16;
}
static inline float readFloat32(QDataStream &s)
{
Q_ASSERT(sizeof(float) == 4);
float value;
float value = 0;
if (s.status() != QDataStream::Ok) {
return value;
}
// TODO: find better way to avoid setting precision each time
QDataStream::FloatingPointPrecision precision = s.floatingPointPrecision();
s.setFloatingPointPrecision(QDataStream::SinglePrecision);
s >> value;
s.setFloatingPointPrecision(precision);
if (qIsNaN(value)) {
s.setStatus(QDataStream::ReadCorruptData);
}
return value;
}
@ -2377,7 +2405,7 @@ bool QDDSHandler::write(const QImage &outImage)
return writeA32B32G32R32F(outImage, s);
}
qWarning() << "Format" << formatName(format) << "is not supported";
qCWarning(LOG_DDSPLUGIN) << "Format" << formatName(format) << "is not supported";
return false;
}
@ -2450,7 +2478,7 @@ bool QDDSHandler::jumpToImage(int imageNumber)
bool QDDSHandler::canRead(QIODevice *device)
{
if (!device) {
qWarning() << "DDSHandler::canRead() called with no device";
qCWarning(LOG_DDSPLUGIN) << "DDSHandler::canRead() called with no device";
return false;
}
@ -2475,7 +2503,7 @@ bool QDDSHandler::ensureScanned() const
that->m_format = FormatUnknown;
if (device()->isSequential()) {
qWarning() << "Sequential devices are not supported";
qCWarning(LOG_DDSPLUGIN) << "Sequential devices are not supported";
return false;
}
@ -2508,25 +2536,25 @@ bool QDDSHandler::verifyHeader(const DDSHeader &dds) const
quint32 requiredFlags = DDSHeader::FlagCaps | DDSHeader::FlagHeight
| DDSHeader::FlagWidth | DDSHeader::FlagPixelFormat;
if ((flags & requiredFlags) != requiredFlags) {
qWarning() << "Wrong dds.flags - not all required flags present. "
qCWarning(LOG_DDSPLUGIN) << "Wrong dds.flags - not all required flags present. "
"Actual flags :" << flags;
return false;
}
if (dds.size != ddsSize) {
qWarning() << "Wrong dds.size: actual =" << dds.size
qCWarning(LOG_DDSPLUGIN) << "Wrong dds.size: actual =" << dds.size
<< "expected =" << ddsSize;
return false;
}
if (dds.pixelFormat.size != pixelFormatSize) {
qWarning() << "Wrong dds.pixelFormat.size: actual =" << dds.pixelFormat.size
qCWarning(LOG_DDSPLUGIN) << "Wrong dds.pixelFormat.size: actual =" << dds.pixelFormat.size
<< "expected =" << pixelFormatSize;
return false;
}
if (dds.width > INT_MAX || dds.height > INT_MAX) {
qWarning() << "Can't read image with w/h bigger than INT_MAX";
if (dds.width > DDS_MAX_IMAGE_WIDTH || dds.height > DDS_MAX_IMAGE_HEIGHT) {
qCWarning(LOG_DDSPLUGIN) << "Can't read image with size bigger than" << DDS_MAX_IMAGE_WIDTH << "x" << DDS_MAX_IMAGE_HEIGHT << "pixels";
return false;
}

View File

@ -7,6 +7,10 @@
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "exr_p.h"
#include "scanlineconverter_p.h"
#include "util_p.h"
/* *** EXR_CONVERT_TO_SRGB ***
* If defined, the linear data is converted to sRGB on read to accommodate
* programs that do not support color profiles.
@ -28,7 +32,7 @@
* The maximum size in pixel allowed by the plugin.
*/
#ifndef EXR_MAX_IMAGE_WIDTH
#define EXR_MAX_IMAGE_WIDTH 300000
#define EXR_MAX_IMAGE_WIDTH KIF_LARGE_IMAGE_PIXEL_LIMIT
#endif
#ifndef EXR_MAX_IMAGE_HEIGHT
#define EXR_MAX_IMAGE_HEIGHT EXR_MAX_IMAGE_WIDTH
@ -50,10 +54,6 @@
#define EXR_LINES_PER_BLOCK 128
#endif
#include "exr_p.h"
#include "scanlineconverter_p.h"
#include "util_p.h"
#include <IexThrowErrnoExc.h>
#include <ImathBox.h>
#include <ImfArray.h>
@ -75,14 +75,20 @@
#include <QColorSpace>
#include <QDataStream>
#include <QDebug>
#include <QFloat16>
#include <QImage>
#include <QImageIOPlugin>
#include <QLocale>
#include <QLoggingCategory>
#include <QThread>
#include <QTimeZone>
#ifdef QT_DEBUG
Q_LOGGING_CATEGORY(LOG_EXRPLUGIN, "kf.imageformats.plugins.exr", QtDebugMsg)
#else
Q_LOGGING_CATEGORY(LOG_EXRPLUGIN, "kf.imageformats.plugins.exr", QtWarningMsg)
#endif
class K_IStream : public Imf::IStream
{
public:
@ -236,10 +242,10 @@ static QStringList viewList(const Imf::Header &h)
}
#ifdef QT_DEBUG
void printAttributes(const Imf::Header &h)
static void printAttributes(const Imf::Header &h)
{
for (auto i = h.begin(); i != h.end(); ++i) {
qDebug() << i.name();
qCDebug(LOG_EXRPLUGIN) << i.name();
}
}
#endif
@ -288,8 +294,14 @@ static void readMetadata(const Imf::Header &header, QImage &image)
if (auto pixelAspectRatio = header.findTypedAttribute<Imf::FloatAttribute>("pixelAspectRatio")) {
par = pixelAspectRatio->value();
}
image.setDotsPerMeterX(qRound(xDensity->value() * 100.0 / 2.54));
image.setDotsPerMeterY(qRound(xDensity->value() * par * 100.0 / 2.54));
auto hres = dpi2ppm(xDensity->value());
if (hres > 0) {
image.setDotsPerMeterX(hres);
}
auto vres = dpi2ppm(xDensity->value() * par);
if (vres > 0) {
image.setDotsPerMeterY(vres);
}
}
// Non-standard attribute
@ -379,14 +391,14 @@ bool EXRHandler::read(QImage *outImage)
// limiting the maximum image size on a reasonable size (as done in other plugins)
if (width > EXR_MAX_IMAGE_WIDTH || height > EXR_MAX_IMAGE_HEIGHT) {
qWarning() << "The maximum image size is limited to" << EXR_MAX_IMAGE_WIDTH << "x" << EXR_MAX_IMAGE_HEIGHT << "px";
qCWarning(LOG_EXRPLUGIN) << "The maximum image size is limited to" << EXR_MAX_IMAGE_WIDTH << "x" << EXR_MAX_IMAGE_HEIGHT << "px";
return false;
}
// creating the image
QImage image = imageAlloc(width, height, imageFormat(file));
if (image.isNull()) {
qWarning() << "Failed to allocate image, invalid size?" << QSize(width, height);
qCWarning(LOG_EXRPLUGIN) << "Failed to allocate image, invalid size?" << QSize(width, height);
return false;
}
@ -554,7 +566,7 @@ bool EXRHandler::write(const QImage &image)
// limiting the maximum image size on a reasonable size (as done in other plugins)
if (width > EXR_MAX_IMAGE_WIDTH || height > EXR_MAX_IMAGE_HEIGHT) {
qWarning() << "The maximum image size is limited to" << EXR_MAX_IMAGE_WIDTH << "x" << EXR_MAX_IMAGE_HEIGHT << "px";
qCWarning(LOG_EXRPLUGIN) << "The maximum image size is limited to" << EXR_MAX_IMAGE_WIDTH << "x" << EXR_MAX_IMAGE_HEIGHT << "px";
return false;
}
@ -770,7 +782,7 @@ int EXRHandler::currentImageNumber() const
bool EXRHandler::canRead(QIODevice *device)
{
if (!device) {
qWarning("EXRHandler::canRead() called with no device");
qCWarning(LOG_EXRPLUGIN) << "EXRHandler::canRead() called with no device";
return false;
}

View File

@ -16,13 +16,21 @@
#include <QLoggingCategory>
#include <QRegularExpressionMatch>
#include <QDebug>
/* *** HDR_HALF_QUALITY ***
* If defined, a 16-bits float image is created, otherwise a 32-bits float ones (default).
*/
//#define HDR_HALF_QUALITY // default commented -> you should define it in your cmake file
/* *** HDR_MAX_IMAGE_WIDTH and HDR_MAX_IMAGE_HEIGHT ***
* The maximum size in pixel allowed by the plugin.
*/
#ifndef HDR_MAX_IMAGE_WIDTH
#define HDR_MAX_IMAGE_WIDTH KIF_LARGE_IMAGE_PIXEL_LIMIT
#endif
#ifndef HDR_MAX_IMAGE_HEIGHT
#define HDR_MAX_IMAGE_HEIGHT HDR_MAX_IMAGE_WIDTH
#endif
typedef unsigned char uchar;
Q_LOGGING_CATEGORY(HDRPLUGIN, "kf.imageformats.plugins.hdr", QtWarningMsg)
@ -42,7 +50,10 @@ public:
Header(const Header&) = default;
Header& operator=(const Header&) = default;
bool isValid() const { return width() > 0 && height() > 0; }
bool isValid() const
{
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); }
@ -113,6 +124,7 @@ public:
{
Header h;
int cnt = 0;
int len;
QByteArray line(MAXLINE + 1, Qt::Uninitialized);
QByteArray format;
@ -154,7 +166,7 @@ public:
}
}
} while ((len > 0) && (line[0] != '\n'));
} while ((len > 0) && (line[0] != '\n') && (cnt++ < 128));
if (format != "32-bit_rle_rgbe") {
qCDebug(HDRPLUGIN) << "Unknown HDR format:" << format;
@ -411,7 +423,7 @@ bool HDRHandler::read(QImage *outImage)
QImage img;
if (!LoadHDR(s, h, img)) {
// qDebug() << "Error loading HDR file.";
// qCWarning(HDRPLUGIN) << "Error loading HDR file.";
return false;
}
@ -488,7 +500,7 @@ bool HDRHandler::canRead() const
bool HDRHandler::canRead(QIODevice *device)
{
if (!device) {
qWarning("HDRHandler::canRead() called with no device");
qCWarning(HDRPLUGIN) << "HDRHandler::canRead() called with no device";
return false;
}

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