Compare commits

...
Author SHA1 Message Date
Albert Astals Cid 8658355701 Fix crash on malformed files
oss-fuzz/449485443
2025-10-05 21:25:48 +00:00
Nicolas Fella 3c8539d53d Update version to 6.20.0 2025-10-05 15:16:13 +02:00
Nicolas Fella d332ad717b Update dependency version to 6.19.0 2025-10-03 15:04:37 +02:00
Albert Astals Cid 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
Albert Astals Cid f1b0c9f0ec add const so we know that QImage is not being modified 2025-10-01 19:15:10 +02:00
Mirco Miranda b83f4c0231 Add color space check during read test 2025-09-27 11:12:50 +02:00
Mirco Miranda a457e5ddcb Limits the max RAW size to 300000x300000 pixels 2025-09-21 15:13:17 +02:00
Mirco Miranda f28cd98661 Limits the max DDS size to 300000x300000 pixels 2025-09-21 11:28:45 +02:00
Jiaqi Wu 05c3a1afe6 Fix compilation failure with Qt 6.7 2025-09-20 13:37:52 +08:00
Mirco Miranda fda751c641 Switch all plugins to QLoggingCategory 2025-09-19 10:00:26 +02:00
Mirco Miranda 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
Azhar MominandAlbert Astals Cid 14286a6ab0 Add a CI job for oss-fuzz 2025-09-13 10:15:10 +00:00
Laurent Montel 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
Albert Astals Cid 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
Mirco Miranda 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
Mirco Miranda 56c8bc7323 Add checks on the seek return value 2025-09-09 22:20:34 +02:00
Mirco Miranda 08e178f098 Fix Null-dereference READ 2025-09-09 21:15:19 +02:00
Mirco MirandaandAlbert Astals Cid 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
Mirco MirandaandAlbert Astals Cid 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
Mirco Miranda 8036b1d032 Fix assert when read corrupted floats 2025-09-08 11:01:06 +02:00
Nicolas Fella 71cc137254 Update version to 6.19.0 2025-09-08 10:23:10 +02:00
75 changed files with 1670 additions and 443 deletions
+1
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
+4 -4
View File
@@ -1,11 +1,11 @@
cmake_minimum_required(VERSION 3.16)
set(KF_VERSION "6.18.0") # handled by release scripts
set(KF_DEP_VERSION "6.18.0") # handled by release scripts
set(KF_VERSION "6.20.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.18.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.17.0
KF 6.18.0
)
add_subdirectory(src)
+2 -2
View File
@@ -229,7 +229,7 @@ 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: same size as Qt's JPG plugin
- HDR: 300,000 x 300,000 pixels
@@ -247,7 +247,7 @@ limit depends on the format encoding).
- PXR: 65,535 x 65,535 pixels
- QOI: 300,000 x 300,000 pixels
- RAS: 300,000 x 300,000 pixels
- RAW: n/a (depends on the RAW format loaded)
- 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
+8 -3
View File
@@ -88,13 +88,17 @@ are iterated sequentially and the first object that matches the requirements
is used for testing (successes are ignored).
Supported values for JSON objects:
- `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
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
command line. The value set on the command line wins over the one in the JSON
@@ -108,8 +112,8 @@ with (if not set means all). E.g. "6.2.0".
- `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.
- `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.
@@ -123,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.
+6
View File
@@ -1,6 +1,12 @@
[
{
"fileName" : "metadata.png",
"colorSpace" : {
"description" : "sRGB build-in",
"primaries" : "SRgb",
"transferFunction" : "SRgb",
"gamma" : 0
},
"metadata" : [
{
"key" : "ModificationDate",
+7 -1
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
}
}
]
+6
View File
@@ -1,6 +1,12 @@
[
{
"fileName" : "rgb-gimp.png",
"colorSpace" : {
"description" : "",
"primaries" : "Custom",
"transferFunction" : "Linear",
"gamma" : 1
},
"resolution" : {
"dotsPerMeterX" : 3937,
"dotsPerMeterY" : 3937
+6
View File
@@ -1,6 +1,12 @@
[
{
"fileName" : "orientation_all.png",
"colorSpace" : {
"description" : "Linear sRGB",
"primaries" : "SRgb",
"transferFunction" : "Linear",
"gamma" : 1
},
"metadata" : [
{
"key" : "Software" ,
+6
View File
@@ -1,6 +1,12 @@
[
{
"fileName" : "orientation_all.png",
"colorSpace" : {
"description" : "Linear sRGB",
"primaries" : "SRgb",
"transferFunction" : "Linear",
"gamma" : 1
},
"metadata" : [
{
"key" : "Software" ,
+6
View File
@@ -1,6 +1,12 @@
[
{
"fileName" : "orientation_all.png",
"colorSpace" : {
"description" : "Linear sRGB",
"primaries" : "SRgb",
"transferFunction" : "Linear",
"gamma" : 1
},
"metadata" : [
{
"key" : "Software" ,
+6
View File
@@ -1,6 +1,12 @@
[
{
"fileName" : "orientation_all.png",
"colorSpace" : {
"description" : "Linear sRGB",
"primaries" : "SRgb",
"transferFunction" : "Linear",
"gamma" : 1
},
"metadata" : [
{
"key" : "Software" ,
+6
View File
@@ -1,6 +1,12 @@
[
{
"fileName" : "orientation_all.png",
"colorSpace" : {
"description" : "Linear sRGB",
"primaries" : "SRgb",
"transferFunction" : "Linear",
"gamma" : 1
},
"metadata" : [
{
"key" : "Software" ,
+6
View File
@@ -1,6 +1,12 @@
[
{
"fileName" : "orientation_all.png",
"colorSpace" : {
"description" : "Linear sRGB",
"primaries" : "SRgb",
"transferFunction" : "Linear",
"gamma" : 1
},
"metadata" : [
{
"key" : "Software" ,
+6
View File
@@ -1,6 +1,12 @@
[
{
"fileName" : "orientation_all.png",
"colorSpace" : {
"description" : "Linear sRGB",
"primaries" : "SRgb",
"transferFunction" : "Linear",
"gamma" : 1
},
"metadata" : [
{
"key" : "Software" ,
+6
View File
@@ -1,6 +1,12 @@
[
{
"fileName" : "orientation_all.png",
"colorSpace" : {
"description" : "Linear sRGB",
"primaries" : "SRgb",
"transferFunction" : "Linear",
"gamma" : 1
},
"metadata" : [
{
"key" : "Software" ,
+6
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.
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

+6
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

+6
View File
@@ -1,6 +1,12 @@
[
{
"fileName" : "gimp_exif.png",
"colorSpace" : {
"description" : "sRGB build-in",
"primaries" : "SRgb",
"transferFunction" : "SRgb",
"gamma" : 0
},
"metadata" : [
{
"key" : "ModificationDate",
@@ -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."
},
{
@@ -11,6 +11,12 @@
},
{
"minQtVersion" : "6.6.2",
"colorSpace" : {
"description" : "sRGB built-in",
"primaries" : "SRgb",
"transferFunction" : "SRgb",
"gamma" : 0
},
"fileName" : "testcard_rgba_fp16.png"
},
{
@@ -11,6 +11,12 @@
},
{
"minQtVersion" : "6.6.2",
"colorSpace" : {
"description" : "sRGB built-in",
"primaries" : "SRgb",
"transferFunction" : "SRgb",
"gamma" : 0
},
"fileName" : "testcard_rgba_fp32.png"
},
{
+6
View File
@@ -1,6 +1,12 @@
[
{
"fileName" : "metadata.png",
"colorSpace" : {
"description" : "sRGB build-in",
"primaries" : "SRgb",
"transferFunction" : "SRgb",
"gamma" : 0
},
"metadata" : [
{
"key" : "CreationDate",
+6
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" ,
@@ -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
@@ -1,5 +1,11 @@
[
{
"fileName" : "testcard_gray_half.png"
"fileName" : "testcard_gray_half.png",
"colorSpace" : {
"description" : "Linear sRGB",
"primaries" : "SRgb",
"transferFunction" : "Linear",
"gamma" : 1
}
}
]
@@ -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
}
}
]
+8 -1
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",
+8 -1
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",
+6
View File
@@ -1,6 +1,12 @@
[
{
"fileName" : "metadata.png",
"colorSpace" : {
"description" : "sRGB build-in",
"primaries" : "SRgb",
"transferFunction" : "SRgb",
"gamma" : 0
},
"metadata" : [
{
"key" : "ModificationDate",
+6
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" ,
+11
View File
@@ -0,0 +1,11 @@
[
{
"fileName" : "testcard_rgba.qoi",
"colorSpace" : {
"description" : "sRGB",
"primaries" : "SRgb",
"transferFunction" : "SRgb",
"gamma" : 2.31
}
}
]
@@ -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",
+6
View File
@@ -2,6 +2,12 @@
{
"fileName" : "extarea.png",
"skipSequential" : true,
"colorSpace" : {
"description" : "sRGB build-in",
"primaries" : "SRgb",
"transferFunction" : "SRgb",
"gamma" : 0
},
"metadata" : [
{
"key" : "Author",
@@ -4,8 +4,8 @@
"seeAlso" : "https://bugreports.qt.io/browse/QTBUG-120614",
"fuzziness" : 1,
"resolution" : {
"dotsPerMeterX" : 2834,
"dotsPerMeterY" : 2834
"dotsPerMeterX" : 2835,
"dotsPerMeterY" : 2835
}
}
]
+43
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)
@@ -104,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()) {
+13 -7
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()) {
+39 -32
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,
+543 -31
View File
@@ -7,9 +7,11 @@
#include "chunks_p.h"
#include "packbits_p.h"
#include "util_p.h"
#include <QBuffer>
#include <QColor>
#include <QDataStream>
#ifdef QT_DEBUG
Q_LOGGING_CATEGORY(LOG_IFFPLUGIN, "kf.imageformats.plugins.iff", QtDebugMsg)
@@ -306,6 +308,8 @@ IFFChunk::ChunkList IFFChunk::innerFromDevice(QIODevice *d, bool *ok, IFFChunk *
chunk = QSharedPointer<IFFChunk>(new ICCPChunk());
} else if (cid == NAME_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new NAMEChunk());
} else if (cid == PCHG_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new PCHGChunk());
} else if (cid == RAST_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new RASTChunk());
} else if (cid == RGBA_CHUNK) {
@@ -316,6 +320,8 @@ IFFChunk::ChunkList IFFChunk::innerFromDevice(QIODevice *d, bool *ok, IFFChunk *
chunk = QSharedPointer<IFFChunk>(new TBHDChunk());
} else if (cid == VERS_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new VERSChunk());
} else if (cid == XBMI_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new XBMIChunk());
} else if (cid == XMP0_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new XMP0Chunk());
} else { // unknown chunk
@@ -539,8 +545,12 @@ bool CMAPChunk::innerReadStructure(QIODevice *d)
QList<QRgb> CMAPChunk::innerPalette() const
{
QList<QRgb> l;
auto &&d = data();
for (qint32 i = 0, n = count(); i < n; ++i) {
const QByteArray &d = data();
const qint32 n = count();
if (n * 3 > d.size()) {
return {};
}
for (qint32 i = 0; i < n; ++i) {
auto i3 = i * 3;
l << qRgb(d.at(i3), d.at(i3 + 1), d.at(i3 + 2));
}
@@ -651,7 +661,7 @@ quint16 DPIChunk::dpiX() const
if (bytes() < 4) {
return 0;
}
return i16(data().at(1), data().at(0));
return ui16(data().at(1), data().at(0));
}
quint16 DPIChunk::dpiY() const
@@ -659,17 +669,17 @@ quint16 DPIChunk::dpiY() const
if (bytes() < 4) {
return 0;
}
return i16(data().at(3), data().at(2));
return ui16(data().at(3), data().at(2));
}
qint32 DPIChunk::dotsPerMeterX() const
{
return qRound(dpiX() / 25.4 * 1000);
return dpi2ppm(dpiX());
}
qint32 DPIChunk::dotsPerMeterY() const
{
return qRound(dpiY() / 25.4 * 1000);
return dpi2ppm(dpiY());
}
bool DPIChunk::innerReadStructure(QIODevice *d)
@@ -677,6 +687,51 @@ bool DPIChunk::innerReadStructure(QIODevice *d)
return cacheData(d);
}
/* ******************
* *** XBMI Chunk ***
* ****************** */
XBMIChunk::~XBMIChunk()
{
}
XBMIChunk::XBMIChunk() : DPIChunk()
{
}
bool XBMIChunk::isValid() const
{
if (dpiX() == 0 || dpiY() == 0) {
return false;
}
return chunkId() == XBMIChunk::defaultChunkId();
}
quint16 XBMIChunk::dpiX() const
{
if (bytes() < 6) {
return 0;
}
return ui16(data().at(3), data().at(2));
}
quint16 XBMIChunk::dpiY() const
{
if (bytes() < 6) {
return 0;
}
return ui16(data().at(5), data().at(4));
}
XBMIChunk::PictureType XBMIChunk::pictureType() const
{
if (bytes() < 6) {
return PictureType(-1);
}
return PictureType(i16(data().at(1), data().at(0)));
}
/* ******************
* *** BODY Chunk ***
* ****************** */
@@ -884,7 +939,10 @@ quint32 BODYChunk::strideSize(const BMHDChunk *header, const QByteArray& formTyp
}
// ILBM
return header->rowLen() * header->bitplanes();
auto sz = header->rowLen() * header->bitplanes();
if (header->masking() == BMHDChunk::Masking::HasMask)
sz += header->rowLen();
return sz;
}
QByteArray BODYChunk::pbm(const QByteArray &planes, qint32, const BMHDChunk *header, const CAMGChunk *, const CMAPChunk *, const IPALChunk *) const
@@ -959,11 +1017,18 @@ QByteArray BODYChunk::deinterleave(const QByteArray &planes, qint32 y, const BMH
ba = QByteArray(rowLen * 8 * 3, char());
auto pal = cmap->palette();
if (ipal) {
auto tmp = ipal->palette(y, header->height());
auto tmp = ipal->palette(y);
if (tmp.size() == pal.size())
pal = tmp;
}
auto max = (1 << (bitplanes - 2)) - 1;
// HAM 6: 2 control bits+4 bits of data, 16-color palette
//
// HAM 8: 2 control bits+6 bits of data, 64-color palette
//
// HAM 5: 1 control bit (and 1 hardwired to zero)+4 bits of data
// (red and green modify operations are unavailable)
auto ctlbits = bitplanes > 5 ? 2 : 1;
auto max = (1 << (bitplanes - ctlbits)) - 1;
quint8 prev[3] = {};
for (qint32 i = 0, cnt = 0; i < rowLen; ++i) {
for (qint32 j = 0; j < 8; ++j, ++cnt) {
@@ -971,11 +1036,14 @@ QByteArray BODYChunk::deinterleave(const QByteArray &planes, qint32 y, const BMH
for (qint32 k = 0, msk = (1 << (7 - j)); k < bitplanes; ++k) {
if ((planes.at(k * rowLen + i) & msk) == 0)
continue;
if (k < bitplanes - 2)
if (k < bitplanes - ctlbits)
idx |= 1 << k;
else
ctl |= 1 << (bitplanes - k - 1);
}
if (ctl && ctlbits == 1) {
ctl <<= 1; // HAM 5 has only 1 control bit and the LSB is always 0
}
switch (ctl) {
case 1: // red
prev[0] = idx * 255 / max;
@@ -1049,22 +1117,23 @@ QByteArray BODYChunk::deinterleave(const QByteArray &planes, qint32 y, const BMH
for (qint32 i = 0; i < rowLen; ++i) {
for (qint32 k = 0, i8 = i * 8; k < bitplanes; ++k) {
auto v = planes.at(k * rowLen + i);
auto msk = 1 << k;
if (v & (1 << 7))
ba[i8] |= 1 << k;
ba[i8] |= msk;
if (v & (1 << 6))
ba[i8 + 1] |= 1 << k;
ba[i8 + 1] |= msk;
if (v & (1 << 5))
ba[i8 + 2] |= 1 << k;
ba[i8 + 2] |= msk;
if (v & (1 << 4))
ba[i8 + 3] |= 1 << k;
ba[i8 + 3] |= msk;
if (v & (1 << 3))
ba[i8 + 4] |= 1 << k;
ba[i8 + 4] |= msk;
if (v & (1 << 2))
ba[i8 + 5] |= 1 << k;
ba[i8 + 5] |= msk;
if (v & (1 << 1))
ba[i8 + 6] |= 1 << k;
ba[i8 + 6] |= msk;
if (v & 1)
ba[i8 + 7] |= 1 << k;
ba[i8 + 7] |= msk;
}
}
}
@@ -1257,8 +1326,9 @@ QImage::Format IFOR_Chunk::optionformat() const
{
auto fmt = this->format();
if (fmt == QImage::Format_Indexed8) {
if (searchIPal())
fmt = FORMAT_RGB_8BIT;
if (auto ipal = searchIPal()) {
fmt = ipal->hasAlpha() ? FORMAT_RGBA_8BIT : FORMAT_RGB_8BIT;
}
}
return fmt;
}
@@ -1282,6 +1352,10 @@ const IPALChunk *IFOR_Chunk::searchIPal() const
if (!rast.isEmpty()) {
ipal = rast.first();
}
auto pchg = IFFChunk::searchT<PCHGChunk>(this);
if (!pchg.isEmpty()) {
ipal = pchg.first();
}
if (ipal && ipal->isValid()) {
return ipal;
}
@@ -1372,11 +1446,6 @@ QImage::Format FORMChunk::format() const
return QImage::Format_RGBA64;
}
if (h->bitplanes() >= 1 && h->bitplanes() <= 8) {
if (!IFFChunk::search(PCHG_CHUNK, chunks()).isEmpty()) {
qCDebug(LOG_IFFPLUGIN) << "FORMChunk::format(): PCHG chunk is not supported";
return QImage::Format_Invalid;
}
if (h->bitplanes() >= BITPLANES_HAM_MIN && h->bitplanes() <= BITPLANES_HAM_MAX) {
if (modeId & CAMGChunk::ModeId::Ham)
return FORMAT_RGB_8BIT;
@@ -2310,7 +2379,9 @@ BEAMChunk::~BEAMChunk()
}
BEAMChunk::BEAMChunk() : IPALChunk()
BEAMChunk::BEAMChunk()
: IPALChunk()
, _height()
{
}
@@ -2320,8 +2391,20 @@ bool BEAMChunk::isValid() const
return chunkId() == BEAMChunk::defaultChunkId();
}
QList<QRgb> BEAMChunk::palette(qint32 y, qint32 height) const
IPALChunk *BEAMChunk::clone() const
{
return new BEAMChunk(*this);
}
bool BEAMChunk::initialize(const QList<QRgb> &, qint32 height)
{
_height = height;
return true;
}
QList<QRgb> BEAMChunk::palette(qint32 y) const
{
auto &&height = _height;
if (height < 1) {
return {};
}
@@ -2378,7 +2461,9 @@ SHAMChunk::~SHAMChunk()
}
SHAMChunk::SHAMChunk() : IPALChunk()
SHAMChunk::SHAMChunk()
: IPALChunk()
, _height()
{
}
@@ -2398,8 +2483,14 @@ bool SHAMChunk::isValid() const
return chunkId() == SHAMChunk::defaultChunkId();
}
QList<QRgb> SHAMChunk::palette(qint32 y, qint32 height) const
IPALChunk *SHAMChunk::clone() const
{
return new SHAMChunk(*this);
}
QList<QRgb> SHAMChunk::palette(qint32 y) const
{
auto && height = _height;
if (height < 1) {
return {};
}
@@ -2426,6 +2517,12 @@ QList<QRgb> SHAMChunk::palette(qint32 y, qint32 height) const
return pal;
}
bool SHAMChunk::initialize(const QList<QRgb> &, qint32 height)
{
_height = height;
return true;
}
bool SHAMChunk::innerReadStructure(QIODevice *d)
{
return cacheData(d);
@@ -2440,7 +2537,9 @@ RASTChunk::~RASTChunk()
}
RASTChunk::RASTChunk() : IPALChunk()
RASTChunk::RASTChunk()
: IPALChunk()
, _height()
{
}
@@ -2450,8 +2549,14 @@ bool RASTChunk::isValid() const
return chunkId() == RASTChunk::defaultChunkId();
}
QList<QRgb> RASTChunk::palette(qint32 y, qint32 height) const
IPALChunk *RASTChunk::clone() const
{
return new RASTChunk(*this);
}
QList<QRgb> RASTChunk::palette(qint32 y) const
{
auto &&height = _height;
if (height < 1) {
return {};
}
@@ -2478,7 +2583,414 @@ QList<QRgb> RASTChunk::palette(qint32 y, qint32 height) const
return pal;
}
bool RASTChunk::initialize(const QList<QRgb> &, qint32 height)
{
_height = height;
return true;
}
bool RASTChunk::innerReadStructure(QIODevice *d)
{
return cacheData(d);
}
/* ******************
* *** PCHG Chunk ***
* ****************** */
PCHGChunk::~PCHGChunk()
{
}
PCHGChunk::PCHGChunk() : IPALChunk()
{
}
PCHGChunk::Compression PCHGChunk::compression() const
{
if (!isValid()) {
return Compression::Uncompressed;
}
return Compression(ui16(data(), 0));
}
PCHGChunk::Flags PCHGChunk::flags() const
{
if (!isValid()) {
return Flags(Flag::None);
}
return Flags(ui16(data(), 2));
}
qint16 PCHGChunk::startLine() const
{
if (!isValid()) {
return 0;
}
return i16(data(), 4);
}
quint16 PCHGChunk::lineCount() const
{
if (!isValid()) {
return 0;
}
return ui16(data(), 6);
}
quint16 PCHGChunk::changedLines() const
{
if (!isValid()) {
return 0;
}
return ui16(data(), 8);
}
quint16 PCHGChunk::minReg() const
{
if (!isValid()) {
return 0;
}
return ui16(data(), 10);
}
quint16 PCHGChunk::maxReg() const
{
if (!isValid()) {
return 0;
}
return ui16(data(), 12);
}
quint16 PCHGChunk::maxChanges() const
{
if (!isValid()) {
return 0;
}
return ui16(data(), 14);
}
quint32 PCHGChunk::totalChanges() const
{
if (!isValid()) {
return 0;
}
return ui32(data(), 16);
}
bool PCHGChunk::hasAlpha() const
{
return (flags() & PCHGChunk::Flag::UseAlpha) ? true : false;
}
bool PCHGChunk::isValid() const
{
if (bytes() < 20) {
return false;
}
return chunkId() == PCHGChunk::defaultChunkId();
}
IPALChunk *PCHGChunk::clone() const
{
return new PCHGChunk(*this);
}
QList<QRgb> PCHGChunk::palette(qint32 y) const
{
return _palettes.value(y);
}
// ----------------------------------------------------------------------------
// PCHG_FastDecomp reimplementation (Amiga 68k -> portable C++/Qt)
// ----------------------------------------------------------------------------
// This mirrors the original 68k routine semantics:
// - The Huffman tree is stored as a sequence of signed 16-bit words (big-endian)
// and TreeCode points to the *last word* of that sequence.
// - Bits are consumed MSB-first from 32-bit big-endian longwords of the source.
// - Navigation rules (matching the assembly):
// bit=1: read w = *(a3). If w < 0 then a3 += w (byte-wise) and continue;
// else emit (w & 0xFF) and reset a3 to TreeCode (last word).
// bit=0: predecrement a3 by 2; read w = *a3. If w < 0: continue;
// else if (w & 0x0100) emit (w & 0xFF) and reset a3; else continue.
// - Stop after writing exactly OriginalSize bytes.
//
// This function expects a single QByteArray laid out as:
// [ tree (treeSize bytes, even) | compressed bitstream (... bytes) ]
//
// On any error, logs with qCCritical(LOG_IFFPLUGIN) and returns {}.
// Comments are in English as requested.
// ----------------------------------------------------------------------------
//
// NOTE: Sebastiano Vigna, the author of the PCHG specification and the ASM
// decompression code for the Motorola 68K, gave us permission to use his
// code and recommended that we convert it with AI.
// Read a big-endian 16-bit signed word from a byte buffer
static inline qint16 read_be16(const char* base, int byteIndex, int size)
{
if (byteIndex + 1 >= size)
return 0; // caller must bounds-check; we keep silent here
const quint8 b0 = static_cast<quint8>(base[byteIndex]);
const quint8 b1 = static_cast<quint8>(base[byteIndex + 1]);
return static_cast<qint16>((b0 << 8) | b1);
}
// Read a big-endian 32-bit unsigned long from a byte buffer
static inline quint32 read_be32(const char* base, int byteIndex, int size)
{
if (byteIndex + 3 >= size)
return 0; // caller must bounds-check
const quint8 b0 = static_cast<quint8>(base[byteIndex]);
const quint8 b1 = static_cast<quint8>(base[byteIndex + 1]);
const quint8 b2 = static_cast<quint8>(base[byteIndex + 2]);
const quint8 b3 = static_cast<quint8>(base[byteIndex + 3]);
return (static_cast<quint32>(b0) << 24) |
(static_cast<quint32>(b1) << 16) |
(static_cast<quint32>(b2) << 8) |
static_cast<quint32>(b3);
}
// Core decompressor (tree + compressed stream in one QByteArray)
static QByteArray pchgFastDecomp(const QByteArray& input, int treeSize, int originalSize)
{
// Basic validation
if (treeSize <= 0 || (treeSize & 1)) {
qCCritical(LOG_IFFPLUGIN) << "Invalid treeSize (must be positive and even)" << treeSize;
return {};
}
if (input.size() < treeSize) {
qCCritical(LOG_IFFPLUGIN) << "Input too small for treeSize" << input.size() << treeSize;
return {};
}
if (originalSize < 0) {
qCCritical(LOG_IFFPLUGIN) << "Invalid originalSize" << originalSize;
return {};
}
const char* data = input.constData();
const int totalSize = input.size();
// Tree view (big-endian words)
const int treeBytes = treeSize;
const int treeWords = treeBytes / 2;
if (treeWords <= 0) {
qCCritical(LOG_IFFPLUGIN) << "Tree has zero words";
return {};
}
// Compressed stream
const int srcBase = treeBytes; // offset where bitstream starts
const int srcSize = totalSize - srcBase;
if (srcSize <= 0 && originalSize > 0) {
qCCritical(LOG_IFFPLUGIN) << "No compressed payload present";
return {};
}
QByteArray out;
out.resize(originalSize);
char* outPtr = out.data();
// Emulate a3 pointer to words:
// a2 points to the *last word* => word index (0..treeWords-1)
auto resetA3 = [&]() {
return treeWords - 1; // last word index
};
int a3_word = resetA3();
// Bit reader: loads 32b big-endian and shifts MSB-first
quint32 bitbuf = 0;
int bits = 0; // remaining bits in bitbuf
int srcPos = 0; // byte offset relative to srcBase
auto refill = [&]() -> bool {
if (srcPos + 4 > srcSize) {
qCCritical(LOG_IFFPLUGIN) << "Compressed stream underflow while refilling bit buffer"
<< "srcPos=" << srcPos << "srcSize=" << srcSize;
return false;
}
bitbuf = read_be32(data + srcBase, srcPos, srcSize);
bits = 32;
srcPos += 4;
return true;
};
int produced = 0;
// Main decode loop: produce exactly originalSize bytes
while (produced < originalSize) {
if (bits == 0) {
if (!refill()) {
// Not enough bits to complete output
return {};
}
}
const bool bit1 = (bitbuf & 0x80000000u) != 0u; // MSB before shift
bitbuf <<= 1;
--bits;
if (bit1) {
// Case bit == 1 --> w = *(a3)
if (a3_word < 0 || a3_word >= treeWords) {
qCCritical(LOG_IFFPLUGIN) << "a3 out of bounds (bit=1)" << a3_word;
return {};
}
const int byteIndex = a3_word * 2;
const qint16 w = read_be16(data, byteIndex, treeBytes);
if (w < 0) {
// a3 += w (w is a signed byte offset, must be even)
if (w & 1) {
qCCritical(LOG_IFFPLUGIN) << "Misaligned tree offset (odd)" << w;
return {};
}
const int deltaWords = w / 2; // arithmetic division, w is even in valid streams
const int next = a3_word + deltaWords;
if (next < 0 || next >= treeWords) {
qCCritical(LOG_IFFPLUGIN) << "a3 out of bounds after offset" << next;
return {};
}
a3_word = next;
} else {
// Leaf: emit low 8 bits, reset a3
outPtr[produced++] = static_cast<char>(w & 0xFF);
a3_word = resetA3();
}
} else {
// Case bit == 0 --> w = *--a3 (predecrement)
--a3_word;
if (a3_word < 0) {
qCCritical(LOG_IFFPLUGIN) << "a3 underflow on predecrement";
return {};
}
const int byteIndex = a3_word * 2;
const qint16 w = read_be16(data, byteIndex, treeBytes);
if (w < 0) {
// Internal node: continue with current a3
continue;
}
// Non-negative: check bit #8; if set -> leaf
if ((w & 0x0100) != 0) {
outPtr[produced++] = static_cast<char>(w & 0xFF);
a3_word = resetA3();
} else {
// Not a leaf: continue scanning
continue;
}
}
}
return out;
}
// !Huffman decompression
bool PCHGChunk::initialize(const QList<QRgb> &cmapPalette, qint32 height)
{
Q_UNUSED(height)
auto dt = data().mid(20);
if (compression() == PCHGChunk::Compression::Huffman) {
QDataStream ds(dt);
ds.setByteOrder(QDataStream::BigEndian);
quint32 infoSize;
ds >> infoSize;
quint32 origSize;
ds >> origSize;
dt = pchgFastDecomp(dt.mid(8), infoSize, origSize);
}
if (dt.isEmpty()) {
return false;
}
QDataStream ds(dt);
ds.setByteOrder(QDataStream::BigEndian);
// read the masks
auto lcnt = lineCount();
auto nlw = (lcnt + 31) / 32; // number of LWORD containing the bit mask
QList<quint32> masks;
for (auto i = 0; i < nlw; ++i) {
quint32 mask;
ds >> mask;
masks << mask;
}
if (ds.status() != QDataStream::Ok) {
return false;
}
// read the palettes
auto changesLoaded = qint64();
auto startY = startLine();
auto last = cmapPalette;
auto flgs = flags();
for (auto i = 0; i < lcnt; ++i) {
auto mask = masks.at(i / 32);
if (((mask >> (31 - i % 32)) & 1) == 0) {
_palettes.insert(i + startY, last);
continue; // no palette change for this line
}
QHash<quint16, QRgb> hash;
if (flgs & PCHGChunk::Flag::F12Bit) {
quint8 c16;
ds >> c16;
quint8 c32;
ds >> c32;
for (auto j = 0; j < int(c16); ++j) {
quint16 tmp;
ds >> tmp;
hash.insert(((tmp >> 12) & 0xF), qRgb(((tmp >> 8) & 0xF) * 17, ((tmp >> 4) & 0xF) * 17, ((tmp & 0xF) * 17)));
}
for (auto j = 0; j < int(c32); ++j) {
quint16 tmp;
ds >> tmp;
hash.insert((((tmp >> 12) & 0xF) + 16), qRgb(((tmp >> 8) & 0xF) * 17, ((tmp >> 4) & 0xF) * 17, ((tmp & 0xF) * 17)));
}
} else if (flgs & PCHGChunk::Flag::F32Bit) { // NOTE: missing test case (not tested)
quint16 cnt;
ds >> cnt;
for (auto j = 0; j < int(cnt); ++j) {
quint16 reg;
ds >> reg;
quint8 alpha;
ds >> alpha;
quint8 red;
ds >> red;
quint8 blue;
ds >> blue;
quint8 green;
ds >> green;
hash.insert(reg, qRgba(red, green, blue, flgs & PCHGChunk::Flag::UseAlpha ? alpha : 0xFF));
}
}
if (ds.status() != QDataStream::Ok) {
return false;
}
for (auto i = qsizetype(), n = last.size(); i < n; ++i) {
if (hash.contains(i))
last[i] = hash.value(i);
}
_palettes.insert(i + startY, last);
changesLoaded += hash.size();
}
if (changesLoaded != qint64(totalChanges())) {
qCDebug(LOG_IFFPLUGIN) << "PCHGChunk::innerReadStructure(): palette changes count mismatch!";
}
return true;
}
bool PCHGChunk::innerReadStructure(QIODevice *d)
{
return cacheData(d);
}
+193 -10
View File
@@ -17,6 +17,7 @@
#include <QByteArray>
#include <QDateTime>
#include <QHash>
#include <QImage>
#include <QIODevice>
#include <QLoggingCategory>
@@ -54,6 +55,7 @@ Q_DECLARE_LOGGING_CATEGORY(LOG_IFFPLUGIN)
#define CMAP_CHUNK QByteArray("CMAP")
#define CMYK_CHUNK QByteArray("CMYK") // https://wiki.amigaos.net/wiki/ILBM_IFF_Interleaved_Bitmap#ILBM.CMYK
#define DPI__CHUNK QByteArray("DPI ")
#define XBMI_CHUNK QByteArray("XBMI")
// Different palette for scanline
#define BEAM_CHUNK QByteArray("BEAM")
@@ -91,17 +93,20 @@ Q_DECLARE_LOGGING_CATEGORY(LOG_IFFPLUGIN)
#define CHUNKID_DEFINE(a) static QByteArray defaultChunkId() { return a; }
// The 8-bit RGB format must be one. If you change it here, you have also to use the same
// 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
#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>>;
@@ -318,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);
@@ -358,7 +375,36 @@ class IPALChunk : public IFFChunk
public:
virtual ~IPALChunk() override {}
IPALChunk() : IFFChunk() {}
virtual QList<QRgb> palette(qint32 y, qint32 height) const = 0;
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;
};
@@ -376,7 +422,17 @@ public:
};
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
@@ -385,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;
@@ -605,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
@@ -631,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
@@ -1312,12 +1412,19 @@ public:
virtual bool isValid() const override;
virtual QList<QRgb> palette(qint32 y, qint32 height) 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;
};
/*!
@@ -1349,12 +1456,19 @@ public:
virtual bool isValid() const override;
virtual QList<QRgb> palette(qint32 y, qint32 height) 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;
};
/*!
@@ -1373,13 +1487,82 @@ public:
virtual bool isValid() const override;
virtual QList<QRgb> palette(qint32 y, qint32 height) 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
+40 -16
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;
}
@@ -1126,11 +1154,7 @@ static QImage readR32F(QDataStream &s, const quint32 width, const quint32 height
for (quint32 y = 0; y < height; y++) {
float *line = reinterpret_cast<float *>(image.scanLine(y));
for (quint32 x = 0; x < width; x++) {
const float f = readFloat32(s);
if (std::isnan(f)) {
return {};
}
line[x * 4] = f;
line[x * 4] = readFloat32(s);
line[x * 4 + 1] = 0;
line[x * 4 + 2] = 0;
line[x * 4 + 3] = 1;
@@ -2381,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;
}
@@ -2454,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;
}
@@ -2479,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;
}
@@ -2512,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;
}
+21 -9
View File
@@ -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;
}
+4 -5
View File
@@ -16,8 +16,6 @@
#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).
*/
@@ -126,6 +124,7 @@ public:
{
Header h;
int cnt = 0;
int len;
QByteArray line(MAXLINE + 1, Qt::Uninitialized);
QByteArray format;
@@ -167,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;
@@ -424,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;
}
@@ -501,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;
}
+37 -30
View File
@@ -13,12 +13,19 @@
#include <libheif/heif.h>
#include <QColorSpace>
#include <QDebug>
#include <QLoggingCategory>
#include <QPointF>
#include <QSysInfo>
#include <cstring>
#include <limits>
#ifdef QT_DEBUG
Q_LOGGING_CATEGORY(LOG_HEIFPLUGIN, "kf.imageformats.plugins.heif", QtDebugMsg)
#else
Q_LOGGING_CATEGORY(LOG_HEIFPLUGIN, "kf.imageformats.plugins.heif", QtWarningMsg)
#endif
#ifndef HEIF_MAX_METADATA_SIZE
/*!
* XMP and EXIF maximum size.
@@ -112,7 +119,7 @@ bool HEIFHandler::read(QImage *outImage)
bool HEIFHandler::write(const QImage &image)
{
if (image.format() == QImage::Format_Invalid || image.isNull()) {
qWarning("No image data to save");
qCWarning(LOG_HEIFPLUGIN) << "No image data to save";
return false;
}
@@ -208,7 +215,7 @@ bool HEIFHandler::write_helper(const QImage &image)
err = heif_image_create(tmpimage.width(), tmpimage.height(), heif_colorspace_RGB, chroma, &h_image);
if (err.code) {
qWarning() << "heif_image_create error:" << err.message;
qCWarning(LOG_HEIFPLUGIN) << "heif_image_create error:" << err.message;
heif_context_free(context);
return false;
}
@@ -287,7 +294,7 @@ bool HEIFHandler::write_helper(const QImage &image)
}
break;
default:
qWarning() << "Unsupported depth:" << save_depth;
qCWarning(LOG_HEIFPLUGIN) << "Unsupported depth:" << save_depth;
heif_image_release(h_image);
heif_context_free(context);
return false;
@@ -297,7 +304,7 @@ bool HEIFHandler::write_helper(const QImage &image)
struct heif_encoder *encoder = nullptr;
err = heif_context_get_encoder_for_format(context, encoder_codec, &encoder);
if (err.code) {
qWarning() << "Unable to get an encoder instance:" << err.message;
qCWarning(LOG_HEIFPLUGIN) << "Unable to get an encoder instance:" << err.message;
heif_image_release(h_image);
heif_context_free(context);
return false;
@@ -315,7 +322,7 @@ bool HEIFHandler::write_helper(const QImage &image)
encoder_options->save_alpha_channel = save_alpha;
if ((tmpimage.width() % 2 == 1) || (tmpimage.height() % 2 == 1)) {
qWarning() << "Image has odd dimension!\nUse even-numbered dimension(s) for better compatibility with other HEIF implementations.";
qCWarning(LOG_HEIFPLUGIN) << "Image has odd dimension!\nUse even-numbered dimension(s) for better compatibility with other HEIF implementations.";
if (save_alpha) {
// This helps to save alpha channel when image has odd dimension
encoder_options->macOS_compatibility_workaround = 0;
@@ -347,7 +354,7 @@ bool HEIFHandler::write_helper(const QImage &image)
}
if (err.code) {
qWarning() << "heif_context_encode_image failed:" << err.message;
qCWarning(LOG_HEIFPLUGIN) << "heif_context_encode_image failed:" << err.message;
heif_encoder_release(encoder);
heif_image_release(h_image);
heif_context_free(context);
@@ -364,7 +371,7 @@ bool HEIFHandler::write_helper(const QImage &image)
heif_image_release(h_image);
if (err.code) {
qWarning() << "Writing HEIF image failed:" << err.message;
qCWarning(LOG_HEIFPLUGIN) << "Writing HEIF image failed:" << err.message;
heif_context_free(context);
return false;
}
@@ -524,7 +531,7 @@ bool HEIFHandler::ensureDecoder()
struct heif_error err = heif_context_read_from_memory(ctx, static_cast<const void *>(buffer.constData()), buffer.size(), nullptr);
if (err.code) {
qWarning() << "heif_context_read_from_memory error:" << err.message;
qCWarning(LOG_HEIFPLUGIN) << "heif_context_read_from_memory error:" << err.message;
heif_context_free(ctx);
m_parseState = ParseHeicError;
return false;
@@ -533,7 +540,7 @@ bool HEIFHandler::ensureDecoder()
struct heif_image_handle *handle = nullptr;
err = heif_context_get_primary_image_handle(ctx, &handle);
if (err.code) {
qWarning() << "heif_context_get_primary_image_handle error:" << err.message;
qCWarning(LOG_HEIFPLUGIN) << "heif_context_get_primary_image_handle error:" << err.message;
heif_context_free(ctx);
m_parseState = ParseHeicError;
return false;
@@ -543,7 +550,7 @@ bool HEIFHandler::ensureDecoder()
m_parseState = ParseHeicError;
heif_image_handle_release(handle);
heif_context_free(ctx);
qWarning() << "HEIC image has zero dimension";
qCWarning(LOG_HEIFPLUGIN) << "HEIC image has zero dimension";
return false;
}
@@ -553,7 +560,7 @@ bool HEIFHandler::ensureDecoder()
m_parseState = ParseHeicError;
heif_image_handle_release(handle);
heif_context_free(ctx);
qWarning() << "HEIF image with undefined or unsupported bit depth.";
qCWarning(LOG_HEIFPLUGIN) << "HEIF image with undefined or unsupported bit depth.";
return false;
}
@@ -582,7 +589,7 @@ bool HEIFHandler::ensureDecoder()
m_parseState = ParseHeicError;
heif_image_handle_release(handle);
heif_context_free(ctx);
qWarning() << "Unsupported bit depth:" << bit_depth;
qCWarning(LOG_HEIFPLUGIN) << "Unsupported bit depth:" << bit_depth;
return false;
}
@@ -597,7 +604,7 @@ bool HEIFHandler::ensureDecoder()
#if LIBHEIF_HAVE_VERSION(1, 13, 0)
if (err.code == heif_error_Invalid_input && err.subcode == heif_suberror_Unknown_NCLX_matrix_coefficients && img == nullptr && buffer.contains("Xiaomi")) {
qWarning() << "Non-standard HEIF image with invalid matrix_coefficients, probably made by a Xiaomi device!";
qCWarning(LOG_HEIFPLUGIN) << "Non-standard HEIF image with invalid matrix_coefficients, probably made by a Xiaomi device!";
// second try to decode with strict decoding disabled
decoder_option->strict_decoding = 0;
@@ -610,7 +617,7 @@ bool HEIFHandler::ensureDecoder()
}
if (err.code) {
qWarning() << "heif_decode_image error:" << err.message;
qCWarning(LOG_HEIFPLUGIN) << "heif_decode_image error:" << err.message;
heif_image_handle_release(handle);
heif_context_free(ctx);
m_parseState = ParseHeicError;
@@ -627,7 +634,7 @@ bool HEIFHandler::ensureDecoder()
heif_image_handle_release(handle);
heif_context_free(ctx);
m_parseState = ParseHeicError;
qWarning() << "HEIC image size invalid:" << imageSize;
qCWarning(LOG_HEIFPLUGIN) << "HEIC image size invalid:" << imageSize;
return false;
}
@@ -639,7 +646,7 @@ bool HEIFHandler::ensureDecoder()
heif_image_handle_release(handle);
heif_context_free(ctx);
m_parseState = ParseHeicError;
qWarning() << "HEIC data pixels information not valid!";
qCWarning(LOG_HEIFPLUGIN) << "HEIC data pixels information not valid!";
return false;
}
@@ -649,7 +656,7 @@ bool HEIFHandler::ensureDecoder()
heif_image_handle_release(handle);
heif_context_free(ctx);
m_parseState = ParseHeicError;
qWarning() << "Unable to allocate memory!";
qCWarning(LOG_HEIFPLUGIN) << "Unable to allocate memory!";
return false;
}
@@ -844,7 +851,7 @@ bool HEIFHandler::ensureDecoder()
heif_image_handle_release(handle);
heif_context_free(ctx);
m_parseState = ParseHeicError;
qWarning() << "Unsupported bit depth:" << bit_depth;
qCWarning(LOG_HEIFPLUGIN) << "Unsupported bit depth:" << bit_depth;
return false;
break;
}
@@ -856,15 +863,15 @@ bool HEIFHandler::ensureDecoder()
QByteArray ba(rawProfileSize, 0);
err = heif_image_handle_get_raw_color_profile(handle, ba.data());
if (err.code) {
qWarning() << "icc profile loading failed";
qCWarning(LOG_HEIFPLUGIN) << "icc profile loading failed";
} else {
QColorSpace colorspace = QColorSpace::fromIccProfile(ba);
if (!colorspace.isValid()) {
qWarning() << "HEIC image has Qt-unsupported or invalid ICC profile!";
qCWarning(LOG_HEIFPLUGIN) << "HEIC 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 expected for HEIF, discarding the ICCprofile!");
qCWarning(LOG_HEIFPLUGIN) << "CMYK ICC profile is not expected for HEIF, discarding the ICCprofile!";
colorspace = QColorSpace();
} else if (colorspace.colorModel() == QColorSpace::ColorModel::Gray) {
if (hasAlphaChannel) {
@@ -884,7 +891,7 @@ bool HEIFHandler::ensureDecoder()
}
colorspace = QColorSpace(gray_whitePoint, redP, greenP, blueP, trc_new, gamma_new);
if (!colorspace.isValid()) {
qWarning("HEIF plugin created invalid QColorSpace!");
qCWarning(LOG_HEIFPLUGIN) << "HEIF plugin created invalid QColorSpace!";
}
} else { // no alpha channel
m_current_image.convertTo(bit_depth > 8 ? QImage::Format_Grayscale16 : QImage::Format_Grayscale8);
@@ -894,14 +901,14 @@ bool HEIFHandler::ensureDecoder()
m_current_image.setColorSpace(colorspace);
}
} else {
qWarning() << "icc profile is empty or above limits";
qCWarning(LOG_HEIFPLUGIN) << "icc profile is empty or above limits";
}
} else if (profileType == heif_color_profile_type_nclx) {
struct heif_color_profile_nclx *nclx = nullptr;
err = heif_image_handle_get_nclx_color_profile(handle, &nclx);
if (err.code || !nclx) {
qWarning() << "nclx profile loading failed";
qCWarning(LOG_HEIFPLUGIN) << "nclx profile loading failed";
} else {
const QPointF redPoint(nclx->color_primary_red_x, nclx->color_primary_red_y);
const QPointF greenPoint(nclx->color_primary_green_x, nclx->color_primary_green_y);
@@ -936,9 +943,9 @@ bool HEIFHandler::ensureDecoder()
break;
#endif
default:
qWarning("CICP color_primaries: %d, transfer_characteristics: %d\nThe colorspace is unsupported by this plug-in yet.",
nclx->color_primaries,
nclx->transfer_characteristics);
qCWarning(LOG_HEIFPLUGIN) << "CICP color_primaries: %d, transfer_characteristics: %d\nThe colorspace is unsupported by this plug-in yet."
<< nclx->color_primaries
<< nclx->transfer_characteristics;
q_trc = QColorSpace::TransferFunction::SRgb;
break;
}
@@ -960,7 +967,7 @@ bool HEIFHandler::ensureDecoder()
heif_nclx_color_profile_free(nclx);
if (!m_current_image.colorSpace().isValid()) {
qWarning() << "HEIC plugin created invalid QColorSpace from NCLX!";
qCWarning(LOG_HEIFPLUGIN) << "HEIC plugin created invalid QColorSpace from NCLX!";
}
}
@@ -984,7 +991,7 @@ bool HEIFHandler::ensureDecoder()
QByteArray ba(sz, char());
auto err = heif_image_handle_get_metadata(handle, ids[n], ba.data());
if (err.code != heif_error_Ok) {
qWarning() << "Error while reading metadata" << err.message;
qCWarning(LOG_HEIFPLUGIN) << "Error while reading metadata" << err.message;
continue;
}
if (isXmp) {
+84 -32
View File
@@ -27,6 +27,37 @@ public:
}
/*!
* \brief atariSTERast
* On Atari STE images, the RAST chunk can be found outside
* the FORM one so, I check if this is the case.
* \param chunks The chunk list.
*/
void atariSTERast(QIODevice *d, IFFChunk::ChunkList &chunks)
{
if (chunks.size() != 1 || d->isSequential()) {
return;
}
auto &&c = chunks.first();
if (c->chunkId() != FORMChunk::defaultChunkId()) {
return;
}
// The RAST chunk is not aligned so I have to temporary change the
// position and the alignment to read it successfully.
auto pos = d->pos();
auto align = c->alignBytes();
c->setAlignBytes(1);
d->seek(c->nextChunkPos());
c->setAlignBytes(align);
if (d->peek(4) == RAST_CHUNK) {
auto rast = QSharedPointer<IFFChunk>(new RASTChunk());
if (rast->readStructure(d) && rast->isValid())
chunks.first()->_chunks.append(rast);
}
d->seek(pos);
}
bool readStructure(QIODevice *d)
{
if (d == nullptr) {
@@ -40,6 +71,7 @@ public:
auto ok = false;
auto chunks = IFFChunk::fromDevice(d, &ok);
if (ok) {
atariSTERast(d, chunks);
m_chunks = chunks;
}
return ok;
@@ -101,7 +133,7 @@ bool IFFHandler::canRead() const
bool IFFHandler::canRead(QIODevice *device)
{
if (!device) {
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::canRead() called with no device";
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::canRead(): called with no device";
return false;
}
@@ -124,7 +156,7 @@ bool IFFHandler::canRead(QIODevice *device)
auto pos = device->pos();
auto chunks = IFFChunk::fromDevice(device, &ok);
if (!device->seek(pos)) {
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::canRead() unable to reset device position";
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::canRead(): unable to reset device position";
}
if (ok) {
auto forms = IFFHandlerPrivate::searchForms<FORMChunk>(chunks, true);
@@ -214,14 +246,18 @@ static void addMetadata(QImage &img, const IFOR_Chunk *form)
}
// resolution -> leave after set of EXIF chunk
const DPIChunk *dpi = nullptr;
auto dpis = IFFChunk::searchT<DPIChunk>(form);
auto xbmis = IFFChunk::searchT<XBMIChunk>(form);
if (!dpis.isEmpty()) {
auto &&dpi = dpis.first();
if (dpi->isValid()) {
img.setDotsPerMeterX(dpi->dotsPerMeterX());
img.setDotsPerMeterY(dpi->dotsPerMeterY());
resChanged = true;
}
dpi = dpis.first();
} else if (!xbmis.isEmpty()) {
dpi = xbmis.first(); // never seen
}
if (dpi && dpi->isValid()) {
img.setDotsPerMeterX(dpi->dotsPerMeterX());
img.setDotsPerMeterY(dpi->dotsPerMeterY());
resChanged = true;
}
// if no explicit resolution was found, apply the aspect ratio to the default one
@@ -248,26 +284,30 @@ static void addMetadata(QImage &img, const IFOR_Chunk *form)
static QImage convertIPAL(const QImage& img, const IPALChunk *ipal)
{
if (img.format() != QImage::Format_Indexed8) {
qDebug(LOG_IFFPLUGIN) << "convertIPAL(): the image is not indexed!";
qCDebug(LOG_IFFPLUGIN) << "convertIPAL(): the image is not indexed!";
return img;
}
auto tmp = img.convertToFormat(FORMAT_RGB_8BIT);
auto tmp = img.convertToFormat(ipal->hasAlpha() ? FORMAT_RGBA_8BIT : FORMAT_RGB_8BIT);
if (tmp.isNull()) {
qCritical(LOG_IFFPLUGIN) << "convertIPAL(): error while converting the image!";
return img;
}
auto mul = tmp.hasAlphaChannel() ? 4 : 3;
for (auto y = 0, h = img.height(); y < h; ++y) {
auto src = reinterpret_cast<const quint8 *>(img.constScanLine(y));
auto dst = tmp.scanLine(y);
auto lpal = ipal->palette(y, h);
auto lpal = ipal->palette(y);
for (auto x = 0, w = img.width(); x < w; ++x) {
if (src[x] < lpal.size()) {
auto x3 = x * 3;
dst[x3] = qRed(lpal.at(src[x]));
dst[x3 + 1] = qGreen(lpal.at(src[x]));
dst[x3 + 2] = qBlue(lpal.at(src[x]));
auto xmul = x * mul;
dst[xmul] = qRed(lpal.at(src[x]));
dst[xmul + 1] = qGreen(lpal.at(src[x]));
dst[xmul + 2] = qBlue(lpal.at(src[x]));
if (mul == 4) {
dst[xmul + 3] = qAlpha(lpal.at(src[x]));
}
}
}
}
@@ -287,7 +327,7 @@ bool IFFHandler::readStandardImage(QImage *image)
// show the first one (I don't have a sample with many images)
auto headers = IFFChunk::searchT<BMHDChunk>(form);
if (headers.isEmpty()) {
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readStandardImage() no supported image found";
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readStandardImage(): no supported image found";
return false;
}
@@ -295,7 +335,7 @@ bool IFFHandler::readStandardImage(QImage *image)
auto &&header = headers.first();
auto img = imageAlloc(header->size(), form->format());
if (img.isNull()) {
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readStandardImage() error while allocating the image";
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readStandardImage(): error while allocating the image";
return false;
}
@@ -324,7 +364,19 @@ bool IFFHandler::readStandardImage(QImage *image)
}
// reading image data
auto ipal = form->searchIPal();
std::unique_ptr<IPALChunk> ipal;
if (auto ptr = form->searchIPal()) {
ipal = std::unique_ptr<IPALChunk>(ptr->clone());
}
if (ipal) {
auto pal = img.colorTable();
if (pal.isEmpty() && cmap)
pal = cmap->palette();
if (!ipal->initialize(pal, img.height())) {
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readStandardImage(): unable to initialize palette changer";
return false;
}
}
auto bodies = IFFChunk::searchT<BODYChunk>(form);
if (bodies.isEmpty()) {
auto abits = IFFChunk::searchT<ABITChunk>(form);
@@ -336,23 +388,23 @@ bool IFFHandler::readStandardImage(QImage *image)
} else {
auto &&body = bodies.first();
if (!body->resetStrideRead(device())) {
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readStandardImage() error while reading image data";
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readStandardImage(): error while reading image data";
return false;
}
for (auto y = 0, h = img.height(); y < h; ++y) {
auto line = reinterpret_cast<char*>(img.scanLine(y));
auto ba = body->strideRead(device(), y, header, camg, cmap, ipal, form->formType());
auto ba = body->strideRead(device(), y, header, camg, cmap, ipal.get(), form->formType());
if (ba.isEmpty()) {
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readStandardImage() error while reading image scanline";
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readStandardImage(): error while reading image scanline";
return false;
}
memcpy(line, ba.constData(), std::min(img.bytesPerLine(), ba.size()));
}
}
// BEAM / CTBL conversion (if not already done)
// BEAM / CTBL, SHAM, RAST, PCHG conversion (if not already done)
if (ipal && img.format() == QImage::Format_Indexed8) {
img = convertIPAL(img, ipal);
img = convertIPAL(img, ipal.get());
}
// set metadata (including image resolution)
@@ -374,7 +426,7 @@ bool IFFHandler::readMayaImage(QImage *image)
// show the first one (I don't have a sample with many images)
auto headers = IFFChunk::searchT<TBHDChunk>(form);
if (headers.isEmpty()) {
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readMayaImage() no supported image found";
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readMayaImage(): no supported image found";
return false;
}
@@ -382,30 +434,30 @@ bool IFFHandler::readMayaImage(QImage *image)
auto &&header = headers.first();
auto img = imageAlloc(header->size(), form->format());
if (img.isNull()) {
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readMayaImage() error while allocating the image";
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readMayaImage(): error while allocating the image";
return false;
}
auto &&tiles = IFFChunk::searchT<RGBAChunk>(form);
if ((tiles.size() & 0xFFFF) != header->tiles()) { // Photoshop, on large images saves more than 65535 tiles
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readMayaImage() tile number mismatch: found" << tiles.size() << "while expected" << header->tiles();
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readMayaImage(): tile number mismatch: found" << tiles.size() << "while expected" << header->tiles();
return false;
}
for (auto &&tile : tiles) {
auto tp = tile->pos();
auto ts = tile->size();
if (tp.x() < 0 || tp.x() + ts.width() > img.width()) {
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readMayaImage() wrong tile position or size";
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readMayaImage(): wrong tile position or size";
return false;
}
if (tp.y() < 0 || tp.y() + ts.height() > img.height()) {
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readMayaImage() wrong tile position or size";
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readMayaImage(): wrong tile position or size";
return false;
}
// For future releases: it might be a good idea not to use a QPainter
auto ti = tile->tile(device(), header);
if (ti.isNull()) {
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readMayaImage() error while decoding the tile";
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readMayaImage(): error while decoding the tile";
return false;
}
QPainter painter(&img);
@@ -426,7 +478,7 @@ bool IFFHandler::readMayaImage(QImage *image)
bool IFFHandler::read(QImage *image)
{
if (!d->readStructure(device())) {
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::read() invalid IFF structure";
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::read(): invalid IFF structure";
return false;
}
@@ -438,7 +490,7 @@ bool IFFHandler::read(QImage *image)
return true;
}
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::read() no supported image found";
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::read(): no supported image found";
return false;
}
@@ -515,7 +567,7 @@ int IFFHandler::imageCount() const
count = QImageIOHandler::imageCount();
if (!d->readStructure(device())) {
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::imageCount() invalid IFF structure";
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::imageCount(): invalid IFF structure";
return count;
}
+22 -15
View File
@@ -13,10 +13,17 @@
#include <QIODevice>
#include <QImage>
#include <QImageReader>
#include <QLoggingCategory>
#include <QThread>
#include <openjpeg.h>
#ifdef QT_DEBUG
Q_LOGGING_CATEGORY(LOG_JP2PLUGIN, "kf.imageformats.plugins.jp2", QtDebugMsg)
#else
Q_LOGGING_CATEGORY(LOG_JP2PLUGIN, "kf.imageformats.plugins.jp2", QtWarningMsg)
#endif
/* *** JP2_MAX_IMAGE_WIDTH and JP2_MAX_IMAGE_HEIGHT ***
* The maximum size in pixel allowed by the plugin.
*/
@@ -48,19 +55,19 @@
static void error_callback(const char *msg, void *client_data)
{
Q_UNUSED(client_data)
qCritical() << msg;
qCCritical(LOG_JP2PLUGIN) << msg;
}
static void warning_callback(const char *msg, void *client_data)
{
Q_UNUSED(client_data)
qWarning() << msg;
qCWarning(LOG_JP2PLUGIN) << msg;
}
static void info_callback(const char *msg, void *client_data)
{
Q_UNUSED(client_data)
qInfo() << msg;
qCInfo(LOG_JP2PLUGIN) << msg;
}
static OPJ_SIZE_T jp2_read(void *p_buffer, OPJ_SIZE_T p_nb_bytes, void *p_user_data)
@@ -186,9 +193,9 @@ public:
void enableThreads(opj_codec_t *codec) const
{
if (!opj_has_thread_support()) {
qInfo() << "OpenJPEG doesn't support multi-threading!";
qCInfo(LOG_JP2PLUGIN) << "OpenJPEG doesn't support multi-threading!";
} else if (!opj_codec_set_threads(codec, std::max(1, QThread::idealThreadCount() / 2))) {
qWarning() << "Unable to enable multi-threading!";
qCWarning(LOG_JP2PLUGIN) << "Unable to enable multi-threading!";
}
}
@@ -230,12 +237,12 @@ public:
opj_set_default_decoder_parameters(&m_dparameters);
if (!opj_setup_decoder(m_jp2_codec, &m_dparameters)) {
qCritical() << "Failed to setup JP2 decoder!";
qCCritical(LOG_JP2PLUGIN) << "Failed to setup JP2 decoder!";
return false;
}
if (!opj_read_header(m_jp2_stream, m_jp2_codec, &m_jp2_image)) {
qCritical() << "Failed to read JP2 header!";
qCCritical(LOG_JP2PLUGIN) << "Failed to read JP2 header!";
return false;
}
@@ -308,7 +315,7 @@ public:
}
if (!opj_decode(m_jp2_codec, m_jp2_stream, m_jp2_image)) {
qCritical() << "Failed to decoding JP2 image!";
qCCritical(LOG_JP2PLUGIN) << "Failed to decoding JP2 image!";
return {};
}
@@ -335,12 +342,12 @@ public:
bool checkSizeLimits(qint32 width, qint32 height, qint32 nchannels) const
{
if (width > JP2_MAX_IMAGE_WIDTH || height > JP2_MAX_IMAGE_HEIGHT || width < 1 || height < 1) {
qCritical() << "Maximum image size is limited to" << JP2_MAX_IMAGE_WIDTH << "x" << JP2_MAX_IMAGE_HEIGHT << "pixels";
qCCritical(LOG_JP2PLUGIN) << "Maximum image size is limited to" << JP2_MAX_IMAGE_WIDTH << "x" << JP2_MAX_IMAGE_HEIGHT << "pixels";
return false;
}
if (qint64(width) * qint64(height) > JP2_MAX_IMAGE_PIXELS) {
qCritical() << "Maximum image size is limited to" << JP2_MAX_IMAGE_PIXELS << "pixels";
qCCritical(LOG_JP2PLUGIN) << "Maximum image size is limited to" << JP2_MAX_IMAGE_PIXELS << "pixels";
return false;
}
@@ -348,7 +355,7 @@ public:
auto maxBytes = qint64(QImageReader::allocationLimit()) * 1024 * 1024;
auto neededBytes = qint64(width) * height * nchannels * 4;
if (maxBytes > 0 && neededBytes > maxBytes) {
qCritical() << "Allocation limit set to" << (maxBytes / 1024 / 1024) << "MiB but" << (neededBytes / 1024 / 1024) << "MiB are needed!";
qCCritical(LOG_JP2PLUGIN) << "Allocation limit set to" << (maxBytes / 1024 / 1024) << "MiB but" << (neededBytes / 1024 / 1024) << "MiB are needed!";
return false;
}
@@ -584,7 +591,7 @@ public:
#endif
default:
if (image.depth() > 32) {
qWarning() << "The image is saved losing precision!";
qCWarning(LOG_JP2PLUGIN) << "The image is saved losing precision!";
}
convFormat = ncomp == 4 ? QImage::Format_RGBA8888 : QImage::Format_RGBX8888;
break;
@@ -677,13 +684,13 @@ public:
bool writeImage(QIODevice *device, const QImage &image)
{
if (!imageToJp2(image)) {
qCritical() << "Error while creating JP2 image!";
qCCritical(LOG_JP2PLUGIN) << "Error while creating JP2 image!";
return false;
}
std::unique_ptr<opj_codec_t, std::function<void(opj_codec_t *)>> codec(opj_create_compress(encoderFormat()), opj_destroy_codec);
if (codec == nullptr) {
qCritical() << "Error while creating encoder!";
qCCritical(LOG_JP2PLUGIN) << "Error while creating encoder!";
return false;
}
enableThreads(codec.get());
@@ -754,7 +761,7 @@ bool JP2Handler::canRead() const
bool JP2Handler::canRead(QIODevice *device)
{
if (!device) {
qWarning("JP2Handler::canRead() called with no device");
qCWarning(LOG_JP2PLUGIN) << "JP2Handler::canRead() called with no device";
return false;
}
+106 -98
View File
@@ -6,6 +6,7 @@
SPDX-License-Identifier: BSD-2-Clause
*/
#include <QLoggingCategory>
#include <QThread>
#include <QtGlobal>
@@ -19,6 +20,13 @@
#include <string.h>
#ifdef QT_DEBUG
Q_LOGGING_CATEGORY(LOG_JXLPLUGIN, "kf.imageformats.plugins.jxl", QtDebugMsg)
#else
Q_LOGGING_CATEGORY(LOG_JXLPLUGIN, "kf.imageformats.plugins.jxl", QtWarningMsg)
#endif
// Avoid rotation on buggy Qts (see also https://bugreports.qt.io/browse/QTBUG-126575)
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 3)
#ifndef JXL_QT_AUTOTRANSFORM
@@ -163,7 +171,7 @@ bool QJpegXLHandler::ensureDecoder()
m_decoder = JxlDecoderCreate(nullptr);
if (!m_decoder) {
qWarning("ERROR: JxlDecoderCreate failed");
qCWarning(LOG_JXLPLUGIN, "ERROR: JxlDecoderCreate failed");
m_parseState = ParseJpegXLError;
return false;
}
@@ -182,14 +190,14 @@ bool QJpegXLHandler::ensureDecoder()
m_runner = JxlThreadParallelRunnerCreate(nullptr, num_worker_threads);
if (JxlDecoderSetParallelRunner(m_decoder, JxlThreadParallelRunner, m_runner) != JXL_DEC_SUCCESS) {
qWarning("ERROR: JxlDecoderSetParallelRunner failed");
qCWarning(LOG_JXLPLUGIN, "ERROR: JxlDecoderSetParallelRunner failed");
m_parseState = ParseJpegXLError;
return false;
}
}
if (JxlDecoderSetInput(m_decoder, reinterpret_cast<const uint8_t *>(m_rawData.constData()), m_rawData.size()) != JXL_DEC_SUCCESS) {
qWarning("ERROR: JxlDecoderSetInput failed");
qCWarning(LOG_JXLPLUGIN, "ERROR: JxlDecoderSetInput failed");
m_parseState = ParseJpegXLError;
return false;
}
@@ -198,38 +206,38 @@ bool QJpegXLHandler::ensureDecoder()
JxlDecoderStatus status = JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING | JXL_DEC_FRAME);
if (status == JXL_DEC_ERROR) {
qWarning("ERROR: JxlDecoderSubscribeEvents failed");
qCWarning(LOG_JXLPLUGIN, "ERROR: JxlDecoderSubscribeEvents failed");
m_parseState = ParseJpegXLError;
return false;
}
status = JxlDecoderProcessInput(m_decoder);
if (status == JXL_DEC_ERROR) {
qWarning("ERROR: JXL decoding failed");
qCWarning(LOG_JXLPLUGIN, "ERROR: JXL decoding failed");
m_parseState = ParseJpegXLError;
return false;
}
if (status == JXL_DEC_NEED_MORE_INPUT) {
qWarning("ERROR: JXL data incomplete");
qCWarning(LOG_JXLPLUGIN, "ERROR: JXL data incomplete");
m_parseState = ParseJpegXLError;
return false;
}
status = JxlDecoderGetBasicInfo(m_decoder, &m_basicinfo);
if (status != JXL_DEC_SUCCESS) {
qWarning("ERROR: JXL basic info not available");
qCWarning(LOG_JXLPLUGIN, "ERROR: JXL basic info not available");
m_parseState = ParseJpegXLError;
return false;
}
if (m_basicinfo.xsize == 0 || m_basicinfo.ysize == 0) {
qWarning("ERROR: JXL image has zero dimensions");
qCWarning(LOG_JXLPLUGIN, "ERROR: JXL image has zero dimensions");
m_parseState = ParseJpegXLError;
return false;
}
if (m_basicinfo.xsize > MAX_IMAGE_WIDTH || m_basicinfo.ysize > MAX_IMAGE_HEIGHT) {
qWarning("JXL image (%dx%d) is too large", m_basicinfo.xsize, m_basicinfo.ysize);
qCWarning(LOG_JXLPLUGIN, "JXL image (%dx%d) is too large", m_basicinfo.xsize, m_basicinfo.ysize);
m_parseState = ParseJpegXLError;
return false;
}
@@ -246,7 +254,7 @@ bool QJpegXLHandler::countALLFrames()
JxlDecoderStatus status = JxlDecoderProcessInput(m_decoder);
if (status != JXL_DEC_COLOR_ENCODING) {
qWarning("Unexpected event %d instead of JXL_DEC_COLOR_ENCODING", status);
qCWarning(LOG_JXLPLUGIN, "Unexpected event %d instead of JXL_DEC_COLOR_ENCODING", status);
m_parseState = ParseJpegXLError;
return false;
}
@@ -258,10 +266,10 @@ bool QJpegXLHandler::countALLFrames()
if (jxlcms) {
status = JxlDecoderSetCms(m_decoder, *jxlcms);
if (status != JXL_DEC_SUCCESS) {
qWarning("JxlDecoderSetCms ERROR");
qCWarning(LOG_JXLPLUGIN, "JxlDecoderSetCms ERROR");
}
} else {
qWarning("No JPEG XL CMS Interface");
qCWarning(LOG_JXLPLUGIN, "No JPEG XL CMS Interface");
}
JxlColorEncodingSetToSRGB(&color_encoding, is_gray ? JXL_TRUE : JXL_FALSE);
@@ -338,16 +346,16 @@ bool QJpegXLHandler::countALLFrames()
m_colorspace = QColorSpace::fromIccProfile(icc_data);
if (!m_colorspace.isValid()) {
qWarning("JXL image has Qt-unsupported or invalid ICC profile!");
qCWarning(LOG_JXLPLUGIN, "JXL image has Qt-unsupported or invalid ICC profile!");
}
} else {
qWarning("Failed to obtain data from JPEG XL decoder");
qCWarning(LOG_JXLPLUGIN, "Failed to obtain data from JPEG XL decoder");
}
} else {
qWarning("Empty ICC data");
qCWarning(LOG_JXLPLUGIN, "Empty ICC data");
}
} else {
qWarning("no ICC, other color profile");
qCWarning(LOG_JXLPLUGIN, "no ICC, other color profile");
}
}
@@ -359,13 +367,13 @@ bool QJpegXLHandler::countALLFrames()
if (status != JXL_DEC_FRAME) {
switch (status) {
case JXL_DEC_ERROR:
qWarning("ERROR: JXL decoding failed");
qCWarning(LOG_JXLPLUGIN, "ERROR: JXL decoding failed");
break;
case JXL_DEC_NEED_MORE_INPUT:
qWarning("ERROR: JXL data incomplete");
qCWarning(LOG_JXLPLUGIN, "ERROR: JXL data incomplete");
break;
default:
qWarning("Unexpected event %d instead of JXL_DEC_FRAME", status);
qCWarning(LOG_JXLPLUGIN, "Unexpected event %d instead of JXL_DEC_FRAME", status);
break;
}
m_parseState = ParseJpegXLError;
@@ -373,7 +381,7 @@ bool QJpegXLHandler::countALLFrames()
}
if (JxlDecoderGetFrameHeader(m_decoder, &frame_header) != JXL_DEC_SUCCESS) {
qWarning("ERROR: JxlDecoderGetFrameHeader failed");
qCWarning(LOG_JXLPLUGIN, "ERROR: JxlDecoderGetFrameHeader failed");
m_parseState = ParseJpegXLError;
return false;
}
@@ -392,13 +400,13 @@ bool QJpegXLHandler::countALLFrames()
}
if (m_framedelays.isEmpty()) {
qWarning("no frames loaded by the JXL plug-in");
qCWarning(LOG_JXLPLUGIN, "no frames loaded by the JXL plug-in");
m_parseState = ParseJpegXLError;
return false;
}
if (m_framedelays.count() == 1) {
qWarning("JXL file was marked as animation but it has only one frame.");
qCWarning(LOG_JXLPLUGIN, "JXL file was marked as animation but it has only one frame.");
m_basicinfo.have_animation = JXL_FALSE;
}
} else { // static picture
@@ -414,7 +422,7 @@ bool QJpegXLHandler::countALLFrames()
for (uint32_t index = 0; index < m_basicinfo.num_extra_channels; index++) {
status = JxlDecoderGetExtraChannelInfo(m_decoder, index, &channel_info);
if (status != JXL_DEC_SUCCESS) {
qWarning("JxlDecoderGetExtraChannelInfo for channel %d returned %d", index, status);
qCWarning(LOG_JXLPLUGIN, "JxlDecoderGetExtraChannelInfo for channel %d returned %d", index, status);
m_parseState = ParseJpegXLError;
return false;
}
@@ -430,7 +438,7 @@ bool QJpegXLHandler::countALLFrames()
for (uint32_t alpha_index = index + 1; alpha_index < m_basicinfo.num_extra_channels; alpha_index++) {
status = JxlDecoderGetExtraChannelInfo(m_decoder, alpha_index, &channel_info);
if (status != JXL_DEC_SUCCESS) {
qWarning("JxlDecoderGetExtraChannelInfo for channel %d returned %d", alpha_index, status);
qCWarning(LOG_JXLPLUGIN, "JxlDecoderGetExtraChannelInfo for channel %d returned %d", alpha_index, status);
m_parseState = ParseJpegXLError;
return false;
}
@@ -443,14 +451,14 @@ bool QJpegXLHandler::countALLFrames()
}
if (!alpha_found) {
qWarning("JXL BasicInfo indicate Alpha channel but it was not found");
qCWarning(LOG_JXLPLUGIN, "JXL BasicInfo indicate Alpha channel but it was not found");
m_parseState = ParseJpegXLError;
return false;
}
}
}
} else {
qWarning("JXL has BLACK channel but colorspace is not CMYK!");
qCWarning(LOG_JXLPLUGIN, "JXL has BLACK channel but colorspace is not CMYK!");
}
break;
} else if ((channel_info.type == JXL_CHANNEL_ALPHA) && !alpha_found) {
@@ -460,7 +468,7 @@ bool QJpegXLHandler::countALLFrames()
}
if (!m_isCMYK && (m_colorspace.colorModel() == QColorSpace::ColorModel::Cmyk)) {
qWarning("JXL has CMYK colorspace but BLACK channel was not found!");
qCWarning(LOG_JXLPLUGIN, "JXL has CMYK colorspace but BLACK channel was not found!");
}
}
#endif
@@ -484,7 +492,7 @@ bool QJpegXLHandler::decode_one_frame()
{
JxlDecoderStatus status = JxlDecoderProcessInput(m_decoder);
if (status != JXL_DEC_NEED_IMAGE_OUT_BUFFER) {
qWarning("Unexpected event %d instead of JXL_DEC_NEED_IMAGE_OUT_BUFFER", status);
qCWarning(LOG_JXLPLUGIN, "Unexpected event %d instead of JXL_DEC_NEED_IMAGE_OUT_BUFFER", status);
m_parseState = ParseJpegXLError;
return false;
}
@@ -512,7 +520,7 @@ bool QJpegXLHandler::decode_one_frame()
if (m_basicinfo.alpha_bits > 0) { // CMYK + alpha
QImage tmp_cmyk_image = imageAlloc(m_basicinfo.xsize, m_basicinfo.ysize, QImage::Format_CMYK8888);
if (tmp_cmyk_image.isNull()) {
qWarning("Memory cannot be allocated");
qCWarning(LOG_JXLPLUGIN, "Memory cannot be allocated");
m_parseState = ParseJpegXLError;
return false;
}
@@ -521,7 +529,7 @@ bool QJpegXLHandler::decode_one_frame()
uchar *pixels_alpha = reinterpret_cast<uchar *>(malloc(extra_buffer_size));
if (!pixels_alpha) {
qWarning("Memory cannot be allocated for ALPHA channel");
qCWarning(LOG_JXLPLUGIN, "Memory cannot be allocated for ALPHA channel");
m_parseState = ParseJpegXLError;
return false;
}
@@ -530,7 +538,7 @@ bool QJpegXLHandler::decode_one_frame()
if (!pixels_cmy) {
free(pixels_alpha);
pixels_alpha = nullptr;
qWarning("Memory cannot be allocated for CMY buffer");
qCWarning(LOG_JXLPLUGIN, "Memory cannot be allocated for CMY buffer");
m_parseState = ParseJpegXLError;
return false;
}
@@ -541,7 +549,7 @@ bool QJpegXLHandler::decode_one_frame()
pixels_cmy = nullptr;
free(pixels_alpha);
pixels_alpha = nullptr;
qWarning("Memory cannot be allocated for BLACK buffer");
qCWarning(LOG_JXLPLUGIN, "Memory cannot be allocated for BLACK buffer");
m_parseState = ParseJpegXLError;
return false;
}
@@ -553,7 +561,7 @@ bool QJpegXLHandler::decode_one_frame()
pixels_cmy = nullptr;
free(pixels_alpha);
pixels_alpha = nullptr;
qWarning("ERROR: JxlDecoderSetImageOutBuffer failed");
qCWarning(LOG_JXLPLUGIN, "ERROR: JxlDecoderSetImageOutBuffer failed");
m_parseState = ParseJpegXLError;
return false;
}
@@ -565,7 +573,7 @@ bool QJpegXLHandler::decode_one_frame()
pixels_cmy = nullptr;
free(pixels_alpha);
pixels_alpha = nullptr;
qWarning("ERROR: JxlDecoderSetExtraChannelBuffer failed");
qCWarning(LOG_JXLPLUGIN, "ERROR: JxlDecoderSetExtraChannelBuffer failed");
m_parseState = ParseJpegXLError;
return false;
}
@@ -577,7 +585,7 @@ bool QJpegXLHandler::decode_one_frame()
pixels_cmy = nullptr;
free(pixels_alpha);
pixels_alpha = nullptr;
qWarning("ERROR: JxlDecoderSetExtraChannelBuffer failed");
qCWarning(LOG_JXLPLUGIN, "ERROR: JxlDecoderSetExtraChannelBuffer failed");
m_parseState = ParseJpegXLError;
return false;
}
@@ -590,7 +598,7 @@ bool QJpegXLHandler::decode_one_frame()
pixels_cmy = nullptr;
free(pixels_alpha);
pixels_alpha = nullptr;
qWarning("Unexpected event %d instead of JXL_DEC_FULL_IMAGE", status);
qCWarning(LOG_JXLPLUGIN, "Unexpected event %d instead of JXL_DEC_FULL_IMAGE", status);
m_parseState = ParseJpegXLError;
return false;
}
@@ -624,7 +632,7 @@ bool QJpegXLHandler::decode_one_frame()
if (m_current_image.isNull()) {
free(pixels_alpha);
pixels_alpha = nullptr;
qWarning("ERROR: convertedToColorSpace returned empty image");
qCWarning(LOG_JXLPLUGIN, "ERROR: convertedToColorSpace returned empty image");
m_parseState = ParseJpegXLError;
return false;
}
@@ -652,7 +660,7 @@ bool QJpegXLHandler::decode_one_frame()
} else { // CMYK (no alpha)
m_current_image = imageAlloc(m_basicinfo.xsize, m_basicinfo.ysize, QImage::Format_CMYK8888);
if (m_current_image.isNull()) {
qWarning("Memory cannot be allocated");
qCWarning(LOG_JXLPLUGIN, "Memory cannot be allocated");
m_parseState = ParseJpegXLError;
return false;
}
@@ -661,7 +669,7 @@ bool QJpegXLHandler::decode_one_frame()
pixels_cmy = reinterpret_cast<uchar *>(malloc(cmy_buffer_size));
if (!pixels_cmy) {
qWarning("Memory cannot be allocated for CMY buffer");
qCWarning(LOG_JXLPLUGIN, "Memory cannot be allocated for CMY buffer");
m_parseState = ParseJpegXLError;
return false;
}
@@ -670,7 +678,7 @@ bool QJpegXLHandler::decode_one_frame()
if (!pixels_black) {
free(pixels_cmy);
pixels_cmy = nullptr;
qWarning("Memory cannot be allocated for BLACK buffer");
qCWarning(LOG_JXLPLUGIN, "Memory cannot be allocated for BLACK buffer");
m_parseState = ParseJpegXLError;
return false;
}
@@ -680,7 +688,7 @@ bool QJpegXLHandler::decode_one_frame()
pixels_black = nullptr;
free(pixels_cmy);
pixels_cmy = nullptr;
qWarning("ERROR: JxlDecoderSetImageOutBuffer failed");
qCWarning(LOG_JXLPLUGIN, "ERROR: JxlDecoderSetImageOutBuffer failed");
m_parseState = ParseJpegXLError;
return false;
}
@@ -690,7 +698,7 @@ bool QJpegXLHandler::decode_one_frame()
pixels_black = nullptr;
free(pixels_cmy);
pixels_cmy = nullptr;
qWarning("ERROR: JxlDecoderSetExtraChannelBuffer failed");
qCWarning(LOG_JXLPLUGIN, "ERROR: JxlDecoderSetExtraChannelBuffer failed");
m_parseState = ParseJpegXLError;
return false;
}
@@ -701,7 +709,7 @@ bool QJpegXLHandler::decode_one_frame()
pixels_black = nullptr;
free(pixels_cmy);
pixels_cmy = nullptr;
qWarning("Unexpected event %d instead of JXL_DEC_FULL_IMAGE", status);
qCWarning(LOG_JXLPLUGIN, "Unexpected event %d instead of JXL_DEC_FULL_IMAGE", status);
m_parseState = ParseJpegXLError;
return false;
}
@@ -739,7 +747,7 @@ bool QJpegXLHandler::decode_one_frame()
} else { // RGB or GRAY
m_current_image = imageAlloc(m_basicinfo.xsize, m_basicinfo.ysize, m_input_image_format);
if (m_current_image.isNull()) {
qWarning("Memory cannot be allocated");
qCWarning(LOG_JXLPLUGIN, "Memory cannot be allocated");
m_parseState = ParseJpegXLError;
return false;
}
@@ -761,21 +769,21 @@ bool QJpegXLHandler::decode_one_frame()
rgb_buffer_size += 2 * size_t(m_input_pixel_format.num_channels) * size_t(m_current_image.width());
break;
default:
qWarning("ERROR: unsupported data type");
qCWarning(LOG_JXLPLUGIN, "ERROR: unsupported data type");
m_parseState = ParseJpegXLError;
return false;
break;
}
if (JxlDecoderSetImageOutBuffer(m_decoder, &m_input_pixel_format, m_current_image.bits(), rgb_buffer_size) != JXL_DEC_SUCCESS) {
qWarning("ERROR: JxlDecoderSetImageOutBuffer failed");
qCWarning(LOG_JXLPLUGIN, "ERROR: JxlDecoderSetImageOutBuffer failed");
m_parseState = ParseJpegXLError;
return false;
}
status = JxlDecoderProcessInput(m_decoder);
if (status != JXL_DEC_FULL_IMAGE) {
qWarning("Unexpected event %d instead of JXL_DEC_FULL_IMAGE", status);
qCWarning(LOG_JXLPLUGIN, "Unexpected event %d instead of JXL_DEC_FULL_IMAGE", status);
m_parseState = ParseJpegXLError;
return false;
}
@@ -841,29 +849,29 @@ bool QJpegXLHandler::read(QImage *image)
bool QJpegXLHandler::write(const QImage &image)
{
if (image.format() == QImage::Format_Invalid) {
qWarning("No image data to save");
qCWarning(LOG_JXLPLUGIN, "No image data to save");
return false;
}
if ((image.width() == 0) || (image.height() == 0)) {
qWarning("Image has zero dimension!");
qCWarning(LOG_JXLPLUGIN, "Image has zero dimension!");
return false;
}
if ((image.width() > MAX_IMAGE_WIDTH) || (image.height() > MAX_IMAGE_HEIGHT)) {
qWarning("Image (%dx%d) is too large to save!", image.width(), image.height());
qCWarning(LOG_JXLPLUGIN, "Image (%dx%d) is too large to save!", image.width(), image.height());
return false;
}
size_t pixel_count = size_t(image.width()) * image.height();
if (MAX_IMAGE_PIXELS && pixel_count > MAX_IMAGE_PIXELS) {
qWarning("Image (%dx%d) will not be saved because it has more than %d megapixels!", image.width(), image.height(), MAX_IMAGE_PIXELS / 1024 / 1024);
qCWarning(LOG_JXLPLUGIN, "Image (%dx%d) will not be saved because it has more than %d megapixels!", image.width(), image.height(), MAX_IMAGE_PIXELS / 1024 / 1024);
return false;
}
JxlEncoder *encoder = JxlEncoderCreate(nullptr);
if (!encoder) {
qWarning("Failed to create Jxl encoder");
qCWarning(LOG_JXLPLUGIN, "Failed to create Jxl encoder");
return false;
}
@@ -873,7 +881,7 @@ bool QJpegXLHandler::write(const QImage &image)
if (num_worker_threads > 1) {
runner = JxlThreadParallelRunnerCreate(nullptr, num_worker_threads);
if (JxlEncoderSetParallelRunner(encoder, JxlThreadParallelRunner, runner) != JXL_ENC_SUCCESS) {
qWarning("JxlEncoderSetParallelRunner failed");
qCWarning(LOG_JXLPLUGIN, "JxlEncoderSetParallelRunner failed");
JxlThreadParallelRunnerDestroy(runner);
JxlEncoderDestroy(encoder);
return false;
@@ -953,7 +961,7 @@ bool QJpegXLHandler::write(const QImage &image)
const QByteArray cmyk_profile = image.colorSpace().iccProfile();
if (cmyk_profile.isEmpty()) {
qWarning("ERROR saving CMYK JXL: empty ICC profile");
qCWarning(LOG_JXLPLUGIN, "ERROR saving CMYK JXL: empty ICC profile");
if (runner) {
JxlThreadParallelRunnerDestroy(runner);
}
@@ -963,7 +971,7 @@ bool QJpegXLHandler::write(const QImage &image)
status = JxlEncoderSetBasicInfo(encoder, &output_info);
if (status != JXL_ENC_SUCCESS) {
qWarning("JxlEncoderSetBasicInfo for CMYK image failed!");
qCWarning(LOG_JXLPLUGIN, "JxlEncoderSetBasicInfo for CMYK image failed!");
if (runner) {
JxlThreadParallelRunnerDestroy(runner);
}
@@ -973,7 +981,7 @@ bool QJpegXLHandler::write(const QImage &image)
status = JxlEncoderSetExtraChannelInfo(encoder, 0, &extra_black_channel);
if (status != JXL_ENC_SUCCESS) {
qWarning("JxlEncoderSetExtraChannelInfo for CMYK image failed!");
qCWarning(LOG_JXLPLUGIN, "JxlEncoderSetExtraChannelInfo for CMYK image failed!");
if (runner) {
JxlThreadParallelRunnerDestroy(runner);
}
@@ -983,7 +991,7 @@ bool QJpegXLHandler::write(const QImage &image)
status = JxlEncoderSetICCProfile(encoder, reinterpret_cast<const uint8_t *>(cmyk_profile.constData()), cmyk_profile.size());
if (status != JXL_ENC_SUCCESS) {
qWarning("JxlEncoderSetICCProfile for CMYK image failed!");
qCWarning(LOG_JXLPLUGIN, "JxlEncoderSetICCProfile for CMYK image failed!");
if (runner) {
JxlThreadParallelRunnerDestroy(runner);
}
@@ -996,7 +1004,7 @@ bool QJpegXLHandler::write(const QImage &image)
const char *box_type = "Exif";
status = JxlEncoderAddBox(encoder, box_type, reinterpret_cast<const uint8_t *>(exif_data.constData()), exif_data.size(), JXL_FALSE);
if (status != JXL_ENC_SUCCESS) {
qWarning("JxlEncoderAddBox failed!");
qCWarning(LOG_JXLPLUGIN, "JxlEncoderAddBox failed!");
if (runner) {
JxlThreadParallelRunnerDestroy(runner);
}
@@ -1009,7 +1017,7 @@ bool QJpegXLHandler::write(const QImage &image)
const char *box_type = "xml ";
status = JxlEncoderAddBox(encoder, box_type, reinterpret_cast<const uint8_t *>(xmp_data.constData()), xmp_data.size(), JXL_FALSE);
if (status != JXL_ENC_SUCCESS) {
qWarning("JxlEncoderAddBox failed!");
qCWarning(LOG_JXLPLUGIN, "JxlEncoderAddBox failed!");
if (runner) {
JxlThreadParallelRunnerDestroy(runner);
}
@@ -1027,7 +1035,7 @@ bool QJpegXLHandler::write(const QImage &image)
pixels_cmy = reinterpret_cast<uchar *>(malloc(cmy_buffer_size));
if (!pixels_cmy) {
qWarning("Memory cannot be allocated for CMY buffer");
qCWarning(LOG_JXLPLUGIN, "Memory cannot be allocated for CMY buffer");
if (runner) {
JxlThreadParallelRunnerDestroy(runner);
}
@@ -1037,7 +1045,7 @@ bool QJpegXLHandler::write(const QImage &image)
pixels_black = reinterpret_cast<uchar *>(malloc(extra_buffer_size));
if (!pixels_black) {
qWarning("Memory cannot be allocated for BLACK buffer");
qCWarning(LOG_JXLPLUGIN, "Memory cannot be allocated for BLACK buffer");
free(pixels_cmy);
pixels_cmy = nullptr;
@@ -1074,7 +1082,7 @@ bool QJpegXLHandler::write(const QImage &image)
status = JxlEncoderAddImageFrame(frame_settings_lossless, &pixel_format, pixels_cmy, cmy_buffer_size);
if (status == JXL_ENC_ERROR) {
qWarning("JxlEncoderAddImageFrame failed!");
qCWarning(LOG_JXLPLUGIN, "JxlEncoderAddImageFrame failed!");
free(pixels_black);
pixels_black = nullptr;
free(pixels_cmy);
@@ -1094,7 +1102,7 @@ bool QJpegXLHandler::write(const QImage &image)
pixels_cmy = nullptr;
if (status == JXL_ENC_ERROR) {
qWarning("JxlEncoderSetExtraChannelBuffer failed!");
qCWarning(LOG_JXLPLUGIN, "JxlEncoderSetExtraChannelBuffer failed!");
if (runner) {
JxlThreadParallelRunnerDestroy(runner);
}
@@ -1263,7 +1271,7 @@ bool QJpegXLHandler::write(const QImage &image)
if (gray_profile.isValid()) {
tmpimage = image.convertedToColorSpace(gray_profile, tmpformat);
} else {
qWarning("JXL plugin created invalid grayscale QColorSpace!");
qCWarning(LOG_JXLPLUGIN, "JXL plugin created invalid grayscale QColorSpace!");
tmpimage = image.convertToFormat(tmpformat);
}
} else if (!is_gray && image.colorSpace().colorModel() != QColorSpace::ColorModel::Rgb) {
@@ -1287,7 +1295,7 @@ bool QJpegXLHandler::write(const QImage &image)
if (rgb_profile.isValid()) {
tmpimage = image.convertedToColorSpace(rgb_profile, tmpformat);
} else {
qWarning("JXL plugin created invalid RGB QColorSpace!");
qCWarning(LOG_JXLPLUGIN, "JXL plugin created invalid RGB QColorSpace!");
tmpimage = image.convertToFormat(tmpformat);
}
} else { // ColorSpace matches the format
@@ -1304,7 +1312,7 @@ bool QJpegXLHandler::write(const QImage &image)
output_info.ysize = tmpimage.height();
if (output_info.xsize == 0 || output_info.ysize == 0 || tmpimage.isNull()) {
qWarning("Unable to allocate memory for output image");
qCWarning(LOG_JXLPLUGIN, "Unable to allocate memory for output image");
if (runner) {
JxlThreadParallelRunnerDestroy(runner);
}
@@ -1420,7 +1428,7 @@ bool QJpegXLHandler::write(const QImage &image)
status = JxlEncoderSetBasicInfo(encoder, &output_info);
if (status != JXL_ENC_SUCCESS) {
qWarning("JxlEncoderSetBasicInfo failed!");
qCWarning(LOG_JXLPLUGIN, "JxlEncoderSetBasicInfo failed!");
if (runner) {
JxlThreadParallelRunnerDestroy(runner);
}
@@ -1431,7 +1439,7 @@ bool QJpegXLHandler::write(const QImage &image)
if (iccprofile.size() > 0) {
status = JxlEncoderSetICCProfile(encoder, reinterpret_cast<const uint8_t *>(iccprofile.constData()), iccprofile.size());
if (status != JXL_ENC_SUCCESS) {
qWarning("JxlEncoderSetICCProfile failed!");
qCWarning(LOG_JXLPLUGIN, "JxlEncoderSetICCProfile failed!");
if (runner) {
JxlThreadParallelRunnerDestroy(runner);
}
@@ -1441,7 +1449,7 @@ bool QJpegXLHandler::write(const QImage &image)
} else {
status = JxlEncoderSetColorEncoding(encoder, &color_profile);
if (status != JXL_ENC_SUCCESS) {
qWarning("JxlEncoderSetColorEncoding failed!");
qCWarning(LOG_JXLPLUGIN, "JxlEncoderSetColorEncoding failed!");
if (runner) {
JxlThreadParallelRunnerDestroy(runner);
}
@@ -1455,7 +1463,7 @@ bool QJpegXLHandler::write(const QImage &image)
const char *box_type = "Exif";
status = JxlEncoderAddBox(encoder, box_type, reinterpret_cast<const uint8_t *>(exif_data.constData()), exif_data.size(), JXL_FALSE);
if (status != JXL_ENC_SUCCESS) {
qWarning("JxlEncoderAddBox failed!");
qCWarning(LOG_JXLPLUGIN, "JxlEncoderAddBox failed!");
if (runner) {
JxlThreadParallelRunnerDestroy(runner);
}
@@ -1468,7 +1476,7 @@ bool QJpegXLHandler::write(const QImage &image)
const char *box_type = "xml ";
status = JxlEncoderAddBox(encoder, box_type, reinterpret_cast<const uint8_t *>(xmp_data.constData()), xmp_data.size(), JXL_FALSE);
if (status != JXL_ENC_SUCCESS) {
qWarning("JxlEncoderAddBox failed!");
qCWarning(LOG_JXLPLUGIN, "JxlEncoderAddBox failed!");
if (runner) {
JxlThreadParallelRunnerDestroy(runner);
}
@@ -1494,7 +1502,7 @@ bool QJpegXLHandler::write(const QImage &image)
float *packed_pixels32 = reinterpret_cast<float *>(malloc(buffer_size));
if (!packed_pixels32) {
qWarning("ERROR: JXL plug-in failed to allocate memory");
qCWarning(LOG_JXLPLUGIN, "ERROR: JXL plug-in failed to allocate memory");
return false;
}
@@ -1522,7 +1530,7 @@ bool QJpegXLHandler::write(const QImage &image)
quint16 *packed_pixels16 = reinterpret_cast<quint16 *>(malloc(buffer_size));
if (!packed_pixels16) {
qWarning("ERROR: JXL plug-in failed to allocate memory");
qCWarning(LOG_JXLPLUGIN, "ERROR: JXL plug-in failed to allocate memory");
return false;
}
@@ -1560,7 +1568,7 @@ bool QJpegXLHandler::write(const QImage &image)
buffer_size += 2 * size_t(pixel_format.num_channels) * size_t(tmpimage.width());
break;
default:
qWarning("ERROR: unsupported data type");
qCWarning(LOG_JXLPLUGIN, "ERROR: unsupported data type");
return false;
break;
}
@@ -1569,7 +1577,7 @@ bool QJpegXLHandler::write(const QImage &image)
}
if (status == JXL_ENC_ERROR) {
qWarning("JxlEncoderAddImageFrame failed!");
qCWarning(LOG_JXLPLUGIN, "JxlEncoderAddImageFrame failed!");
if (runner) {
JxlThreadParallelRunnerDestroy(runner);
}
@@ -1594,7 +1602,7 @@ bool QJpegXLHandler::write(const QImage &image)
offset = next_out - compressed.data();
compressed.resize(compressed.size() * 2);
} else if (status == JXL_ENC_ERROR) {
qWarning("JxlEncoderProcessOutput failed!");
qCWarning(LOG_JXLPLUGIN, "JxlEncoderProcessOutput failed!");
if (runner) {
JxlThreadParallelRunnerDestroy(runner);
}
@@ -1616,7 +1624,7 @@ bool QJpegXLHandler::write(const QImage &image)
if (write_status > 0) {
return true;
} else if (write_status == -1) {
qWarning("Write error: %s\n", qUtf8Printable(device()->errorString()));
qCWarning(LOG_JXLPLUGIN, "Write error: %s\n", qUtf8Printable(device()->errorString()));
}
}
@@ -1838,14 +1846,14 @@ bool QJpegXLHandler::rewind()
JxlDecoderRewind(m_decoder);
if (m_runner) {
if (JxlDecoderSetParallelRunner(m_decoder, JxlThreadParallelRunner, m_runner) != JXL_DEC_SUCCESS) {
qWarning("ERROR: JxlDecoderSetParallelRunner failed");
qCWarning(LOG_JXLPLUGIN, "ERROR: JxlDecoderSetParallelRunner failed");
m_parseState = ParseJpegXLError;
return false;
}
}
if (JxlDecoderSetInput(m_decoder, reinterpret_cast<const uint8_t *>(m_rawData.constData()), m_rawData.size()) != JXL_DEC_SUCCESS) {
qWarning("ERROR: JxlDecoderSetInput failed");
qCWarning(LOG_JXLPLUGIN, "ERROR: JxlDecoderSetInput failed");
m_parseState = ParseJpegXLError;
return false;
}
@@ -1854,14 +1862,14 @@ bool QJpegXLHandler::rewind()
if (m_basicinfo.uses_original_profile == JXL_FALSE && m_basicinfo.have_animation == JXL_FALSE) {
if (JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_COLOR_ENCODING | JXL_DEC_FULL_IMAGE) != JXL_DEC_SUCCESS) {
qWarning("ERROR: JxlDecoderSubscribeEvents failed");
qCWarning(LOG_JXLPLUGIN, "ERROR: JxlDecoderSubscribeEvents failed");
m_parseState = ParseJpegXLError;
return false;
}
JxlDecoderStatus status = JxlDecoderProcessInput(m_decoder);
if (status != JXL_DEC_COLOR_ENCODING) {
qWarning("Unexpected event %d instead of JXL_DEC_COLOR_ENCODING", status);
qCWarning(LOG_JXLPLUGIN, "Unexpected event %d instead of JXL_DEC_COLOR_ENCODING", status);
m_parseState = ParseJpegXLError;
return false;
}
@@ -1870,10 +1878,10 @@ bool QJpegXLHandler::rewind()
if (jxlcms) {
status = JxlDecoderSetCms(m_decoder, *jxlcms);
if (status != JXL_DEC_SUCCESS) {
qWarning("JxlDecoderSetCms ERROR");
qCWarning(LOG_JXLPLUGIN, "JxlDecoderSetCms ERROR");
}
} else {
qWarning("No JPEG XL CMS Interface");
qCWarning(LOG_JXLPLUGIN, "No JPEG XL CMS Interface");
}
bool is_gray = m_basicinfo.num_color_channels == 1 && m_basicinfo.alpha_bits == 0;
@@ -1882,7 +1890,7 @@ bool QJpegXLHandler::rewind()
JxlDecoderSetPreferredColorProfile(m_decoder, &color_encoding);
} else {
if (JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_FULL_IMAGE) != JXL_DEC_SUCCESS) {
qWarning("ERROR: JxlDecoderSubscribeEvents failed");
qCWarning(LOG_JXLPLUGIN, "ERROR: JxlDecoderSubscribeEvents failed");
m_parseState = ParseJpegXLError;
return false;
}
@@ -1913,7 +1921,7 @@ bool QJpegXLHandler::decodeContainer()
JxlDecoderRewind(m_decoder);
if (JxlDecoderSetInput(m_decoder, buf, len) != JXL_DEC_SUCCESS) {
qWarning("ERROR: JxlDecoderSetInput failed");
qCWarning(LOG_JXLPLUGIN, "ERROR: JxlDecoderSetInput failed");
m_parseState = ParseJpegXLError;
return false;
}
@@ -1921,11 +1929,11 @@ bool QJpegXLHandler::decodeContainer()
JxlDecoderCloseInput(m_decoder);
if (JxlDecoderSetDecompressBoxes(m_decoder, JXL_TRUE) != JXL_DEC_SUCCESS) {
qWarning("WARNING: JxlDecoderSetDecompressBoxes failed");
qCWarning(LOG_JXLPLUGIN, "WARNING: JxlDecoderSetDecompressBoxes failed");
}
if (JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_BOX | JXL_DEC_BOX_COMPLETE) != JXL_DEC_SUCCESS) {
qWarning("ERROR: JxlDecoderSubscribeEvents failed");
qCWarning(LOG_JXLPLUGIN, "ERROR: JxlDecoderSubscribeEvents failed");
m_parseState = ParseJpegXLError;
return false;
}
@@ -1947,7 +1955,7 @@ bool QJpegXLHandler::decodeContainer()
case JXL_DEC_BOX:
status = JxlDecoderGetBoxType(m_decoder, box_type, JXL_TRUE);
if (status != JXL_DEC_SUCCESS) {
qWarning("Error in JxlDecoderGetBoxType");
qCWarning(LOG_JXLPLUGIN, "Error in JxlDecoderGetBoxType");
m_parseState = ParseJpegXLError;
return false;
}
@@ -1965,17 +1973,17 @@ bool QJpegXLHandler::decodeContainer()
}
break;
case JXL_DEC_ERROR:
qWarning("JXL Metadata decoding error");
qCWarning(LOG_JXLPLUGIN, "JXL Metadata decoding error");
m_parseState = ParseJpegXLError;
return false;
break;
case JXL_DEC_NEED_MORE_INPUT:
qWarning("JXL metadata are probably incomplete");
qCWarning(LOG_JXLPLUGIN, "JXL metadata are probably incomplete");
m_parseState = ParseJpegXLError;
return false;
break;
default:
qWarning("Unexpected event %d instead of JXL_DEC_BOX", status);
qCWarning(LOG_JXLPLUGIN, "Unexpected event %d instead of JXL_DEC_BOX", status);
m_parseState = ParseJpegXLError;
return false;
break;
@@ -2003,7 +2011,7 @@ bool QJpegXLHandler::decodeContainer()
} else if (headerindexBE != -1) {
m_exif = exifBox.mid(headerindexBE);
} else {
qWarning("Exif box in JXL file doesn't have TIFF header");
qCWarning(LOG_JXLPLUGIN, "Exif box in JXL file doesn't have TIFF header");
}
}
#endif
@@ -2016,13 +2024,13 @@ bool QJpegXLHandler::extractBox(QByteArray &output, size_t container_size)
uint64_t rawboxsize = 0;
JxlDecoderStatus status = JxlDecoderGetBoxSizeRaw(m_decoder, &rawboxsize);
if (status != JXL_DEC_SUCCESS) {
qWarning("ERROR: JxlDecoderGetBoxSizeRaw failed");
qCWarning(LOG_JXLPLUGIN, "ERROR: JxlDecoderGetBoxSizeRaw failed");
m_parseState = ParseJpegXLError;
return false;
}
if (rawboxsize > container_size) {
qWarning("JXL metadata box is incomplete");
qCWarning(LOG_JXLPLUGIN, "JXL metadata box is incomplete");
m_parseState = ParseJpegXLError;
return false;
}
@@ -2030,7 +2038,7 @@ bool QJpegXLHandler::extractBox(QByteArray &output, size_t container_size)
output.resize(rawboxsize);
status = JxlDecoderSetBoxBuffer(m_decoder, reinterpret_cast<uint8_t *>(output.data()), output.size());
if (status != JXL_DEC_SUCCESS) {
qWarning("ERROR: JxlDecoderSetBoxBuffer failed");
qCWarning(LOG_JXLPLUGIN, "ERROR: JxlDecoderSetBoxBuffer failed");
m_parseState = ParseJpegXLError;
return false;
}
@@ -2041,7 +2049,7 @@ bool QJpegXLHandler::extractBox(QByteArray &output, size_t container_size)
size_t bytes_remains = JxlDecoderReleaseBoxBuffer(m_decoder);
if (output.size() > 4194304) { // approx. 4MB limit for decompressed metadata box
qWarning("JXL metadata box is too large");
qCWarning(LOG_JXLPLUGIN, "JXL metadata box is too large");
m_parseState = ParseJpegXLError;
return false;
}
@@ -2051,7 +2059,7 @@ bool QJpegXLHandler::extractBox(QByteArray &output, size_t container_size)
uint8_t *extension_buffer = reinterpret_cast<uint8_t *>(output.data()) + (output.size() - extension_size);
if (JxlDecoderSetBoxBuffer(m_decoder, extension_buffer, extension_size) != JXL_DEC_SUCCESS) {
qWarning("ERROR: JxlDecoderSetBoxBuffer failed after JXL_DEC_BOX_NEED_MORE_OUTPUT");
qCWarning(LOG_JXLPLUGIN, "ERROR: JxlDecoderSetBoxBuffer failed after JXL_DEC_BOX_NEED_MORE_OUTPUT");
m_parseState = ParseJpegXLError;
return false;
}
@@ -2059,7 +2067,7 @@ bool QJpegXLHandler::extractBox(QByteArray &output, size_t container_size)
} while (status == JXL_DEC_BOX_NEED_MORE_OUTPUT);
if (status != JXL_DEC_BOX_COMPLETE) {
qWarning("Unexpected event %d instead of JXL_DEC_BOX_COMPLETE", status);
qCWarning(LOG_JXLPLUGIN, "Unexpected event %d instead of JXL_DEC_BOX_COMPLETE", status);
m_parseState = ParseJpegXLError;
return false;
}
+9 -3
View File
@@ -977,8 +977,14 @@ bool JXRHandler::read(QImage *outImage)
if (auto err = d->pDecoder->GetResolution(d->pDecoder, &hres, &vres)) {
qCWarning(LOG_JXRPLUGIN) << "JXRHandler::read() error while reading resolution:" << err;
} else {
img.setDotsPerMeterX(qRound(hres * 1000 / 25.4));
img.setDotsPerMeterY(qRound(vres * 1000 / 25.4));
const qint32 hdpm = dpi2ppm(hres);
if (hdpm > 0) {
img.setDotsPerMeterX(hdpm);
}
const qint32 vdpm = dpi2ppm(vres);
if (vdpm > 0) {
img.setDotsPerMeterY(vdpm);
}
}
// alpha copy mode
@@ -1124,7 +1130,7 @@ bool JXRHandler::write(const QImage &image)
qCWarning(LOG_JXRPLUGIN) << "JXRHandler::write() error while setting the image size:" << err;
return false;
}
if (auto err = d->pEncoder->SetResolution(d->pEncoder, qi.dotsPerMeterX() * 25.4 / 1000, qi.dotsPerMeterY() * 25.4 / 1000)) {
if (auto err = d->pEncoder->SetResolution(d->pEncoder, dppm2dpi(qi.dotsPerMeterX()), dppm2dpi(qi.dotsPerMeterY()))) {
qCWarning(LOG_JXRPLUGIN) << "JXRHandler::write() error while setting the image resolution:" << err;
return false;
}
+11 -7
View File
@@ -1195,11 +1195,15 @@ void MicroExif::updateImageMetadata(QImage &targetImage, bool replaceExisting) c
bool MicroExif::updateImageResolution(QImage &targetImage)
{
if (horizontalResolution() > 0)
targetImage.setDotsPerMeterX(qRound(horizontalResolution() / 25.4 * 1000));
if (verticalResolution() > 0)
targetImage.setDotsPerMeterY(qRound(verticalResolution() / 25.4 * 1000));
return (horizontalResolution() > 0) || (verticalResolution() > 0);
const auto hdpm = dpi2ppm(horizontalResolution());
if (hdpm > 0) {
targetImage.setDotsPerMeterX(hdpm);
}
const auto vdpm = dpi2ppm(verticalResolution());
if (vdpm > 0) {
targetImage.setDotsPerMeterY(vdpm);
}
return (hdpm > 0) || (vdpm > 0);
}
MicroExif MicroExif::fromByteArray(const QByteArray &ba, bool searchHeader)
@@ -1269,8 +1273,8 @@ MicroExif MicroExif::fromImage(const QImage &image)
// Image properties
exif.setWidth(image.width());
exif.setHeight(image.height());
exif.setHorizontalResolution(image.dotsPerMeterX() * 25.4 / 1000);
exif.setVerticalResolution(image.dotsPerMeterY() * 25.4 / 1000);
exif.setHorizontalResolution(dppm2dpi(image.dotsPerMeterX()));
exif.setVerticalResolution(dppm2dpi(image.dotsPerMeterY()));
exif.setColorSpace(image.colorSpace());
// TIFF strings
+28 -17
View File
@@ -12,8 +12,14 @@
#include <QColor>
#include <QColorSpace>
#include <QDataStream>
#include <QDebug>
#include <QImage>
#include <QLoggingCategory>
#ifdef QT_DEBUG
Q_LOGGING_CATEGORY(LOG_PCXPLUGIN, "kf.imageformats.plugins.pcx", QtDebugMsg)
#else
Q_LOGGING_CATEGORY(LOG_PCXPLUGIN, "kf.imageformats.plugins.pcx", QtWarningMsg)
#endif
#pragma pack(push, 1)
class RGB
@@ -325,7 +331,7 @@ static bool readImage1(QImage &img, QDataStream &s, const PCXHEADER &header)
img.setColorCount(2);
if (img.isNull()) {
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());
qCWarning(LOG_PCXPLUGIN) << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());
return false;
}
@@ -360,12 +366,12 @@ static bool readImage4(QImage &img, QDataStream &s, const PCXHEADER &header)
img = imageAlloc(header.width(), header.height(), header.format());
img.setColorCount(16);
if (img.isNull()) {
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());
qCWarning(LOG_PCXPLUGIN) << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());
return false;
}
if (header.BytesPerLine < (header.width() + 7) / 8) {
qWarning() << "PCX image has invalid BytesPerLine value";
qCWarning(LOG_PCXPLUGIN) << "PCX image has invalid BytesPerLine value";
return false;
}
@@ -390,7 +396,7 @@ static bool readImage4(QImage &img, QDataStream &s, const PCXHEADER &header)
uchar *p = img.scanLine(y);
if (!p) {
qWarning() << "Failed to get scanline for" << y << "might be out of bounds";
qCWarning(LOG_PCXPLUGIN) << "Failed to get scanline for" << y << "might be out of bounds";
}
for (int x = 0; x < header.width(); ++x) {
p[x] = pixbuf[x];
@@ -413,7 +419,7 @@ static bool readImage2(QImage &img, QDataStream &s, const PCXHEADER &header)
img.setColorCount(4);
if (img.isNull()) {
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());
qCWarning(LOG_PCXPLUGIN) << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());
return false;
}
@@ -456,7 +462,7 @@ static bool readImage4v2(QImage &img, QDataStream &s, const PCXHEADER &header)
img.setColorCount(16);
if (img.isNull()) {
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());
qCWarning(LOG_PCXPLUGIN) << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());
return false;
}
@@ -497,7 +503,7 @@ static bool readImage8(QImage &img, QDataStream &s, const PCXHEADER &header)
img.setColorCount(256);
if (img.isNull()) {
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());
qCWarning(LOG_PCXPLUGIN) << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());
return false;
}
@@ -535,7 +541,6 @@ static bool readImage8(QImage &img, QDataStream &s, const PCXHEADER &header)
}
}
// qDebug() << "Palette Flag: " << flag;
if (flag == 12 && (header.Version == 5 || header.Version == 2)) {
// Read the palette
quint8 r;
@@ -560,7 +565,7 @@ static bool readImage24(QImage &img, QDataStream &s, const PCXHEADER &header)
img = imageAlloc(header.width(), header.height(), header.format());
if (img.isNull()) {
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());
qCWarning(LOG_PCXPLUGIN) << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());
return false;
}
@@ -774,10 +779,10 @@ static bool writeImage24(const QImage &image, QDataStream &s, PCXHEADER &header)
return false;
}
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
auto cs = image.colorSpace();
auto tcs = QColorSpace();
auto tfmt = image.format();
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
auto cs = image.colorSpace();
if (cs.isValid() && cs.colorModel() == QColorSpace::ColorModel::Cmyk && tfmt == QImage::Format_CMYK8888) {
tcs = QColorSpace(QColorSpace::SRgb);
tfmt = QImage::Format_RGB32;
@@ -888,8 +893,14 @@ bool PCXHandler::read(QImage *outImage)
return false;
}
img.setDotsPerMeterX(qRound(header.HDpi / 25.4 * 1000));
img.setDotsPerMeterY(qRound(header.YDpi / 25.4 * 1000));
auto hres = dpi2ppm(header.HDpi);
if (hres > 0) {
img.setDotsPerMeterX(hres);
}
auto vres = dpi2ppm(header.YDpi);
if (vres > 0) {
img.setDotsPerMeterY(vres);
}
*outImage = img;
return true;
}
@@ -915,8 +926,8 @@ bool PCXHandler::write(const QImage &image)
header.YMin = 0;
header.XMax = w - 1;
header.YMax = h - 1;
header.HDpi = qRound(image.dotsPerMeterX() * 25.4 / 1000);
header.YDpi = qRound(image.dotsPerMeterY() * 25.4 / 1000);
header.HDpi = qRoundOrZero_T<quint16>(dppm2dpi(image.dotsPerMeterX()));
header.YDpi = qRoundOrZero_T<quint16>(dppm2dpi(image.dotsPerMeterY()));
header.Reserved = 0;
header.PaletteInfo = 1;
@@ -977,7 +988,7 @@ QVariant PCXHandler::option(ImageOption option) const
bool PCXHandler::canRead(QIODevice *device)
{
if (!device) {
qWarning("PCXHandler::canRead() called with no device");
qCWarning(LOG_PCXPLUGIN) << "PCXHandler::canRead() called with no device";
return false;
}
+17 -11
View File
@@ -20,8 +20,8 @@
#include <QColorSpace>
#include <QDataStream>
#include <QDebug>
#include <QImage>
#include <QLoggingCategory>
#include <QVariant>
#include <algorithm>
@@ -29,6 +29,12 @@
#include <qendian.h>
#include <utility>
#ifdef QT_DEBUG
Q_LOGGING_CATEGORY(LOG_PICPLUGIN, "kf.imageformats.plugins.pic", QtDebugMsg)
#else
Q_LOGGING_CATEGORY(LOG_PICPLUGIN, "kf.imageformats.plugins.pic", QtWarningMsg)
#endif
/**
* Reads a PIC file header from a data stream.
*
@@ -193,7 +199,7 @@ static bool readRow(QDataStream &stream, QRgb *row, quint16 width, const QList<P
if (channel.encoding == MixedRLE) {
bool success = decodeRLEData(RLEVariant::PIC, stream, row, width, readPixel, updatePixel);
if (!success) {
qDebug() << "decodeRLEData failed";
qCDebug(LOG_PICPLUGIN) << "decodeRLEData failed";
return false;
}
} else if (channel.encoding == Uncompressed) {
@@ -203,12 +209,12 @@ static bool readRow(QDataStream &stream, QRgb *row, quint16 width, const QList<P
}
} else {
// unknown encoding
qDebug() << "Unknown encoding";
qCDebug(LOG_PICPLUGIN) << "Unknown encoding";
return false;
}
}
if (stream.status() != QDataStream::Ok) {
qDebug() << "DataStream status was" << stream.status();
qCDebug(LOG_PICPLUGIN) << "DataStream status was" << stream.status();
}
return stream.status() == QDataStream::Ok;
}
@@ -232,7 +238,7 @@ bool SoftimagePICHandler::read(QImage *image)
for (const PicChannel &channel : std::as_const(m_channels)) {
if (channel.size != 8) {
// we cannot read images that do not come in bytes
qDebug() << "Channel size was" << channel.size;
qCDebug(LOG_PICPLUGIN) << "Channel size was" << channel.size;
m_state = Error;
return false;
}
@@ -243,7 +249,7 @@ bool SoftimagePICHandler::read(QImage *image)
QImage img = imageAlloc(m_header.width, m_header.height, fmt);
if (img.isNull()) {
qDebug() << "Failed to allocate image, invalid dimensions?" << QSize(m_header.width, m_header.height) << fmt;
qCDebug(LOG_PICPLUGIN) << "Failed to allocate image, invalid dimensions?" << QSize(m_header.width, m_header.height) << fmt;
return false;
}
@@ -252,7 +258,7 @@ bool SoftimagePICHandler::read(QImage *image)
for (int y = 0; y < m_header.height; y++) {
QRgb *row = reinterpret_cast<QRgb *>(img.scanLine(y));
if (!readRow(m_dataStream, row, m_header.width, m_channels)) {
qDebug() << "readRow failed";
qCDebug(LOG_PICPLUGIN) << "readRow failed";
m_state = Error;
return false;
}
@@ -267,10 +273,10 @@ bool SoftimagePICHandler::read(QImage *image)
bool SoftimagePICHandler::write(const QImage &image)
{
bool alpha = image.hasAlphaChannel();
auto tcs = QColorSpace();
auto tfmt = image.format();
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
auto cs = image.colorSpace();
auto tfmt = image.format();
auto tcs = QColorSpace();
if (cs.isValid() && cs.colorModel() == QColorSpace::ColorModel::Cmyk && tfmt == QImage::Format_CMYK8888) {
tcs = QColorSpace(QColorSpace::SRgb);
tfmt = QImage::Format_RGB32;
@@ -281,11 +287,11 @@ bool SoftimagePICHandler::write(const QImage &image)
}
if (image.width() < 0 || image.height() < 0) {
qDebug() << "Image size invalid:" << image.width() << image.height();
qCDebug(LOG_PICPLUGIN) << "Image size invalid:" << image.width() << image.height();
return false;
}
if (image.width() > 65535 || image.height() > 65535) {
qDebug() << "Image too big:" << image.width() << image.height();
qCDebug(LOG_PICPLUGIN) << "Image too big:" << image.width() << image.height();
// there are only two bytes for each dimension
return false;
}
+44 -34
View File
@@ -33,14 +33,20 @@
#include "scanlineconverter_p.h"
#include "util_p.h"
#include <QDataStream>
#include <QDebug>
#include <QImage>
#include <QColorSpace>
#include <QDataStream>
#include <QImage>
#include <QLoggingCategory>
#include <cmath>
#include <cstring>
#ifdef QT_DEBUG
Q_LOGGING_CATEGORY(LOG_PSDPLUGIN, "kf.imageformats.plugins.psd", QtDebugMsg)
#else
Q_LOGGING_CATEGORY(LOG_PSDPLUGIN, "kf.imageformats.plugins.psd", QtWarningMsg)
#endif
typedef quint32 uint;
typedef quint16 ushort;
typedef quint8 uchar;
@@ -343,7 +349,7 @@ static PSDImageResourceSection readImageResourceSection(QDataStream &s, bool *ok
size -= sizeof(signature);
// NOTE: MeSa signature is not documented but found in some old PSD take from Photoshop 7.0 CD.
if (signature != S_8BIM && signature != S_MeSa) { // 8BIM and MeSa
qDebug() << "Invalid Image Resource Block Signature!";
qCDebug(LOG_PSDPLUGIN) << "Invalid Image Resource Block Signature!";
*ok = false;
break;
}
@@ -373,7 +379,7 @@ static PSDImageResourceSection readImageResourceSection(QDataStream &s, bool *ok
if (read > 0)
size -= read;
if (quint32(read) != dataSize) {
qDebug() << "Image Resource Block Read Error!";
qCDebug(LOG_PSDPLUGIN) << "Image Resource Block Read Error!";
*ok = false;
break;
}
@@ -596,17 +602,21 @@ static bool setResolution(QImage &img, const PSDImageResourceSection &irs)
s >> i32; // Horizontal resolution in pixels per inch.
if (i32 <= 0)
return false;
auto hres = fixedPointToDouble(i32);
auto hres = dpi2ppm(fixedPointToDouble(i32));
s.skipRawData(4); // Display data (not used here)
s >> i32; // Vertial resolution in pixels per inch.
if (i32 <= 0)
return false;
auto vres = fixedPointToDouble(i32);
auto vres = dpi2ppm(fixedPointToDouble(i32));
img.setDotsPerMeterX(hres * 1000 / 25.4);
img.setDotsPerMeterY(vres * 1000 / 25.4);
if (hres > 0) {
img.setDotsPerMeterX(hres);
}
if (vres > 0) {
img.setDotsPerMeterY(vres);
}
return true;
}
@@ -657,18 +667,18 @@ static QDataStream &operator>>(QDataStream &s, PSDHeader &header)
static bool IsValid(const PSDHeader &header)
{
if (header.signature != 0x38425053) { // '8BPS'
// qDebug() << "PSD header: invalid signature" << header.signature;
// qCDebug(LOG_PSDPLUGIN) << "PSD header: invalid signature" << header.signature;
return false;
}
if (header.version != 1 && header.version != 2) {
qDebug() << "PSD header: invalid version" << header.version;
qCDebug(LOG_PSDPLUGIN) << "PSD header: invalid version" << header.version;
return false;
}
if (header.depth != 8 &&
header.depth != 16 &&
header.depth != 32 &&
header.depth != 1) {
qDebug() << "PSD header: invalid depth" << header.depth;
qCDebug(LOG_PSDPLUGIN) << "PSD header: invalid depth" << header.depth;
return false;
}
if (header.color_mode != CM_RGB &&
@@ -679,17 +689,17 @@ static bool IsValid(const PSDHeader &header)
header.color_mode != CM_LABCOLOR &&
header.color_mode != CM_MULTICHANNEL &&
header.color_mode != CM_BITMAP) {
qDebug() << "PSD header: invalid color mode" << header.color_mode;
qCDebug(LOG_PSDPLUGIN) << "PSD header: invalid color mode" << header.color_mode;
return false;
}
// Specs tells: "Supported range is 1 to 56" but when the alpha channel is present the limit is 57:
// Photoshop does not make you add more (see also 53alphas.psd test case).
if (header.channel_count < 1 || header.channel_count > 57) {
qDebug() << "PSD header: invalid number of channels" << header.channel_count;
qCDebug(LOG_PSDPLUGIN) << "PSD header: invalid number of channels" << header.channel_count;
return false;
}
if (header.width > std::min(300000, PSD_MAX_IMAGE_WIDTH) || header.height > std::min(300000, PSD_MAX_IMAGE_HEIGHT)) {
qDebug() << "PSD header: invalid image size" << header.width << "x" << header.height;
qCDebug(LOG_PSDPLUGIN) << "PSD header: invalid image size" << header.width << "x" << header.height;
return false;
}
return true;
@@ -973,7 +983,7 @@ inline void cmykToRgb(uchar *target, qint32 targetChannels, const char *source,
auto invmax = 1.0 / max; // speed improvements by ~10%
if (sourceChannels < 2) {
qDebug() << "cmykToRgb: image is not a valid MCH/CMYK!";
qCDebug(LOG_PSDPLUGIN) << "cmykToRgb: image is not a valid MCH/CMYK!";
return;
}
@@ -1022,7 +1032,7 @@ inline void labToRgb(uchar *target, qint32 targetChannels, const char *source, q
auto invmax = 1.0 / max;
if (sourceChannels < 3) {
qDebug() << "labToRgb: image is not a valid LAB!";
qCDebug(LOG_PSDPLUGIN) << "labToRgb: image is not a valid LAB!";
return;
}
@@ -1163,39 +1173,39 @@ public:
// Check image file format.
if (stream.atEnd() || !IsValid(m_header)) {
// qDebug() << "This PSD file is not valid.";
// qCDebug(LOG_PSDPLUGIN) << "This PSD file is not valid.";
return false;
}
// Check if it's a supported format.
if (!IsSupported(m_header)) {
// qDebug() << "This PSD file is not supported.";
// qCDebug(LOG_PSDPLUGIN) << "This PSD file is not supported.";
return false;
}
// Color Mode Data section
m_cmds = readColorModeDataSection(stream, &ok);
if (!ok) {
qDebug() << "Error while skipping Color Mode Data section";
qCDebug(LOG_PSDPLUGIN) << "Error while skipping Color Mode Data section";
return false;
}
// Image Resources Section
m_irs = readImageResourceSection(stream, &ok);
if (!ok) {
qDebug() << "Error while reading Image Resources Section";
qCDebug(LOG_PSDPLUGIN) << "Error while reading Image Resources Section";
return false;
}
// Checking for merged image (Photoshop compatibility data)
if (!hasMergedData()) {
qDebug() << "No merged data found";
qCDebug(LOG_PSDPLUGIN) << "No merged data found";
return false;
}
// Layer and Mask section
m_lms = readLayerAndMaskSection(stream, isPsb(), &ok);
if (!ok) {
qDebug() << "Error while skipping Layer and Mask section";
qCDebug(LOG_PSDPLUGIN) << "Error while skipping Layer and Mask section";
return false;
}
@@ -1256,19 +1266,19 @@ bool PSDHandler::read(QImage *image)
quint16 compression;
stream >> compression;
if (compression > 1) {
qDebug() << "Unknown compression type";
qCDebug(LOG_PSDPLUGIN) << "Unknown compression type";
return false;
}
const QImage::Format format = d->format();
if (format == QImage::Format_Invalid) {
qWarning() << "Unsupported image format. color_mode:" << header.color_mode << "depth:" << header.depth << "channel_count:" << header.channel_count;
qCWarning(LOG_PSDPLUGIN) << "Unsupported image format. color_mode:" << header.color_mode << "depth:" << header.depth << "channel_count:" << header.channel_count;
return false;
}
img = imageAlloc(d->size(), format);
if (img.isNull()) {
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width, header.height);
qCWarning(LOG_PSDPLUGIN) << "Failed to allocate image, invalid dimensions?" << QSize(header.width, header.height);
return false;
}
img.fill(qRgb(0, 0, 0));
@@ -1283,7 +1293,7 @@ bool PSDHandler::read(QImage *image)
auto native_cmyk = img.format() == CMYK_FORMAT;
if (header.height > kMaxQVectorSize / header.channel_count / sizeof(quint32)) {
qWarning() << "LoadPSD() header height/channel_count too big" << header.height << header.channel_count;
qCWarning(LOG_PSDPLUGIN) << "LoadPSD() header height/channel_count too big" << header.height << header.channel_count;
return false;
}
@@ -1342,12 +1352,12 @@ bool PSDHandler::read(QImage *image)
for (qint32 c = 0; c < header.channel_count; ++c) {
auto strideNumber = c * qsizetype(h) + y;
if (!device->seek(stridePositions.at(strideNumber))) {
qDebug() << "Error while seeking the stream of channel" << c << "line" << y;
qCDebug(LOG_PSDPLUGIN) << "Error while seeking the stream of channel" << c << "line" << y;
return false;
}
auto &&strideSize = strides.at(strideNumber);
if (!readChannel(rawStride, stream, strideSize, compression)) {
qDebug() << "Error while reading the stream of channel" << c << "line" << y;
qCDebug(LOG_PSDPLUGIN) << "Error while reading the stream of channel" << c << "line" << y;
return false;
}
@@ -1428,7 +1438,7 @@ bool PSDHandler::read(QImage *image)
for (qint32 y = 0, h = header.height; y < h; ++y) {
auto&& strideSize = strides.at(c * qsizetype(h) + y);
if (!readChannel(rawStride, stream, strideSize, compression)) {
qDebug() << "Error while reading the stream of channel" << c << "line" << y;
qCDebug(LOG_PSDPLUGIN) << "Error while reading the stream of channel" << c << "line" << y;
return false;
}
@@ -1461,7 +1471,7 @@ bool PSDHandler::read(QImage *image)
// Resolution info
if (!setResolution(img, irs)) {
// qDebug() << "No resolution info found!";
// qCDebug(LOG_PSDPLUGIN) << "No resolution info found!";
}
// ICC profile
@@ -1489,12 +1499,12 @@ bool PSDHandler::read(QImage *image)
// XMP data
if (!setXmpData(img, irs)) {
// qDebug() << "No XMP data found!";
// qCDebug(LOG_PSDPLUGIN) << "No XMP data found!";
}
// EXIF data
if (!setExifData(img, d->m_exif)) {
// qDebug() << "No EXIF data found!";
// qCDebug(LOG_PSDPLUGIN) << "No EXIF data found!";
}
// Duotone images: color data contains the duotone specification (not documented).
@@ -1565,7 +1575,7 @@ QVariant PSDHandler::option(ImageOption option) const
bool PSDHandler::canRead(QIODevice *device)
{
if (!device) {
qWarning("PSDHandler::canRead() called with no device");
qCWarning(LOG_PSDPLUGIN) << "PSDHandler::canRead() called with no device";
return false;
}
+8 -1
View File
@@ -14,6 +14,13 @@
#include <QFile>
#include <QIODevice>
#include <QImage>
#include <QLoggingCategory>
#ifdef QT_DEBUG
Q_LOGGING_CATEGORY(LOG_QOIPLUGIN, "kf.imageformats.plugins.qoi", QtDebugMsg)
#else
Q_LOGGING_CATEGORY(LOG_QOIPLUGIN, "kf.imageformats.plugins.qoi", QtWarningMsg)
#endif
/* *** QOI_MAX_IMAGE_WIDTH and QOI_MAX_IMAGE_HEIGHT ***
* The maximum size in pixel allowed by the plugin.
@@ -347,7 +354,7 @@ bool QOIHandler::canRead() const
bool QOIHandler::canRead(QIODevice *device)
{
if (!device) {
qWarning("QOIHandler::canRead() called with no device");
qCWarning(LOG_QOIPLUGIN) << "QOIHandler::canRead() called with no device";
return false;
}
+23 -17
View File
@@ -12,8 +12,14 @@
#include "util_p.h"
#include <QDataStream>
#include <QDebug>
#include <QImage>
#include <QLoggingCategory>
#ifdef QT_DEBUG
Q_LOGGING_CATEGORY(LOG_RASPLUGIN, "kf.imageformats.plugins.ras", QtDebugMsg)
#else
Q_LOGGING_CATEGORY(LOG_RASPLUGIN, "kf.imageformats.plugins.ras", QtWarningMsg)
#endif
/* *** RAS_MAX_IMAGE_WIDTH and RAS_MAX_IMAGE_HEIGHT ***
* The maximum size in pixel allowed by the plugin.
@@ -73,14 +79,14 @@ static QDataStream &operator>>(QDataStream &s, RasHeader &head)
s >> head.Type;
s >> head.ColorMapType;
s >> head.ColorMapLength;
/*qDebug() << "MagicNumber: " << head.MagicNumber
<< "Width: " << head.Width
<< "Height: " << head.Height
<< "Depth: " << head.Depth
<< "Length: " << head.Length
<< "Type: " << head.Type
<< "ColorMapType: " << head.ColorMapType
<< "ColorMapLength: " << head.ColorMapLength;*/
// qCDebug(LOG_RASPLUGIN) << "MagicNumber: " << head.MagicNumber
// << "Width: " << head.Width
// << "Height: " << head.Height
// << "Depth: " << head.Depth
// << "Length: " << head.Length
// << "Type: " << head.Type
// << "ColorMapType: " << head.ColorMapType
// << "ColorMapLength: " << head.ColorMapLength;
return s;
}
@@ -218,7 +224,7 @@ static bool LoadRAS(QDataStream &s, const RasHeader &ras, QImage &img)
if (rasLineSize & 1)
++rasLineSize;
if (rasLineSize > kMaxQVectorSize) {
qWarning() << "LoadRAS() unsupported line size" << rasLineSize;
qCWarning(LOG_RASPLUGIN) << "LoadRAS() unsupported line size" << rasLineSize;
return false;
}
@@ -256,7 +262,7 @@ static bool LoadRAS(QDataStream &s, const RasHeader &ras, QImage &img)
for (quint32 y = 0; y < ras.Height; ++y) {
auto rasLine = dec.readLine(rasLineSize);
if (rasLine.size() != rasLineSize) {
qWarning() << "LoadRAS() unable to read line" << y << ": the seems corrupted!";
qCWarning(LOG_RASPLUGIN) << "LoadRAS() unable to read line" << y << ": the seems corrupted!";
return false;
}
@@ -336,8 +342,8 @@ static bool LoadRAS(QDataStream &s, const RasHeader &ras, QImage &img)
continue;
}
qWarning() << "LoadRAS() unsupported format!"
<< "ColorMapType:" << ras.ColorMapType << "Type:" << ras.Type << "Depth:" << ras.Depth;
qCWarning(LOG_RASPLUGIN) << "LoadRAS() unsupported format!"
<< "ColorMapType:" << ras.ColorMapType << "Type:" << ras.Type << "Depth:" << ras.Depth;
return false;
}
@@ -373,7 +379,7 @@ bool RASHandler::canRead() const
bool RASHandler::canRead(QIODevice *device)
{
if (!device) {
qWarning("RASHandler::canRead() called with no device");
qCWarning(LOG_RASPLUGIN) << "RASHandler::canRead() called with no device";
return false;
}
@@ -399,19 +405,19 @@ bool RASHandler::read(QImage *outImage)
s >> ras;
if (ras.ColorMapLength > kMaxQVectorSize) {
qWarning() << "read() unsupported image color map length in file header" << ras.ColorMapLength;
qCWarning(LOG_RASPLUGIN) << "LoadRAS() unsupported image color map length in file header" << ras.ColorMapLength;
return false;
}
// Check supported file types.
if (!IsSupported(ras)) {
// qDebug() << "This RAS file is not supported.";
// qCDebug(LOG_RASPLUGIN) << "This RAS file is not supported.";
return false;
}
QImage img;
if (!LoadRAS(s, ras, img)) {
// qDebug() << "Error loading RAS file.";
// qCDebug(LOG_RASPLUGIN) << "Error loading RAS file.";
return false;
}
+39 -6
View File
@@ -12,6 +12,7 @@
#include <QColorSpace>
#include <QDateTime>
#include <QImage>
#include <QLoggingCategory>
#include <QSet>
#include <QTimeZone>
@@ -27,7 +28,17 @@
* By defining RAW_IGNORE_BROKEN_STREAMS removes this protection, leaving
* LibRaw in complete control of error handling (which is mostly absent).
*/
// #define RAW_DISABLE_BROKEN_STREAM_PROTECTION
#define RAW_DISABLE_BROKEN_STREAM_PROTECTION
#endif
/* *** RAW_MAX_IMAGE_WIDTH and RAW_MAX_IMAGE_HEIGHT ***
* The maximum size in pixel allowed by the plugin.
*/
#ifndef RAW_MAX_IMAGE_WIDTH
#define RAW_MAX_IMAGE_WIDTH std::min(65535, KIF_LARGE_IMAGE_PIXEL_LIMIT)
#endif
#ifndef RAW_MAX_IMAGE_HEIGHT
#define RAW_MAX_IMAGE_HEIGHT RAW_MAX_IMAGE_WIDTH
#endif
#ifdef QT_DEBUG
@@ -41,6 +52,12 @@
// #define EXCLUDE_LibRaw_QIODevice // Uncomment this code if you think that the problem is LibRaw_QIODevice (default commented)
#endif
#ifdef QT_DEBUG
Q_LOGGING_CATEGORY(LOG_RAWPLUGIN, "kf.imageformats.plugins.raw", QtDebugMsg)
#else
Q_LOGGING_CATEGORY(LOG_RAWPLUGIN, "kf.imageformats.plugins.raw", QtWarningMsg)
#endif
namespace // Private.
{
@@ -68,6 +85,18 @@ const auto supported_formats = QSet<QByteArray>{
};
// clang-format on
/*!
* \brief rawImageSize
* \return The size in pixels of the RAW image.
*/
static QSize rawImageSize(LibRaw *rawProcessor)
{
auto w = libraw_get_iwidth(&rawProcessor->imgdata);
auto h = libraw_get_iheight(&rawProcessor->imgdata);
// flip & 4: taken from LibRaw code
return (rawProcessor->imgdata.sizes.flip & 4) ? QSize(h, w) : QSize(w, h);
}
inline int raw_scanf_one(const QByteArray &ba, const char *fmt, void *val)
{
// WARNING: Here it would be nice to use sscanf like LibRaw does but there
@@ -728,6 +757,13 @@ bool LoadRAW(QImageIOHandler *handler, QImage &img)
}
#endif
// *** Limiting the maximum image size on a reasonable size
auto size = rawImageSize(rawProcessor.get());
if (size.width() > RAW_MAX_IMAGE_WIDTH || size.height() > RAW_MAX_IMAGE_HEIGHT) {
qCWarning(LOG_RAWPLUGIN) << "The maximum image size is limited to" << RAW_MAX_IMAGE_WIDTH << "x" << RAW_MAX_IMAGE_HEIGHT << "px";
return false;
}
// *** Unpacking selected image
if (rawProcessor->unpack() != LIBRAW_SUCCESS) {
return false;
@@ -946,10 +982,7 @@ QVariant RAWHandler::option(ImageOption option) const
rawProcessor->imgdata.rawparams.shot_select = currentImageNumber();
#endif
if (rawProcessor->open_datastream(&stream) == LIBRAW_SUCCESS) {
auto w = libraw_get_iwidth(&rawProcessor->imgdata);
auto h = libraw_get_iheight(&rawProcessor->imgdata);
// flip & 4: taken from LibRaw code
v = (rawProcessor->imgdata.sizes.flip & 4) ? QSize(h, w) : QSize(w, h);
v = rawImageSize(rawProcessor.get());
}
d->rollbackTransaction();
}
@@ -1010,7 +1043,7 @@ int RAWHandler::currentImageNumber() const
bool RAWHandler::canRead(QIODevice *device)
{
if (!device) {
qWarning("RAWHandler::canRead() called with no device");
qCWarning(LOG_RAWPLUGIN) << "RAWHandler::canRead() called with no device";
return false;
}
if (device->isSequential()) {
+20 -15
View File
@@ -26,11 +26,17 @@
#include <cstring>
#include <QColorSpace>
#include <QDebug>
#include <QImage>
#include <QList>
#include <QLoggingCategory>
#include <QMap>
#ifdef QT_DEBUG
Q_LOGGING_CATEGORY(LOG_RGBPLUGIN, "kf.imageformats.plugins.rgb", QtDebugMsg)
#else
Q_LOGGING_CATEGORY(LOG_RGBPLUGIN, "kf.imageformats.plugins.rgb", QtWarningMsg)
#endif
class RLEData : public QList<uchar>
{
public:
@@ -296,12 +302,12 @@ bool SGIImagePrivate::readImage(QImage &img)
img = imageAlloc(size(), format());
if (img.isNull()) {
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(_xsize, _ysize);
qCWarning(LOG_RGBPLUGIN) << "Failed to allocate image, invalid dimensions?" << QSize(_xsize, _ysize);
return false;
}
if (_zsize > 4) {
// qDebug() << "using first 4 of " << _zsize << " channels";
// qCDebug(LOG_RGBPLUGIN) << "using first 4 of " << _zsize << " channels";
// Only let this continue if it won't cause a int overflow later
// this is most likely a broken file anyway
if (_ysize > std::numeric_limits<int>::max() / _zsize) {
@@ -345,14 +351,14 @@ bool SGIImagePrivate::readImage(QImage &img)
for (uint o = 0; o < _numrows; o++) {
// don't change to greater-or-equal!
if (_starttab[o] + _lengthtab[o] > (uint)_data.size()) {
// qDebug() << "image corrupt (sanity check failed)";
// qCDebug(LOG_RGBPLUGIN) << "image corrupt (sanity check failed)";
return false;
}
}
}
if (!readData(img)) {
// qDebug() << "image corrupt (incomplete scanline)";
// qCDebug(LOG_RGBPLUGIN) << "image corrupt (incomplete scanline)";
return false;
}
@@ -475,7 +481,7 @@ bool SGIImagePrivate::scanData(const QImage &img, const QImage::Format &tfmt, co
for (y = 0; y < _ysize; y++) {
const int yPos = _ysize - y - 1; // scanline doesn't do any sanity checking
if (yPos >= img.height()) {
qWarning() << "Failed to get scanline for" << yPos;
qCWarning(LOG_RGBPLUGIN) << "Failed to get scanline for" << yPos;
return false;
}
@@ -496,7 +502,7 @@ bool SGIImagePrivate::scanData(const QImage &img, const QImage::Format &tfmt, co
for (y = 0; y < _ysize; y++) {
const int yPos = _ysize - y - 1;
if (yPos >= img.height()) {
qWarning() << "Failed to get scanline for" << yPos;
qCWarning(LOG_RGBPLUGIN) << "Failed to get scanline for" << yPos;
return false;
}
@@ -511,7 +517,7 @@ bool SGIImagePrivate::scanData(const QImage &img, const QImage::Format &tfmt, co
for (y = 0; y < _ysize; y++) {
const int yPos = _ysize - y - 1;
if (yPos >= img.height()) {
qWarning() << "Failed to get scanline for" << yPos;
qCWarning(LOG_RGBPLUGIN) << "Failed to get scanline for" << yPos;
return false;
}
@@ -531,7 +537,7 @@ bool SGIImagePrivate::scanData(const QImage &img, const QImage::Format &tfmt, co
for (y = 0; y < _ysize; y++) {
const int yPos = _ysize - y - 1;
if (yPos >= img.height()) {
qWarning() << "Failed to get scanline for" << yPos;
qCWarning(LOG_RGBPLUGIN) << "Failed to get scanline for" << yPos;
return false;
}
@@ -661,7 +667,7 @@ bool SGIImagePrivate::writeHeader()
bool SGIImagePrivate::writeRle()
{
_rle = 1;
// qDebug() << "writing RLE data";
// qCDebug(LOG_RGBPLUGIN) << "writing RLE data";
if (!writeHeader()) {
return false;
}
@@ -747,7 +753,6 @@ bool SGIImagePrivate::writeVerbatim(const QImage &img, const QImage::Format &tfm
bool SGIImagePrivate::writeImage(const QImage &image)
{
// qDebug() << "writing "; // TODO add filename
if (image.allGray()) {
_dim = 2, _zsize = 1;
} else {
@@ -759,10 +764,10 @@ bool SGIImagePrivate::writeImage(const QImage &image)
_dim = 3, _zsize++;
}
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
auto cs = image.colorSpace();
auto tcs = QColorSpace();
auto tfmt = image.format();
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
auto cs = image.colorSpace();
if (cs.isValid() && cs.colorModel() == QColorSpace::ColorModel::Cmyk && tfmt == QImage::Format_CMYK8888) {
tcs = QColorSpace(QColorSpace::SRgb);
tfmt = QImage::Format_RGB32;
@@ -794,7 +799,7 @@ bool SGIImagePrivate::writeImage(const QImage &image)
_rlemap.setBaseOffset(512 + _numrows * 2 * sizeof(quint32));
if (!scanData(image, tfmt, tcs)) {
// qDebug() << "this can't happen";
// qCDebug(LOG_RGBPLUGIN) << "this can't happen";
return false;
}
@@ -884,7 +889,7 @@ QVariant RGBHandler::option(ImageOption option) const
bool RGBHandler::canRead(QIODevice *device)
{
if (!device) {
qWarning("RGBHandler::canRead() called with no device");
qCWarning(LOG_RGBPLUGIN) << "RGBHandler::canRead() called with no device";
return false;
}
+4 -4
View File
@@ -216,7 +216,7 @@ public:
auto v = QString::fromLatin1(pchar_t(res.data()), res.size()).toDouble(&ok);
if (ok && v > 0) {
if (m_pb._unitsOfMeasurement) { // Inches
return qRoundOrZero(width() / v / 25.4 * 1000);
return dpi2ppm(width() / v);
}
// Millimeters
return qRoundOrZero(width() / v * 1000);
@@ -230,7 +230,7 @@ public:
auto v = QString::fromLatin1(pchar_t(res.data()), res.size()).toDouble(&ok);
if (ok && v > 0) {
if (m_pb._unitsOfMeasurement) { // Inches
return qRoundOrZero(height() / v / 25.4 * 1000);
return dpi2ppm(height() / v);
}
// Millimeters
return qRoundOrZero(height() / v * 1000);
@@ -330,7 +330,7 @@ bool ScitexHandler::canRead() const
bool ScitexHandler::canRead(QIODevice *device)
{
if (!device) {
qWarning("ScitexHandler::canRead() called with no device");
qCWarning(LOG_SCTPLUGIN) << "ScitexHandler::canRead() called with no device";
return false;
}
ScitexHandlerPrivate hp;
@@ -344,7 +344,7 @@ bool ScitexHandler::read(QImage *image)
{
auto dev = device();
if (dev == nullptr) {
qWarning("ScitexHandler::read() called with no device");
qCWarning(LOG_SCTPLUGIN) << "ScitexHandler::read() called with no device";
return false;
}
if (!d->loadHeader(dev)) {
+1 -1
View File
@@ -988,9 +988,9 @@ bool TGAHandler::writeRGBA(const QImage &image)
{
auto format = image.format();
const bool hasAlpha = image.hasAlphaChannel();
auto tcs = QColorSpace();
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
auto cs = image.colorSpace();
auto tcs = QColorSpace();
if (cs.isValid() && cs.colorModel() == QColorSpace::ColorModel::Cmyk && image.format() == QImage::Format_CMYK8888) {
format = QImage::Format_RGB32;
tcs = QColorSpace(QColorSpace::SRgb);
+60 -3
View File
@@ -66,13 +66,70 @@ inline QImage imageAlloc(qint32 width, qint32 height, const QImage::Format &form
return imageAlloc(QSize(width, height), format);
}
inline double qRoundOrZero(double d)
template<class TI, class SF> // SF = source FP, TI = target INT
TI qRoundOrZero_T(SF d, bool *ok = nullptr)
{
// If the value d is outside the range of int, the behavior is undefined.
if (d > std::numeric_limits<int>::max()) {
// checks for undefined behavior
if (qIsNaN(d) || qIsInf(d) || d < SF() || d > SF(std::numeric_limits<TI>::max())) {
if (ok) {
*ok = false;
}
return 0;
}
if (ok) {
*ok = true;
}
return qRound(d);
}
inline qint32 qRoundOrZero(double d, bool *ok = nullptr)
{
return qRoundOrZero_T<qint32>(d, ok);
}
inline qint32 qRoundOrZero(float d, bool *ok = nullptr)
{
return qRoundOrZero_T<qint32>(d, ok);
}
/*!
* \brief dpi2ppm
* Converts a value from DPI to PPM.
* \return \a dpi converted to pixel per meter.
*/
inline qint32 dpi2ppm(double dpi, bool *ok = nullptr)
{
return qRoundOrZero(dpi / double(25.4) * double(1000), ok);
}
inline qint32 dpi2ppm(float dpi, bool *ok = nullptr)
{
return qRoundOrZero(dpi / float(25.4) * float(1000), ok);
}
inline qint32 dpi2ppm(quint16 dpi, bool *ok = nullptr)
{
return qRoundOrZero(dpi / double(25.4) * double(1000), ok);
}
/*!
* \brief ppm2dpi
* Converts a value from PPM to DPI.
* \return \a ppm converted to dot per inch.
*/
template<class TF, class SI> // SI = source INT, TF = target FP
TF ppm2dpi_T(SI ppm, bool *ok = nullptr)
{
if (ok) {
*ok = ppm > 0;
}
return ppm > 0 ? ppm * TF(25.4) / TF(1000) : TF();
}
inline double dppm2dpi(qint32 ppm, bool *ok = nullptr)
{
return ppm2dpi_T<double>(ppm, ok);
}
inline float fppm2dpi(qint32 ppm, bool *ok = nullptr)
{
return ppm2dpi_T<float>(ppm, ok);
}
#endif // UTIL_P_H
+38 -24
View File
@@ -10,7 +10,6 @@
#include "xcf_p.h"
#include <QColorSpace>
#include <QDebug>
#include <QIODevice>
#include <QImage>
#include <QImageReader>
@@ -55,8 +54,6 @@ Q_LOGGING_CATEGORY(XCFPLUGIN, "kf.imageformats.plugins.xcf", QtWarningMsg)
#define DISABLE_TILE_PROFILE_CONV // default uncommented (comment to use the conversion as intended by Martin)
#define DISABLE_IMAGE_PROFILE_CONV // default uncommented (comment to use the conversion as intended by Martin)
const float INCHESPERMETER = (100.0f / 2.54f);
namespace
{
struct RandomTable {
@@ -1170,7 +1167,9 @@ bool XCFImageFormat::loadLayer(QDataStream &xcf_io, XCFImage &xcf_image)
if (!composeTiles(xcf_image)) {
return false;
}
xcf_io.device()->seek(layer.hierarchy_offset);
if (!xcf_io.device()->seek(layer.hierarchy_offset)) {
return false;
}
// As tiles are loaded, they are copied into the layers tiles by
// this routine. (loadMask(), below, uses a slightly different
@@ -1188,7 +1187,9 @@ bool XCFImageFormat::loadLayer(QDataStream &xcf_io, XCFImage &xcf_image)
layer.apply_mask = 1;
}
xcf_io.device()->seek(layer.mask_offset);
if (!xcf_io.device()->seek(layer.mask_offset)) {
return false;
}
if (!loadMask(xcf_io, layer, xcf_image.header.precision)) {
return false;
@@ -1800,6 +1801,9 @@ bool XCFImageFormat::assignImageBytes(Layer &layer, uint i, uint j, const GimpPr
dataPtr[x + 1] = qFromBigEndian(src[x + 1]);
dataPtr[x + 2] = qFromBigEndian(src[x + 2]);
dataPtr[x + 3] = qfloat16(1);
if (dataPtr[x + 0].isNaN() || dataPtr[x + 1].isNaN() || dataPtr[x + 2].isNaN()) {
return false;
}
}
}
break;
@@ -1828,6 +1832,9 @@ bool XCFImageFormat::assignImageBytes(Layer &layer, uint i, uint j, const GimpPr
dataPtr[x + 1] = qFromBigEndian(src[x + 1]);
dataPtr[x + 2] = qFromBigEndian(src[x + 2]);
dataPtr[x + 3] = 1.f;
if (std::isnan(dataPtr[x + 0]) || std::isnan(dataPtr[x + 1]) || std::isnan(dataPtr[x + 2])) {
return false;
}
}
}
break;
@@ -1948,12 +1955,15 @@ bool XCFImageFormat::loadHierarchy(QDataStream &xcf_io, Layer &layer, const Gimp
qint64 saved_pos = xcf_io.device()->pos();
xcf_io.device()->seek(offset);
if (!xcf_io.device()->seek(offset)) {
return false;
}
if (!loadLevel(xcf_io, layer, bpp, precision)) {
return false;
}
xcf_io.device()->seek(saved_pos);
if (!xcf_io.device()->seek(saved_pos)) {
return false;
}
return true;
}
@@ -2056,7 +2066,9 @@ bool XCFImageFormat::loadLevel(QDataStream &xcf_io, Layer &layer, qint32 bpp, co
offset2 = offset + blockSize;
}
xcf_io.device()->seek(offset);
if (!xcf_io.device()->seek(offset)) {
return false;
}
qint64 bytesParsed = 0;
switch (layer.compression) {
@@ -2163,7 +2175,9 @@ bool XCFImageFormat::loadLevel(QDataStream &xcf_io, Layer &layer, qint32 bpp, co
return false;
}
xcf_io.device()->seek(saved_pos);
if (!xcf_io.device()->seek(saved_pos)) {
return false;
}
offset = readOffsetPtr(xcf_io);
if (offset < 0) {
@@ -2203,7 +2217,9 @@ bool XCFImageFormat::loadMask(QDataStream &xcf_io, Layer &layer, const GimpPreci
return false;
}
xcf_io.device()->seek(hierarchy_offset);
if (!xcf_io.device()->seek(hierarchy_offset)) {
return false;
}
layer.assignBytes = assignMaskBytes;
if (!loadHierarchy(xcf_io, layer, precision)) {
@@ -2709,17 +2725,13 @@ bool XCFImageFormat::initializeImage(XCFImage &xcf_image)
}
#endif
if (xcf_image.x_resolution > 0 && xcf_image.y_resolution > 0) {
const float dpmx = xcf_image.x_resolution * INCHESPERMETER;
if (dpmx > float(std::numeric_limits<int>::max())) {
return false;
}
const float dpmy = xcf_image.y_resolution * INCHESPERMETER;
if (dpmy > float(std::numeric_limits<int>::max())) {
return false;
}
image.setDotsPerMeterX((int)dpmx);
image.setDotsPerMeterY((int)dpmy);
const qint32 dpmx = dpi2ppm(xcf_image.x_resolution);
if (dpmx > 0) {
image.setDotsPerMeterX(dpmx);
}
const qint32 dpmy = dpi2ppm(xcf_image.y_resolution);
if (dpmy > 0) {
image.setDotsPerMeterY(dpmy);
}
return true;
}
@@ -3198,7 +3210,7 @@ void XCFImageFormat::mergeLayerIntoImage(XCFImage &xcf_image)
for (uint i = 0; i < layer.ncols; i++) {
qint32 x = qint32(i * TILE_WIDTH);
QImage &tile = layer.image_tiles[j][i];
const QImage &tile = layer.image_tiles[j][i];
if (x + layer.x_offset < XCF_MAX_IMAGE_WIDTH && y + layer.y_offset < XCF_MAX_IMAGE_HEIGHT) {
painter.drawImage(x + layer.x_offset, y + layer.y_offset, tile);
}
@@ -4235,7 +4247,9 @@ bool XCFHandler::canRead(QIODevice *device)
bool failed = !XCFImageFormat::readXCFHeader(ds, &header);
ds.setDevice(nullptr);
device->seek(oldPos);
if (!device->seek(oldPos)) {
return false;
}
if (failed) {
return false;
}