Compare commits

..
20 Commits
Author SHA1 Message Date
Mirco Miranda 5e5072d590 EXR: store of "adobe_rsrc" attribute with XMP/EXIF/Resolution data 2026-09-10 16:44:54 +02:00
Mirco MirandaandMirco Miranda 6a3c7f2c86 EXR: support for metadata saved by Photoshop 2026-09-10 07:36:10 +02:00
Albert Astals Cid 43e6efb0e5 ANIHandler::nextImageDelay: Protect against overflow
oss-fuzz:
 issue #558190560
 testcase #6629308500869120
2026-09-09 18:36:51 +00:00
Daniel Novomeský b7a407a752 autotests: add AVIF image-grid image to read test 2026-09-08 16:43:31 +02:00
Daniel Novomeský 62c45582df ossfuzz: update libaom to 3.15.0 2026-09-08 14:20:50 +02:00
Mirco MirandaandMirco Miranda a738553dc9 EXR: fix alpha channel imperpretation 2026-09-04 18:40:44 +02:00
Mirco MirandaandMirco Miranda 0862db49b8 EXR: RGB / YC subtype support 2026-09-04 18:40:44 +02:00
Nicolas Fella 05af59e819 Update version to 6.31.0 2026-09-04 14:36:24 +02:00
Nicolas Fella 5d6dfb9012 Update dependency version to 6.30.0 2026-09-04 13:26:17 +02:00
Albert Astals Cid 72518dcc1a jp2: Fix new[] vs delete by not doing new[]
unique_ptr will call delete which is not the right thing to do with
memory allocated with new[]

BUGS: 525120
2026-09-03 00:13:53 +02:00
Mirco MirandaandAlbert Astals Cid a6027264d4 qRoundOrZero_T: fix possible assert in qRound
also:
- qRoundOrZero_T: added documentation
- EXR: I don't set the ISO Speed ​​if it's zero.

BUG: 524678
2026-08-26 22:45:12 +00:00
Daniel Novomeský 47ea774621 autotests: Add non-standard HEIF image 2026-08-24 18:49:54 +02:00
Daniel Novomeský 5ed0affc95 heif: re-enable decoding of non-standard images
BUG: 495686

IMG_20241019_195232.heic stopped decoding recently because
buffer.contains("Xiaomi") is almost all the time false
(only start of the file is stored now),
except sequential devices (full file is stored).
2026-08-24 12:02:58 +02:00
Daniel Novomeský 42e9733060 Add HEIC test files with crop transformation 2026-08-20 22:21:48 +02:00
Daniel Novomeský bae45287ad heif: check crop values 2026-08-20 19:20:10 +02:00
Daniel Novomeský 458257b7a3 jxl: Do not rewind after reading final frame 2026-08-18 08:48:40 +00:00
Aljo Joby 09452b0964 rgb: reject RLE start offsets that underflow the raster data
The start table is stored as file offsets. We subtract the
header+table size to get an index into the remaining data.
If the file offset is smaller than that, the subtract wraps
and the later start+length check can wrap too.

Drop the file instead of walking off the buffer.
2026-08-14 21:03:38 +05:30
Daniel Novomeský c7baa057a6 avif: Do not rewind in jumpToNextImage() 2026-08-10 20:10:46 +02:00
Daniel Novomeský c57af25d36 ossfuzz: call functions used in animations 2026-08-10 17:46:33 +00:00
Nicolas Fella 320ded493a Update version to 6.30.0 2026-08-07 23:07:47 +02:00
64 changed files with 820 additions and 284 deletions
+3 -3
View File
@@ -1,11 +1,11 @@
cmake_minimum_required(VERSION 3.29) cmake_minimum_required(VERSION 3.29)
set(KF_VERSION "6.29.0") # handled by release scripts set(KF_VERSION "6.31.0") # handled by release scripts
set(KF_DEP_VERSION "6.29.0") # handled by release scripts set(KF_DEP_VERSION "6.30.0") # handled by release scripts
project(KImageFormats VERSION ${KF_VERSION}) project(KImageFormats VERSION ${KF_VERSION})
include(FeatureSummary) include(FeatureSummary)
find_package(ECM 6.29.0 NO_MODULE) find_package(ECM 6.30.0 NO_MODULE)
set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://commits.kde.org/extra-cmake-modules") set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://commits.kde.org/extra-cmake-modules")
feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES) feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES)
+22 -3
View File
@@ -384,14 +384,33 @@ The following defines can be defined in cmake to modify the behavior of the
plugin: plugin:
- `EXR_CONVERT_TO_SRGB`: the linear data is converted to sRGB on read to - `EXR_CONVERT_TO_SRGB`: the linear data is converted to sRGB on read to
accommodate programs that do not support color profiles. accommodate programs that do not support color profiles.
- `EXR_DISABLE_XMP_ATTRIBUTE`: disables the stores XMP values in a non-standard - `EXR_DISABLE_XMP_ATTRIBUTE`: disables the storage of XMP values in a
attribute named "xmp". Note that Gimp reads the "xmp" attribute and Darktable non-standard attribute named "xmp". Note that Gimp reads the "xmp" attribute
writes it as well. and Darktable writes it as well.
- `EXR_DISABLE_ADOBE_ATTRIBUTE`: disables the storage of non-standard
attribute "adobe_rsrc". Note that Photoshop uses "adobe_rsrc" attribute to
read and write XMP/EXIF/IPTC data.
The plugin can set the following additional metadata: The plugin can set the following additional metadata:
- `EXRLayerName`: A string containing the name of the EXR layer used to decode - `EXRLayerName`: A string containing the name of the EXR layer used to decode
the image. the image.
When writing, it is possible to specify compression ratio and quality. The
values you can set depend on the version of the OpenEXR library you're using.
Please check the [exr_p.h](./src/imageformats/exr_p.h) for more
info.
When writing, it is also possible to specify a subtype:
- `RGB` (default): preserves full-resolution color data (R, G, B) without
subsampling or loss of chromatic detail.
- `YC`: converts RGB data into luminance (Y) and 2x2 subsampled chroma
(RY, BY), cutting raw color data by ~50% for significantly smaller file
sizes. **If the image does not have a height and width that are multiples
of 2, `RGB` is used.**
> [!note]
> When writing grayscale images, the subtype makes no difference.
### The EPS plugin ### The EPS plugin
The plugin uses `Ghostscript` to convert the raster image. When reading it The plugin uses `Ghostscript` to convert the raster image. When reading it
+2 -2
View File
@@ -101,7 +101,7 @@ cd $SRC/aom
mkdir build.libavif mkdir build.libavif
cd build.libavif cd build.libavif
extra_libaom_flags='-DAOM_MAX_ALLOCABLE_MEMORY=536870912 -DDO_RANGE_CHECK_CLAMP=1' extra_libaom_flags='-DAOM_MAX_ALLOCABLE_MEMORY=536870912 -DDO_RANGE_CHECK_CLAMP=1'
cmake -G Ninja -DBUILD_SHARED_LIBS=0 -DENABLE_DOCS=0 -DENABLE_EXAMPLES=0 -DENABLE_TESTDATA=0 -DENABLE_TESTS=0 -DENABLE_TOOLS=0 -DCONFIG_PIC=1 -DAOM_TARGET_CPU=generic -DCONFIG_SIZE_LIMIT=1 -DDECODE_HEIGHT_LIMIT=12288 -DDECODE_WIDTH_LIMIT=12288 -DAOM_EXTRA_C_FLAGS="${extra_libaom_flags}" -DAOM_EXTRA_CXX_FLAGS="${extra_libaom_flags}" .. cmake -G Ninja -DBUILD_SHARED_LIBS=0 -DENABLE_APPS=0 -DENABLE_DOCS=0 -DENABLE_EXAMPLES=0 -DENABLE_TESTDATA=0 -DENABLE_TESTS=0 -DENABLE_TOOLS=0 -DCONFIG_PIC=1 -DAOM_TARGET_CPU=generic -DCONFIG_SIZE_LIMIT=1 -DDECODE_HEIGHT_LIMIT=12288 -DDECODE_WIDTH_LIMIT=12288 -DAOM_EXTRA_C_FLAGS="${extra_libaom_flags}" -DAOM_EXTRA_CXX_FLAGS="${extra_libaom_flags}" ..
ninja install ninja install
# Build libavif # Build libavif
@@ -186,7 +186,7 @@ echo "$HANDLER_TYPES" | while read format; do
/usr/libexec/moc $SRC/kimageformats/src/imageformats/$format.cpp -o $format.moc /usr/libexec/moc $SRC/kimageformats/src/imageformats/$format.cpp -o $format.moc
header=`ls $SRC/kimageformats/src/imageformats/$format*.h` header=`ls $SRC/kimageformats/src/imageformats/$format*.h`
/usr/libexec/moc $header -o moc_`basename $header .h`.cpp /usr/libexec/moc $header -o moc_`basename $header .h`.cpp
$CXX $CXXFLAGS $LDFLAGS -fPIC -DKIMG_FUZZER_${format}=1 -std=c++17 autotests/ossfuzz/kimgio_fuzzer.cc $SRC/kimageformats/src/imageformats/$format.cpp $SRC/kimageformats/src/imageformats/scanlineconverter.cpp $SRC/kimageformats/src/imageformats/microexif.cpp $SRC/kimageformats/src/imageformats/chunks.cpp -o $OUT/$fuzz_target_name -DJXL_STATIC_DEFINE -DJXL_THREADS_STATIC_DEFINE -DJXL_CMS_STATIC_DEFINE -D__ANSI__ -I $SRC/kimageformats/src/imageformats/ -I $SRC/libavif/include/ -I $SRC/libjxl/build/lib/include/ -I $SRC/libjxl/lib/include/ -I /usr/local/include/OpenEXR/ -I /usr/local/include/KF6/KArchive/ -I /usr/local/include/openjpeg-2.5 -I /usr/local/include/Imath -I $SRC/jxrlib/common/include -I $SRC/jxrlib/jxrgluelib -I $SRC/jxrlib/image/sys -I /usr/include/QtCore/ -I /usr/include/QtGui/ -I . $SRC/libavif/build/libavif.a /usr/local/lib/libheif.a /usr/local/lib/libde265.a /usr/local/lib/libopenh264.a $SRC/aom/build.libavif/libaom.a $SRC/libjxl/build/lib/libjxl_threads.a $SRC/libjxl/build/lib/libjxl.a $SRC/libjxl/build/lib/libjxl_cms.a $SRC/libjxl/build/third_party/highway/libhwy.a $SRC/libjxl/build/third_party/brotli/libbrotlidec.a $SRC/libjxl/build/third_party/brotli/libbrotlienc.a $SRC/libjxl/build/third_party/brotli/libbrotlicommon.a -lQt6Gui -lQt6Core -lQt6BundledLibpng -lQt6BundledHarfbuzz -lm -lQt6BundledPcre2 -ldl -lpthread $LIB_FUZZING_ENGINE /usr/local/lib/libz.a /usr/local/lib/x86_64-linux-gnu/libKF6Archive.a /usr/local/lib/libz.a /usr/local/lib/libraw.a /usr/local/lib/libOpenEXR-3_4.a /usr/local/lib/libIex-3_4.a /usr/local/lib/libImath-3_2.a /usr/local/lib/libIlmThread-3_4.a /usr/local/lib/libOpenEXRCore-3_4.a /usr/local/lib/libOpenEXRUtil-3_4.a /usr/local/lib/libopenjp2.a /usr/local/lib/libzstd.a $SRC/jxrlib/build/libjxrglue.a $SRC/jxrlib/build/libjpegxr.a /usr/local/lib/liblzma.a /usr/local/lib/libbz2.a -lclang_rt.builtins $CXX $CXXFLAGS $LDFLAGS -fPIC -DKIMG_FUZZER_${format}=1 -std=c++17 autotests/ossfuzz/kimgio_fuzzer.cc $SRC/kimageformats/src/imageformats/$format.cpp $SRC/kimageformats/src/imageformats/scanlineconverter.cpp $SRC/kimageformats/src/imageformats/photoshop.cpp $SRC/kimageformats/src/imageformats/microexif.cpp $SRC/kimageformats/src/imageformats/chunks.cpp -o $OUT/$fuzz_target_name -DJXL_STATIC_DEFINE -DJXL_THREADS_STATIC_DEFINE -DJXL_CMS_STATIC_DEFINE -D__ANSI__ -I $SRC/kimageformats/src/imageformats/ -I $SRC/libavif/include/ -I $SRC/libjxl/build/lib/include/ -I $SRC/libjxl/lib/include/ -I /usr/local/include/OpenEXR/ -I /usr/local/include/KF6/KArchive/ -I /usr/local/include/openjpeg-2.5 -I /usr/local/include/Imath -I $SRC/jxrlib/common/include -I $SRC/jxrlib/jxrgluelib -I $SRC/jxrlib/image/sys -I /usr/include/QtCore/ -I /usr/include/QtGui/ -I . $SRC/libavif/build/libavif.a /usr/local/lib/libheif.a /usr/local/lib/libde265.a /usr/local/lib/libopenh264.a $SRC/aom/build.libavif/libaom.a $SRC/libjxl/build/lib/libjxl_threads.a $SRC/libjxl/build/lib/libjxl.a $SRC/libjxl/build/lib/libjxl_cms.a $SRC/libjxl/build/third_party/highway/libhwy.a $SRC/libjxl/build/third_party/brotli/libbrotlidec.a $SRC/libjxl/build/third_party/brotli/libbrotlienc.a $SRC/libjxl/build/third_party/brotli/libbrotlicommon.a -lQt6Gui -lQt6Core -lQt6BundledLibpng -lQt6BundledHarfbuzz -lm -lQt6BundledPcre2 -ldl -lpthread $LIB_FUZZING_ENGINE /usr/local/lib/libz.a /usr/local/lib/x86_64-linux-gnu/libKF6Archive.a /usr/local/lib/libz.a /usr/local/lib/libraw.a /usr/local/lib/libOpenEXR-3_4.a /usr/local/lib/libIex-3_4.a /usr/local/lib/libImath-3_2.a /usr/local/lib/libIlmThread-3_4.a /usr/local/lib/libOpenEXRCore-3_4.a /usr/local/lib/libOpenEXRUtil-3_4.a /usr/local/lib/libopenjp2.a /usr/local/lib/libzstd.a $SRC/jxrlib/build/libjxrglue.a $SRC/jxrlib/build/libjpegxr.a /usr/local/lib/liblzma.a /usr/local/lib/libbz2.a -lclang_rt.builtins
# -lclang_rt.builtins in the previous line is a temporary workaround to avoid a linker error "undefined reference to __truncsfhf2". Investigate why this is needed here, but not anywhere else, and possibly remove it. # -lclang_rt.builtins in the previous line is a temporary workaround to avoid a linker error "undefined reference to __truncsfhf2". Investigate why this is needed here, but not anywhere else, and possibly remove it.
+21 -1
View File
@@ -125,7 +125,27 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
b.open(QIODevice::ReadOnly); b.open(QIODevice::ReadOnly);
handler->setDevice(&b); handler->setDevice(&b);
handler->canRead(); handler->canRead();
handler->read(&i);
bool is_animated = false;
if (handler->supportsOption(QImageIOHandler::Animation)) {
is_animated = handler->option(QImageIOHandler::Animation).toBool();
}
if (is_animated) { // animated image
if (handler->supportsOption(QImageIOHandler::Size)) {
handler->option(QImageIOHandler::Size).toSize();
}
handler->imageCount();
handler->loopCount();
handler->read(&i);
handler->nextImageDelay();
handler->currentImageNumber();
handler->jumpToImage(0);
handler->jumpToNextImage();
} else { // static image
handler->read(&i);
}
delete handler; delete handler;
+1 -1
View File
@@ -33,7 +33,7 @@ git clone --depth 1 -b master https://invent.kde.org/frameworks/extra-cmake-modu
git clone --depth 1 --branch=dev git://code.qt.io/qt/qtbase.git git clone --depth 1 --branch=dev git://code.qt.io/qt/qtbase.git
git clone --depth 1 --branch=dev git://code.qt.io/qt/qttools.git git clone --depth 1 --branch=dev git://code.qt.io/qt/qttools.git
git clone --depth 1 -b master https://invent.kde.org/frameworks/karchive.git git clone --depth 1 -b master https://invent.kde.org/frameworks/karchive.git
git clone --depth 1 -b v3.14.1 https://aomedia.googlesource.com/aom git clone --depth 1 -b v3.15.0 https://aomedia.googlesource.com/aom
git clone --depth 1 -b v1.4.2 https://github.com/AOMediaCodec/libavif.git git clone --depth 1 -b v1.4.2 https://github.com/AOMediaCodec/libavif.git
git clone --depth 1 https://github.com/strukturag/libde265.git git clone --depth 1 https://github.com/strukturag/libde265.git
git clone --depth 1 -b v2.5.4 https://github.com/uclouvain/openjpeg.git git clone --depth 1 -b v2.5.4 https://github.com/uclouvain/openjpeg.git
Binary file not shown.

After

Width:  |  Height:  |  Size: 255 KiB

+7
View File
@@ -0,0 +1,7 @@
[
{
"fileName": "grid.png",
"fuzziness": 2,
"description": "Minimum fuzziness value to pass the test on all architectures."
}
]
Binary file not shown.

After

Width:  |  Height:  |  Size: 277 KiB

Binary file not shown.
@@ -0,0 +1,33 @@
[
{
"fileName" : "alphatest_rgba.tif",
"colorSpace" : {
"description" : "sRGB build-in (Profilo RGB lineare)",
"primaries" : "SRgb",
"transferFunction" : "Linear",
"gamma" : 1
},
"metadata" : [
{
"key" : "ModificationDate",
"value" : "2026-09-04T11:45:06"
},
{
"key" : "Software" ,
"value" : "Adobe Photoshop 27.10 (Windows)"
},
{
"key" : "Author",
"value" : "Mirco Miranda"
},
{
"key" : "Description",
"value" : "This test is designed to detect problems with alpha encoding. Specifically, it detects incorrect use of associated values (premultiplied alpha) and unassociated values (normal alpha)."
}
],
"resolution" : {
"dotsPerMeterX" : 11811,
"dotsPerMeterY" : 11811
}
}
]
Binary file not shown.
@@ -7,9 +7,19 @@
"transferFunction" : "Linear", "transferFunction" : "Linear",
"gamma" : 1 "gamma" : 1
}, },
"metadata" : [
{
"key" : "ModificationDate",
"value" : "2026-04-15T10:48:48"
},
{
"key" : "Software" ,
"value" : "Adobe Photoshop 27.5 (Windows)"
}
],
"resolution" : { "resolution" : {
"dotsPerMeterX" : 3937, "dotsPerMeterX" : 11811,
"dotsPerMeterY" : 3937 "dotsPerMeterY" : 11811
} }
} }
] ]
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 199 B

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 242 B

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 234 B

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 232 B

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 221 B

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 222 B

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 243 B

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 228 B

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 244 B

Binary file not shown.
+7
View File
@@ -0,0 +1,7 @@
[
{
"fileName": "konqi.jpg",
"fuzziness": 72,
"description": "Minimum fuzziness value to pass the test on all architectures."
}
]
Binary file not shown.

After

Width:  |  Height:  |  Size: 362 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+2 -2
View File
@@ -57,7 +57,7 @@ endif()
################################## ##################################
if(OpenEXR_FOUND) if(OpenEXR_FOUND)
kimageformats_add_plugin(kimg_exr SOURCES exr.cpp scanlineconverter.cpp) kimageformats_add_plugin(kimg_exr SOURCES exr.cpp microexif.cpp photoshop.cpp scanlineconverter.cpp)
if(TARGET OpenEXR::OpenEXR) if(TARGET OpenEXR::OpenEXR)
target_link_libraries(kimg_exr PRIVATE OpenEXR::OpenEXR) target_link_libraries(kimg_exr PRIVATE OpenEXR::OpenEXR)
else() else()
@@ -117,7 +117,7 @@ kimageformats_add_plugin(kimg_pfm SOURCES pfm.cpp)
################################## ##################################
kimageformats_add_plugin(kimg_psd SOURCES psd.cpp microexif.cpp scanlineconverter.cpp) kimageformats_add_plugin(kimg_psd SOURCES psd.cpp microexif.cpp photoshop.cpp scanlineconverter.cpp)
################################## ##################################
+12 -1
View File
@@ -12,6 +12,7 @@
#include <QScopeGuard> #include <QScopeGuard>
#include <QVariant> #include <QVariant>
#include <QtEndian> #include <QtEndian>
#include <QtNumeric>
#include <cstring> #include <cstring>
@@ -267,7 +268,17 @@ int ANIHandler::nextImageDelay() const
rate = m_displayRates.at(previousImage); rate = m_displayRates.at(previousImage);
} }
return rate * 1000 / 60; int delayNumerator;
if (qMulOverflow(rate, 1000, &delayNumerator)) {
int delay;
// We're losing a bit of precision by dividing first and multiplying later
// but this is most probably a broken file so not much of an issue
if (qMulOverflow(rate / 60, 1000, &delay)) {
return 0;
}
return delay;
}
return delayNumerator / 60;
} }
bool ANIHandler::supportsOption(ImageOption option) const bool ANIHandler::supportsOption(ImageOption option) const
+5 -11
View File
@@ -1160,25 +1160,19 @@ bool QAVIFHandler::jumpToNextImage()
return false; return false;
} }
avifResult decodeResult;
if (m_decoder->imageIndex >= 0) { if (m_decoder->imageIndex >= 0) {
if (m_decoder->imageCount < 2) { if (m_decoder->imageCount < 2) {
m_parseState = ParseAvifSuccess; // single image, there is no next image
return false; return false;
} }
if (m_decoder->imageIndex >= m_decoder->imageCount - 1) { // start from beginning if (m_decoder->imageIndex >= m_decoder->imageCount - 1) {
decodeResult = avifDecoderReset(m_decoder); // do not start from beginning when at the end already
if (decodeResult != AVIF_RESULT_OK) { return false;
qCWarning(LOG_AVIFPLUGIN, "ERROR in avifDecoderReset: %s", avifResultToString(decodeResult));
m_parseState = ParseAvifError;
return false;
}
} }
} }
decodeResult = avifDecoderNextImage(m_decoder); avifResult decodeResult = avifDecoderNextImage(m_decoder);
if (decodeResult != AVIF_RESULT_OK) { if (decodeResult != AVIF_RESULT_OK) {
qCWarning(LOG_AVIFPLUGIN, "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));
+172 -12
View File
@@ -8,6 +8,8 @@
*/ */
#include "exr_p.h" #include "exr_p.h"
#include "microexif_p.h"
#include "photoshop_p.h"
#include "scanlineconverter_p.h" #include "scanlineconverter_p.h"
#include "util_p.h" #include "util_p.h"
@@ -19,8 +21,8 @@
*/ */
//#define EXR_CONVERT_TO_SRGB // default: commented -> you should define it in your cmake file //#define EXR_CONVERT_TO_SRGB // default: commented -> you should define it in your cmake file
/* *** EXR_STORE_XMP_ATTRIBUTE *** /* *** EXR_DISABLE_XMP_ATTRIBUTE ***
* If defined, disables the stores XMP values in a non-standard attribute named "xmp". * If defined, disables the storage of XMP values in the non-standard attribute named "xmp"
* The QImage metadata used is "XML:com.adobe.xmp". * The QImage metadata used is "XML:com.adobe.xmp".
* NOTE: The use of non-standard attributes is possible but discouraged by the specification. However, * NOTE: The use of non-standard attributes is possible but discouraged by the specification. However,
* metadata is essential for good image management and programs like darktable also set this * metadata is essential for good image management and programs like darktable also set this
@@ -28,6 +30,15 @@
*/ */
//#define EXR_DISABLE_XMP_ATTRIBUTE // default: commented -> you should define it in your cmake file //#define EXR_DISABLE_XMP_ATTRIBUTE // default: commented -> you should define it in your cmake file
/* *** EXR_DISABLE_ADOBE_ATTRIBUTE ***
* If defined, disables the storage of non-standard attribute "adobe_rsrc".
* NOTE: The use of non-standard attributes is possible but discouraged by the specification. However,
* metadata is essential for good image management and programs like Photoshop also set this
* attribute. Photoshop uses "adobe_rsrc" attribute to read/write XMP/EXIF/IPTC data.
*/
//#define EXR_DISABLE_ADOBE_ATTRIBUTE // default: commented -> you should define it in your cmake file
/* *** EXR_MAX_IMAGE_WIDTH and EXR_MAX_IMAGE_HEIGHT *** /* *** EXR_MAX_IMAGE_WIDTH and EXR_MAX_IMAGE_HEIGHT ***
* The maximum size in pixel allowed by the plugin. * The maximum size in pixel allowed by the plugin.
*/ */
@@ -90,6 +101,12 @@ Q_LOGGING_CATEGORY(LOG_EXRPLUGIN, "kf.imageformats.plugins.exr", QtDebugMsg)
Q_LOGGING_CATEGORY(LOG_EXRPLUGIN, "kf.imageformats.plugins.exr", QtWarningMsg) Q_LOGGING_CATEGORY(LOG_EXRPLUGIN, "kf.imageformats.plugins.exr", QtWarningMsg)
#endif #endif
#ifndef EXR_DISABLE_ADOBE_ATTRIBUTE
#if QT_VERSION_CHECK(OPENEXR_VERSION_MAJOR, OPENEXR_VERSION_MINOR, OPENEXR_VERSION_PATCH) < QT_VERSION_CHECK(3, 3, 0)
#define EXR_DISABLE_ADOBE_ATTRIBUTE
#endif
#endif
class K_IStream : public Imf::IStream class K_IStream : public Imf::IStream
{ {
public: public:
@@ -200,12 +217,18 @@ void K_OStream::seekg(Imf::Int64 pos)
m_dev->seek(pos); m_dev->seek(pos);
} }
#define EXR_SUBFORMAT_RGB QByteArray("RGB")
#define EXR_SUBFORMAT_YC QByteArray("YC")
EXRHandler::EXRHandler() EXRHandler::EXRHandler()
: m_compressionRatio(-1) : m_compressionRatio(-1)
, m_quality(-1) , m_quality(-1)
, m_imageNumber(0) , m_imageNumber(0)
, m_imageCount(0) , m_imageCount(0)
, m_startPos(-1) , m_startPos(-1)
, m_subType(EXR_SUBFORMAT_RGB)
, m_transformation(QImageIOHandler::TransformationNone)
{ {
// Set the number of threads to use (0 is allowed) // Set the number of threads to use (0 is allowed)
Imf::setGlobalThreadCount(QThread::idealThreadCount() / 2); Imf::setGlobalThreadCount(QThread::idealThreadCount() / 2);
@@ -225,7 +248,7 @@ bool EXRHandler::canRead() const
static QImage::Format imageFormat(const Imf::RgbaInputFile &file) static QImage::Format imageFormat(const Imf::RgbaInputFile &file)
{ {
auto isRgba = file.channels() & Imf::RgbaChannels::WRITE_A; auto isRgba = file.channels() & Imf::RgbaChannels::WRITE_A;
return (isRgba ? QImage::Format_RGBA16FPx4 : QImage::Format_RGBX16FPx4); return (isRgba ? QImage::Format_RGBA16FPx4_Premultiplied : QImage::Format_RGBX16FPx4);
} }
/*! /*!
@@ -285,6 +308,27 @@ static void printAttributes(const Imf::Header &h)
} }
#endif #endif
static PSDImageResourceSection readPSDImageResourceSection(const Imf::Header &header)
{
if (auto adobe = header.findTypedAttribute<Imf::OpaqueAttribute>("adobe_rsrc")) {
auto &&data = adobe->data();
auto ba = QByteArray(data, data.size());
ba.prepend((data.size()) & 0xFF);
ba.prepend((data.size() >> 8) & 0xFF);
ba.prepend((data.size() >> 16) & 0xFF);
ba.prepend((data.size() >> 24) & 0xFF);
QDataStream ds(ba);
ds.setByteOrder(QDataStream::BigEndian);
auto ok = false;
auto irs = readImageResourceSection(ds, &ok);
if (ok) {
return irs;
}
}
return{};
}
/*! /*!
* \brief readMetadata * \brief readMetadata
* Reads EXR attributes from the \a header and set its as metadata in the \a image. * Reads EXR attributes from the \a header and set its as metadata in the \a image.
@@ -368,7 +412,8 @@ static void readMetadata(const Imf::Header &header, QImage &image)
// shot metadata // shot metadata
if (auto isoSpeed = header.findTypedAttribute<Imf::FloatAttribute>("isoSpeed")) { if (auto isoSpeed = header.findTypedAttribute<Imf::FloatAttribute>("isoSpeed")) {
image.setText(QStringLiteral(META_KEY_ISOSPEEDRATINGS), QLocale::c().toString(qRound(isoSpeed->value()))); if (auto v = qRoundOrZero(isoSpeed->value()))
image.setText(QStringLiteral(META_KEY_ISOSPEEDRATINGS), QLocale::c().toString(v));
} }
if (auto expTime = header.findTypedAttribute<Imf::FloatAttribute>("expTime")) { if (auto expTime = header.findTypedAttribute<Imf::FloatAttribute>("expTime")) {
image.setText(QStringLiteral(META_KEY_EXPOSURETIME), QLocale::c().toString(expTime->value())); image.setText(QStringLiteral(META_KEY_EXPOSURETIME), QLocale::c().toString(expTime->value()));
@@ -379,6 +424,20 @@ static void readMetadata(const Imf::Header &header, QImage &image)
if (auto focalLen = header.findTypedAttribute<Imf::FloatAttribute>("effectiveFocalLength")) { if (auto focalLen = header.findTypedAttribute<Imf::FloatAttribute>("effectiveFocalLength")) {
image.setText(QStringLiteral(META_KEY_FOCALLENGTH), QLocale::c().toString(focalLen->value())); image.setText(QStringLiteral(META_KEY_FOCALLENGTH), QLocale::c().toString(focalLen->value()));
} }
// Photoshop image resource section
auto irs = readPSDImageResourceSection(header);
if (irs.contains(IRI_EXIFDATA1)) {
auto exif = MicroExif::fromByteArray(irs.value(IRI_EXIFDATA1).data);
exif.updateImageMetadata(image);
exif.updateImageResolution(image);
}
if (irs.contains(IRI_XMPMETADATA)) {
auto irb = irs.value(IRI_XMPMETADATA);
auto xmp = QString::fromUtf8(irb.data);
if (!xmp.isEmpty())
image.setText(QStringLiteral(META_KEY_XMP_ADOBE), xmp);
}
} }
/*! /*!
@@ -513,9 +572,9 @@ bool makePreview(const QImage &image, Imf::Array2D<Imf::PreviewRgba> &pixels)
QImage preview; QImage preview;
if (w > h) { if (w > h) {
preview = image.scaledToWidth(256).convertToFormat(QImage::Format_ARGB32); preview = image.scaledToWidth(256).convertToFormat(QImage::Format_ARGB32_Premultiplied);
} else { } else {
preview = image.scaledToHeight(256).convertToFormat(QImage::Format_ARGB32); preview = image.scaledToHeight(256).convertToFormat(QImage::Format_ARGB32_Premultiplied);
} }
if (preview.isNull()) { if (preview.isNull()) {
return false; return false;
@@ -544,8 +603,11 @@ bool makePreview(const QImage &image, Imf::Array2D<Imf::PreviewRgba> &pixels)
* \brief setMetadata * \brief setMetadata
* Reades the metadata from \a image and set its as attributes in the \a header. * Reades the metadata from \a image and set its as attributes in the \a header.
*/ */
static void setMetadata(const QImage &image, Imf::Header &header) static void setMetadata(const QImage &image, Imf::Header &header, const QImageIOHandler::Transformation &transformation)
{ {
PSDImageResourceSection irs;
QString xmpData;
auto dateTime = QDateTime::currentDateTime(); auto dateTime = QDateTime::currentDateTime();
for (auto &&key : image.textKeys()) { for (auto &&key : image.textKeys()) {
auto text = image.text(key); auto text = image.text(key);
@@ -576,11 +638,9 @@ static void setMetadata(const QImage &image, Imf::Header &header)
} }
} }
#ifndef EXR_DISABLE_XMP_ATTRIBUTE // warning: Non-standard attribute!
if (!key.compare(QStringLiteral(META_KEY_XMP_ADOBE), Qt::CaseInsensitive)) { if (!key.compare(QStringLiteral(META_KEY_XMP_ADOBE), Qt::CaseInsensitive)) {
header.insert("xmp", Imf::StringAttribute(text.toStdString())); xmpData = text;
} }
#endif
if (!key.compare(QStringLiteral(META_KEY_MANUFACTURER), Qt::CaseInsensitive)) { if (!key.compare(QStringLiteral(META_KEY_MANUFACTURER), Qt::CaseInsensitive)) {
header.insert("cameraMake", Imf::StringAttribute(text.toStdString())); header.insert("cameraMake", Imf::StringAttribute(text.toStdString()));
@@ -640,6 +700,41 @@ static void setMetadata(const QImage &image, Imf::Header &header)
header.insert("pixelAspectRatio", Imf::FloatAttribute(float(image.dotsPerMeterY()) / float(image.dotsPerMeterX()))); header.insert("pixelAspectRatio", Imf::FloatAttribute(float(image.dotsPerMeterY()) / float(image.dotsPerMeterX())));
} }
#ifndef EXR_DISABLE_XMP_ATTRIBUTE
if (!xmpData.isEmpty()) {
header.insert("xmp", Imf::StringAttribute(xmpData.toStdString()));
}
#endif
#ifndef EXR_DISABLE_ADOBE_ATTRIBUTE
auto resInfo = PSDResolutionInfoBlock::fromImage(image);
if (resInfo.isValid()) {
PSDImageResourceBlock irb;
irb.data = resInfo.toByteArray();
if (!irb.data.isEmpty())
irs.insert(IRI_RESOLUTIONINFO, irb);
}
auto exif = MicroExif::fromImage(image);
if (!exif.isEmpty()) {
exif.setTransformation(transformation);
PSDImageResourceBlock irb;
irb.data = exif.toByteArray(QDataStream::BigEndian);
irs.insert(IRI_EXIFDATA1, irb);
}
if (!xmpData.isEmpty()) {
PSDImageResourceBlock irb;
irb.data = xmpData.toUtf8();
irs.insert(IRI_XMPMETADATA, irb);
}
if (!irs.isEmpty()) {
bool ok = false;
auto ba = irs.toByteArray(&ok);
if (ok) {
header.insert("adobe_rsrc", Imf::OpaqueAttribute("adobe_rsrc", ba.size(), ba.data()));
}
}
#endif
// set default chroma (default constructor ITU-R BT.709-3 -> sRGB) // set default chroma (default constructor ITU-R BT.709-3 -> sRGB)
// The image is converted to Linear sRGB so, the chroma is the default EXR value. // The image is converted to Linear sRGB so, the chroma is the default EXR value.
// If a file doesnt have a chromaticities attribute, display software should assume that the // If a file doesnt have a chromaticities attribute, display software should assume that the
@@ -682,11 +777,15 @@ bool EXRHandler::write(const QImage &image)
} }
// set metadata (EXR attributes) // set metadata (EXR attributes)
setMetadata(image, header); setMetadata(image, header, m_transformation);
// write the EXR // write the EXR
K_OStream ostr(device()); K_OStream ostr(device());
auto channelsType = image.hasAlphaChannel() ? Imf::RgbaChannels::WRITE_RGBA : Imf::RgbaChannels::WRITE_RGB; auto channelsType = image.hasAlphaChannel() ? Imf::RgbaChannels::WRITE_RGBA : Imf::RgbaChannels::WRITE_RGB;
if (m_subType == EXR_SUBFORMAT_YC && !(width % 2) && !(height % 2)) {
// Works only with images with height and width that are multiples of 2.
channelsType = channelsType == Imf::RgbaChannels::WRITE_RGBA ? Imf::RgbaChannels::WRITE_YCA : Imf::RgbaChannels::WRITE_YC;
}
if (image.format() == QImage::Format_Mono || if (image.format() == QImage::Format_Mono ||
image.format() == QImage::Format_MonoLSB || image.format() == QImage::Format_MonoLSB ||
image.format() == QImage::Format_Grayscale16 || image.format() == QImage::Format_Grayscale16 ||
@@ -698,7 +797,7 @@ bool EXRHandler::write(const QImage &image)
pixels.resizeErase(EXR_LINES_PER_BLOCK, width); pixels.resizeErase(EXR_LINES_PER_BLOCK, width);
// convert the image and write into the stream // convert the image and write into the stream
auto convFormat = image.hasAlphaChannel() ? QImage::Format_RGBA32FPx4 : QImage::Format_RGBX32FPx4; auto convFormat = image.hasAlphaChannel() ? QImage::Format_RGBA32FPx4_Premultiplied : QImage::Format_RGBX32FPx4;
ScanLineConverter slc(convFormat); ScanLineConverter slc(convFormat);
slc.setDefaultSourceColorSpace(QColorSpace(QColorSpace::SRgb)); slc.setDefaultSourceColorSpace(QColorSpace(QColorSpace::SRgb));
slc.setTargetColorSpace(QColorSpace(QColorSpace::SRgbLinear)); slc.setTargetColorSpace(QColorSpace(QColorSpace::SRgbLinear));
@@ -742,6 +841,24 @@ void EXRHandler::setOption(ImageOption option, const QVariant &value)
m_quality = q; m_quality = q;
} }
} }
if (option == QImageIOHandler::SubType) {
auto subType = value.toByteArray();
auto list = EXRHandler::option(QImageIOHandler::SupportedSubTypes).value<QList<QByteArray>>();
if (list.contains(subType)) {
m_subType = subType;
} else {
m_subType = EXR_SUBFORMAT_RGB;
}
}
#ifndef EXR_DISABLE_ADOBE_ATTRIBUTE
if (option == QImageIOHandler::ImageTransformation) {
auto ok = false;
auto t = value.toInt(&ok);
if (ok) {
m_transformation = QImageIOHandler::Transformation(t);
}
}
#endif
} }
bool EXRHandler::supportsOption(ImageOption option) const bool EXRHandler::supportsOption(ImageOption option) const
@@ -760,6 +877,17 @@ bool EXRHandler::supportsOption(ImageOption option) const
if (option == QImageIOHandler::Quality) { if (option == QImageIOHandler::Quality) {
return true; return true;
} }
if (option == QImageIOHandler::SubType) {
return true;
}
if (option == QImageIOHandler::SupportedSubTypes) {
return true;
}
#ifndef EXR_DISABLE_ADOBE_ATTRIBUTE
if (option == QImageIOHandler::ImageTransformation) {
return true;
}
#endif
return false; return false;
} }
@@ -814,6 +942,38 @@ QVariant EXRHandler::option(ImageOption option) const
v = QVariant(m_quality); v = QVariant(m_quality);
} }
if (option == QImageIOHandler::SupportedSubTypes) {
v = QVariant::fromValue(QList<QByteArray>() << EXR_SUBFORMAT_RGB << EXR_SUBFORMAT_YC);
}
if (option == QImageIOHandler::SubType) {
v = QVariant::fromValue(m_subType);
}
#ifndef EXR_DISABLE_ADOBE_ATTRIBUTE
if (option == QImageIOHandler::ImageTransformation) {
if (auto d = device()) {
// transactions works on both random and sequential devices
d->startTransaction();
if (m_startPos > -1) {
d->seek(m_startPos);
}
try {
K_IStream istr(d);
Imf::RgbaInputFile file(istr);
auto irs = readPSDImageResourceSection(file.header());
if (irs.contains(IRI_EXIFDATA1)) {
auto exif = MicroExif::fromByteArray(irs.value(IRI_EXIFDATA1).data);
v = int(exif.transformation());
}
} catch (const std::exception &) {
// broken file or unsupported version
}
d->rollbackTransaction();
}
}
#endif
return v; return v;
} }
+14
View File
@@ -118,6 +118,20 @@ private:
* The initial device position to allow multi image load (cache value). * The initial device position to allow multi image load (cache value).
*/ */
qint64 m_startPos; qint64 m_startPos;
/*!
* \brief m_subType
* The EXR subtype to use when writing:
* - RGB: preserves full-resolution color data (R, G, B) without subsampling or loss of chromatic detail (default).
* - YC: converts RGB data into luminance (Y) and 2x2 subsampled chroma (RY, BY), cutting raw color data by ~50% for significantly smaller file sizes.
*/
QByteArray m_subType;
/*!
* \brief m_transformation
* The Qt image transformation.
*/
QImageIOHandler::Transformation m_transformation;
}; };
class EXRPlugin : public QImageIOPlugin class EXRPlugin : public QImageIOPlugin
+25 -4
View File
@@ -642,10 +642,31 @@ bool HEIFHandler::read_crop(void *heif_handle, const void *heif_ctx, const QSize
if (heif_item_get_properties_of_type(ctx, item_id, heif_item_property_type_transform_crop, &crop_id, 1) > 0) { if (heif_item_get_properties_of_type(ctx, item_id, heif_item_property_type_transform_crop, &crop_id, 1) > 0) {
int l = 0, t = 0, r = 0, b = 0; int l = 0, t = 0, r = 0, b = 0;
heif_item_get_property_transform_crop_borders(ctx, item_id, crop_id, size.width(), size.height(), &l, &t, &r, &b); heif_item_get_property_transform_crop_borders(ctx, item_id, crop_id, size.width(), size.height(), &l, &t, &r, &b);
crop = QRect(QPoint(t, l), size - QSize(b + t, r + l));
if (l >= size.width() || r >= size.width() || t >= size.height() || b >= size.height()) {
qCWarning(LOG_HEIFPLUGIN) << "Invalid crop values: left=" << l << "top=" << t << "right=" << r << "bottom=" << b;
}
if (l < 0) {
l = 0;
}
if (t < 0) {
t = 0;
}
if (r < 0) {
r = 0;
}
if (b < 0) {
b = 0;
}
if (l == 0 && t == 0 && r == 0 && b == 0) {
// no cropping needed
return false;
}
crop = QRect(QPoint(l, t), size - QSize(l + r, t + b));
return crop.isValid();
} }
return crop.isValid(); return false;
} }
bool HEIFHandler::isSupportedBMFFType(const QByteArray &header) bool HEIFHandler::isSupportedBMFFType(const QByteArray &header)
@@ -930,8 +951,8 @@ bool HEIFHandler::ensureDecoder()
struct heif_image *img = nullptr; struct heif_image *img = nullptr;
err = heif_decode_image(handle, &img, heif_colorspace_RGB, chroma, decoder_option); err = heif_decode_image(handle, &img, heif_colorspace_RGB, chroma, decoder_option);
if (err.code == heif_error_Invalid_input && err.subcode == heif_suberror_Unknown_NCLX_matrix_coefficients && img == nullptr && buffer.contains("Xiaomi")) { if (err.code == heif_error_Invalid_input && err.subcode == heif_suberror_Unknown_NCLX_matrix_coefficients && img == nullptr) {
qCWarning(LOG_HEIFPLUGIN) << "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!";
// second try to decode with strict decoding disabled // second try to decode with strict decoding disabled
decoder_option->strict_decoding = 0; decoder_option->strict_decoding = 0;
+12 -13
View File
@@ -622,21 +622,20 @@ public:
m_cparameters.tcp_rates[0] = 100.0 - (m_quality < 10 ? m_quality : 10 + (std::log10(m_quality) - 1) * 90); m_cparameters.tcp_rates[0] = 100.0 - (m_quality < 10 ? m_quality : 10 + (std::log10(m_quality) - 1) * 90);
} }
std::unique_ptr<opj_image_cmptparm_t> cmptparm(new opj_image_cmptparm_t[ncomp]); std::vector<opj_image_cmptparm_t> cmptparm(ncomp);
for (int i = 0; i < ncomp; ++i) { for (opj_image_cmptparm_t &p : cmptparm) {
auto &&p = cmptparm.get() + i; memset(&p, 0, sizeof(opj_image_cmptparm_t));
memset(p, 0, sizeof(opj_image_cmptparm_t)); p.dx = m_cparameters.subsampling_dx;
p->dx = m_cparameters.subsampling_dx; p.dy = m_cparameters.subsampling_dy;
p->dy = m_cparameters.subsampling_dy; p.w = image.width();
p->w = image.width(); p.h = image.height();
p->h = image.height(); p.x0 = 0;
p->x0 = 0; p.y0 = 0;
p->y0 = 0; p.prec = prec;
p->prec = prec; p.sgnd = 0;
p->sgnd = 0;
} }
m_jp2_image = opj_image_create(ncomp, cmptparm.get(), cs); m_jp2_image = opj_image_create(ncomp, cmptparm.data(), cs);
if (m_jp2_image == nullptr) { if (m_jp2_image == nullptr) {
return false; return false;
} }
+32 -33
View File
@@ -60,7 +60,7 @@ Q_LOGGING_CATEGORY(LOG_JXLPLUGIN, "kf.imageformats.plugins.jxl", QtWarningMsg)
void *QtJXLMemoryManagerAlloc(void *opaque, size_t size) void *QtJXLMemoryManagerAlloc(void *opaque, size_t size)
{ {
if (opaque) { if (opaque) {
size_t maxBytes = *(size_t*)opaque; size_t maxBytes = *(size_t *)opaque;
if (maxBytes && size > maxBytes) if (maxBytes && size > maxBytes)
return NULL; return NULL;
} }
@@ -75,8 +75,7 @@ void QtJXLMemoryManagerFree(void *, void *address)
QJpegXLHandler::QJpegXLHandler() QJpegXLHandler::QJpegXLHandler()
: m_parseState(ParseJpegXLNotParsed) : m_parseState(ParseJpegXLNotParsed)
, m_quality(90) , m_quality(90)
, m_currentimage_index(0) , m_currentimage_index(-1)
, m_previousimage_index(-1)
, m_transformations(QImageIOHandler::TransformationNone) , m_transformations(QImageIOHandler::TransformationNone)
, m_decoder(nullptr) , m_decoder(nullptr)
, m_runner(nullptr) , m_runner(nullptr)
@@ -183,11 +182,7 @@ bool QJpegXLHandler::ensureDecoder()
} }
// Creating a simple memory manager // Creating a simple memory manager
JxlMemoryManager memory_manager = { JxlMemoryManager memory_manager = {.opaque = &m_maxBytes, .alloc = QtJXLMemoryManagerAlloc, .free = QtJXLMemoryManagerFree};
.opaque = &m_maxBytes,
.alloc = QtJXLMemoryManagerAlloc,
.free = QtJXLMemoryManagerFree
};
// Creating the decoder (it makes a deep copy of memory manager) // Creating the decoder (it makes a deep copy of memory manager)
m_decoder = JxlDecoderCreate(&memory_manager); m_decoder = JxlDecoderCreate(&memory_manager);
if (!m_decoder) { if (!m_decoder) {
@@ -817,17 +812,11 @@ bool QJpegXLHandler::decode_one_frame()
exif.updateImageMetadata(m_current_image); exif.updateImageMetadata(m_current_image);
} }
m_currentimage_index++;
m_next_image_delay = m_framedelays[m_currentimage_index]; m_next_image_delay = m_framedelays[m_currentimage_index];
m_previousimage_index = m_currentimage_index;
if (m_framedelays.count() > 1) { if (m_framedelays.count() > 1) {
m_currentimage_index++; if (m_currentimage_index >= m_framedelays.count() - 1) {
if (m_currentimage_index >= m_framedelays.count()) {
if (!rewind()) {
return false;
}
// all frames in animation have been read // all frames in animation have been read
m_parseState = ParseJpegXLFinished; m_parseState = ParseJpegXLFinished;
} else { } else {
@@ -847,9 +836,9 @@ bool QJpegXLHandler::read(QImage *image)
return false; return false;
} }
if (m_currentimage_index == m_previousimage_index) { if (m_parseState == ParseJpegXLFinished) {
*image = m_current_image; // at the end already
return jumpToNextImage(); return false;
} }
if (decode_one_frame()) { if (decode_one_frame()) {
@@ -1757,19 +1746,26 @@ bool QJpegXLHandler::jumpToNextImage()
} }
if (m_framedelays.count() > 1) { if (m_framedelays.count() > 1) {
m_currentimage_index++; if (m_currentimage_index >= (m_framedelays.count() - 1)) {
// we are already at the last frame
if (m_currentimage_index >= m_framedelays.count()) { return false;
if (!rewind()) {
return false;
}
} else { } else {
m_currentimage_index++;
m_next_image_delay = m_framedelays[m_currentimage_index];
JxlDecoderSkipFrames(m_decoder, 1); JxlDecoderSkipFrames(m_decoder, 1);
if (m_currentimage_index >= (m_framedelays.count() - 1)) {
// last frame reached but no more reading is possible
m_parseState = ParseJpegXLFinished;
} else {
m_parseState = ParseJpegXLSuccess;
}
return true;
} }
} }
m_parseState = ParseJpegXLSuccess; // static image, there is no next frame
return true; return false;
} }
bool QJpegXLHandler::jumpToImage(int imageNumber) bool QJpegXLHandler::jumpToImage(int imageNumber)
@@ -1782,14 +1778,17 @@ bool QJpegXLHandler::jumpToImage(int imageNumber)
return false; return false;
} }
if (imageNumber == m_currentimage_index) { const int next_image_index = m_currentimage_index + 1;
if (imageNumber == next_image_index) {
// This the already the next image to read, no action
m_parseState = ParseJpegXLSuccess; m_parseState = ParseJpegXLSuccess;
return true; return true;
} }
if (imageNumber > m_currentimage_index) { if (imageNumber > next_image_index) {
JxlDecoderSkipFrames(m_decoder, imageNumber - m_currentimage_index); JxlDecoderSkipFrames(m_decoder, imageNumber - next_image_index);
m_currentimage_index = imageNumber; m_currentimage_index = imageNumber - 1;
m_parseState = ParseJpegXLSuccess; m_parseState = ParseJpegXLSuccess;
return true; return true;
} }
@@ -1801,7 +1800,7 @@ bool QJpegXLHandler::jumpToImage(int imageNumber)
if (imageNumber > 0) { if (imageNumber > 0) {
JxlDecoderSkipFrames(m_decoder, imageNumber); JxlDecoderSkipFrames(m_decoder, imageNumber);
} }
m_currentimage_index = imageNumber; m_currentimage_index = imageNumber - 1;
m_parseState = ParseJpegXLSuccess; m_parseState = ParseJpegXLSuccess;
return true; return true;
} }
@@ -1834,7 +1833,7 @@ int QJpegXLHandler::loopCount() const
bool QJpegXLHandler::rewind() bool QJpegXLHandler::rewind()
{ {
m_currentimage_index = 0; m_currentimage_index = -1;
JxlDecoderReleaseInput(m_decoder); JxlDecoderReleaseInput(m_decoder);
JxlDecoderRewind(m_decoder); JxlDecoderRewind(m_decoder);
-1
View File
@@ -65,7 +65,6 @@ private:
ParseJpegXLState m_parseState; ParseJpegXLState m_parseState;
int m_quality; int m_quality;
int m_currentimage_index; int m_currentimage_index;
int m_previousimage_index;
QImageIOHandler::Transformations m_transformations; QImageIOHandler::Transformations m_transformations;
QByteArray m_rawData; QByteArray m_rawData;
+4 -2
View File
@@ -84,6 +84,8 @@
#define EXIF_TAG_SIZEOF(dataType) (quint16(dataType) & 0x3F) #define EXIF_TAG_SIZEOF(dataType) (quint16(dataType) & 0x3F)
#define EXIF_TAG_DATATYPE(dataType) (quint16(dataType) >> 6) #define EXIF_TAG_DATATYPE(dataType) (quint16(dataType) >> 6)
#define GPS_GPSVERSION_VALUE QList<quint8>{0x02, 0x04, 0x00, 0x00}
enum class ExifTagType : quint16 { enum class ExifTagType : quint16 {
// Base data types // Base data types
Byte = EXIF_TAG_VALUE(1, 1), Byte = EXIF_TAG_VALUE(1, 1),
@@ -1325,7 +1327,7 @@ QByteArray MicroExif::gpsIfdByteArray(const QDataStream::ByteOrder &byteOrder, c
QDataStream ds(&ba, QIODevice::WriteOnly); QDataStream ds(&ba, QIODevice::WriteOnly);
ds.setByteOrder(byteOrder); ds.setByteOrder(byteOrder);
auto gpsTags = m_gpsTags; auto gpsTags = m_gpsTags;
gpsTags.insert(GPS_GPSVERSION, QByteArray("2400")); gpsTags.insert(GPS_GPSVERSION, QVariant::fromValue(GPS_GPSVERSION_VALUE));
TagPos positions; TagPos positions;
if (!writeIfd(ds, version, gpsTags, positions, 0, staticGpsTagTypes)) if (!writeIfd(ds, version, gpsTags, positions, 0, staticGpsTagTypes))
return {}; return {};
@@ -1723,7 +1725,7 @@ void MicroExif::updateTags(Tags &tiffTags, Tags &exifTags, Tags &gpsTags, const
tiffTags.remove(EXIF_GPSIFD); tiffTags.remove(EXIF_GPSIFD);
} else { } else {
tiffTags.insert(EXIF_GPSIFD, quint32()); tiffTags.insert(EXIF_GPSIFD, quint32());
gpsTags.insert(GPS_GPSVERSION, QByteArray("2400")); gpsTags.insert(GPS_GPSVERSION, QVariant::fromValue(GPS_GPSVERSION_VALUE));
} }
} }
+265
View File
@@ -0,0 +1,265 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2026 Mirco Miranda <mircomir@outlook.com>
SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "photoshop_p.h"
#include "util_p.h"
#include <QLoggingCategory>
#ifdef QT_DEBUG
Q_LOGGING_CATEGORY(LOG_PSDSHARED, "kf.imageformats.plugins.psdshared", QtDebugMsg)
#else
Q_LOGGING_CATEGORY(LOG_PSDSHARED, "kf.imageformats.plugins.psdshared", QtWarningMsg)
#endif
QString readPascalString(QDataStream &s, qint32 alignBytes, qint32 *size)
{
qint32 tmp = 0;
if (size == nullptr)
size = &tmp;
quint8 stringSize;
s >> stringSize;
*size = sizeof(stringSize);
QString str;
if (stringSize > 0) {
QByteArray ba;
ba.resize(stringSize);
auto read = s.readRawData(ba.data(), ba.size());
if (read > 0) {
*size += read;
str = QString::fromLatin1(ba);
}
}
// align
if (alignBytes > 1)
if (auto pad = *size % alignBytes)
*size += s.skipRawData(alignBytes - pad);
return str;
}
bool writePascalString(const QString &str, QDataStream &s, qint32 alignBytes)
{
auto data = str.toLatin1();
if(data.size() > 250) {
data = data.left(250);
}
auto sz = data.size();
s << quint8(sz);
if (sz && s.writeRawData(data.data(), sz) != sz) {
return false;
}
for(alignBytes = std::max(1, alignBytes), sz += 1; sz % alignBytes; ++sz) {
s << char();
}
return s.status() == QDataStream::Ok;
}
PSDImageResourceSection readImageResourceSection(QDataStream &s, bool *ok)
{
PSDImageResourceSection irs;
bool tmp = true;
if (ok == nullptr)
ok = &tmp;
*ok = true;
// Section size
quint32 tmpSize;
s >> tmpSize;
qint64 sectioSize = tmpSize;
// Reading Image resource block
for (auto size = sectioSize; size > 0;) {
#define DEC_SIZE(value) \
if ((size -= qint64(value)) < 0) { \
*ok = false; \
break; }
// Length Description
// -------------------------------------------------------------------
// 4 Signature: '8BIM'
// 2 Unique identifier for the resource. Image resource IDs
// contains a list of resource IDs used by Photoshop.
// Variable Name: Pascal string, padded to make the size even
// (a null name consists of two bytes of 0)
// 4 Actual size of resource data that follows
// Variable The resource data, described in the sections on the
// individual resource types. It is padded to make the size
// even.
quint32 signature;
s >> signature;
DEC_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
qCDebug(LOG_PSDSHARED) << "Invalid Image Resource Block Signature!";
*ok = false;
break;
}
// id
quint16 id;
s >> id;
DEC_SIZE(sizeof(id))
// getting data
PSDImageResourceBlock irb;
// name
qint32 bytes = 0;
irb.name = readPascalString(s, 2, &bytes);
DEC_SIZE(bytes)
// data read
quint32 dataSize;
s >> dataSize;
DEC_SIZE(sizeof(dataSize))
if (auto dev = s.device()) {
if (dataSize > size) {
qCDebug(LOG_PSDSHARED) << "Invalid Image Resource Block Data Size!";
*ok = false;
break;
}
irb.data = deviceRead(dev, dataSize);
}
auto read = irb.data.size();
if (read > 0) {
DEC_SIZE(read)
}
if (read != qint64(dataSize)) {
qCDebug(LOG_PSDSHARED) << "Image Resource Block Read Error!";
*ok = false;
break;
}
if (auto pad = dataSize % 2) {
auto skipped = s.skipRawData(pad);
if (skipped > 0) {
DEC_SIZE(skipped);
}
}
// insert IRB
irs.insert(ImageResourceId(id), irb);
#undef DEC_SIZE
}
return irs;
}
bool writeImageResourceSection(const PSDImageResourceSection &irs, QDataStream &s)
{
bool ok = false;
auto ba = irs.toByteArray(&ok);
if (!ok) {
return false;
}
s << quint32(ba.size());
if (s.writeRawData(ba.data(), ba.size()) != ba.size()) {
return false;
}
return (s.status() == QDataStream::Ok);
}
QByteArray PSDImageResourceSection::toByteArray(bool *ok) const
{
QByteArray ba;
bool tmp = true;
if (ok == nullptr)
ok = &tmp;
*ok = true;
if (!isEmpty()) {
QDataStream s(&ba, QDataStream::WriteOnly);
s.setByteOrder(QDataStream::BigEndian);
auto ids = keys();
for(auto &&id : ids) {
auto irb = value(id);
if (irb.data.isEmpty()) {
continue;
}
// signature
s << quint32(S_8BIM);
// resource id
s << quint16(id);
// resource name (2 bytes aligned)
if (!writePascalString(irb.name, s, 2)) {
*ok = false;
return{};
}
// data (2 bytes aligned)
auto sz = irb.data.size();
s << quint32(sz);
if (s.writeRawData(irb.data.data(), sz) != sz) {
*ok = false;
return{};
}
if (sz % 2) {
s << char();
}
if (s.status() != QDataStream::Ok) {
*ok = false;
return{};
}
}
}
Q_ASSERT(ba.size() % 2 == 0);
return ba;
}
PSDResolutionInfoBlock::PSDResolutionInfoBlock(qint32 ppmX, qint32 ppmY)
: m_ppmX(ppmX)
, m_ppmY(ppmY)
{
}
bool PSDResolutionInfoBlock::isValid() const
{
return m_ppmX > 0 && m_ppmY > 0;
}
PSDResolutionInfoBlock PSDResolutionInfoBlock::fromImage(const QImage &image)
{
return PSDResolutionInfoBlock(image.dotsPerMeterX(), image.dotsPerMeterY());
}
QByteArray PSDResolutionInfoBlock::toByteArray() const
{
QByteArray ba;
QDataStream ds(&ba, QIODevice::WriteOnly);
ds.setByteOrder(QDataStream::BigEndian);
auto hres = qRoundOrZero(dppm2dpi(m_ppmX) * 65536);
ds << hres;
ds << quint16(1); // dpi
ds << quint16(2); // cm (display)
auto vres = qRoundOrZero(dppm2dpi(m_ppmY) * 65536);
ds << vres;
ds << quint16(1);
ds << quint16(2);
if (hres == 0 || vres == 0) {
return{};
}
return ba;
}
+143
View File
@@ -0,0 +1,143 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2026 Mirco Miranda <mircomir@outlook.com>
SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef PHOTOSHOP_P_H
#define PHOTOSHOP_P_H
#include <QDataStream>
#include <QHash>
#include <QImage>
#include <QString>
enum Signature : quint32 {
S_8BIM = 0x3842494D, // '8BIM'
S_8B64 = 0x38423634, // '8B64'
S_MeSa = 0x4D655361 // 'MeSa'
};
enum ColorMode : quint16 {
CM_BITMAP = 0,
CM_GRAYSCALE = 1,
CM_INDEXED = 2,
CM_RGB = 3,
CM_CMYK = 4,
CM_MULTICHANNEL = 7,
CM_DUOTONE = 8,
CM_LABCOLOR = 9,
};
enum ImageResourceId : quint16 {
IRI_RESOLUTIONINFO = 0x03ED,
IRI_ICCPROFILE = 0x040F,
IRI_TRANSPARENCYINDEX = 0x0417,
IRI_ALPHAIDENTIFIERS = 0x041D,
IRI_VERSIONINFO = 0x0421,
IRI_EXIFDATA1 = 0x0422,
IRI_EXIFDATA3 = 0x0423, // never seen
IRI_XMPMETADATA = 0x0424
};
enum LayerId : quint32 {
LI_MT16 = 0x4D743136, // 'Mt16',
LI_MT32 = 0x4D743332, // 'Mt32',
LI_MTRN = 0x4D74726E // 'Mtrn'
};
/*!
* \brief The PSDImageResourceBlock class
* Raw data of a PSD image resource block
*/
struct PSDImageResourceBlock {
QString name;
QByteArray data;
};
class PSDImageResourceSection : public QHash<ImageResourceId, PSDImageResourceBlock>
{
public:
PSDImageResourceSection() : QHash<ImageResourceId, PSDImageResourceBlock>() {}
PSDImageResourceSection(const PSDImageResourceSection& other) = default;
PSDImageResourceSection& operator =(const PSDImageResourceSection& other) = default;
/*!
* \brief toByteArray
* \param ok Pointer to the operation result variable.
* \return The binary IRB to be written into the PSD file.
*/
QByteArray toByteArray(bool *ok = nullptr) const;
};
class PSDResolutionInfoBlock
{
public:
PSDResolutionInfoBlock(qint32 ppmX, qint32 ppmY);
PSDResolutionInfoBlock(const PSDResolutionInfoBlock& other) = default;
PSDResolutionInfoBlock& operator =(const PSDResolutionInfoBlock& other) = default;
/*!
* \brief isValid
* \return true if both m_ppmX and m_ppmY are grater than 0. Otherwise false.
*/
bool isValid() const;
/*!
* \brief fromImage
* Initialize the class using the image resolution.
*/
static PSDResolutionInfoBlock fromImage(const QImage& image);
/*!
* \brief toByteArray
* \return The binary data ready for the IMage Resource Section.
*/
QByteArray toByteArray() const;
private:
qint32 m_ppmX;
qint32 m_ppmY;
};
/*!
* \brief readPascalString
* Reads the Pascal string as defined in the PSD specification.
* \param s The stream.
* \param alignBytes Alignment of the string.
* \param size Number of stream bytes used.
* \return The string read.
*/
QString readPascalString(QDataStream &s, qint32 alignBytes = 1, qint32 *size = nullptr);
/*!
* \brief writePascalString
* Writes the Pascal string as defined in the PSD specification.
* \param str The string to be written.
* \param s The stream.
* \param alignBytes Alignment of the string.
* \return True on success, otherwise false.
*/
bool writePascalString(const QString& str, QDataStream &s, qint32 alignBytes = 1);
/*!
* \brief readImageResourceSection
* Reads the image resource section.
* \param s The stream.
* \param ok Pointer to the operation result variable.
* \return The image resource section raw data.
*/
PSDImageResourceSection readImageResourceSection(QDataStream &s, bool *ok = nullptr);
/*!
* \brief writeImageResourceSection
* Writes the image resource section.
* \param irs The image resource section raw data.
* \param s The stream.
* \return True on success, otherwise false.
*/
bool writeImageResourceSection(const PSDImageResourceSection& irs, QDataStream &s);
#endif // PHOTOSHOP_P_H
+1 -181
View File
@@ -29,12 +29,12 @@
#include "fastmath_p.h" #include "fastmath_p.h"
#include "microexif_p.h" #include "microexif_p.h"
#include "packbits_p.h" #include "packbits_p.h"
#include "photoshop_p.h"
#include "psd_p.h" #include "psd_p.h"
#include "scanlineconverter_p.h" #include "scanlineconverter_p.h"
#include "util_p.h" #include "util_p.h"
#include <QColorSpace> #include <QColorSpace>
#include <QDataStream>
#include <QImage> #include <QImage>
#include <QLoggingCategory> #include <QLoggingCategory>
@@ -99,41 +99,6 @@ namespace // Private.
#define NATIVE_CMYK (CMYK_FORMAT != QImage::Format_Invalid) #define NATIVE_CMYK (CMYK_FORMAT != QImage::Format_Invalid)
enum Signature : quint32 {
S_8BIM = 0x3842494D, // '8BIM'
S_8B64 = 0x38423634, // '8B64'
S_MeSa = 0x4D655361 // 'MeSa'
};
enum ColorMode : quint16 {
CM_BITMAP = 0,
CM_GRAYSCALE = 1,
CM_INDEXED = 2,
CM_RGB = 3,
CM_CMYK = 4,
CM_MULTICHANNEL = 7,
CM_DUOTONE = 8,
CM_LABCOLOR = 9,
};
enum ImageResourceId : quint16 {
IRI_RESOLUTIONINFO = 0x03ED,
IRI_ICCPROFILE = 0x040F,
IRI_TRANSPARENCYINDEX = 0x0417,
IRI_ALPHAIDENTIFIERS = 0x041D,
IRI_VERSIONINFO = 0x0421,
IRI_EXIFDATA1 = 0x0422,
IRI_EXIFDATA3 = 0x0423, // never seen
IRI_XMPMETADATA = 0x0424
};
enum LayerId : quint32 {
LI_MT16 = 0x4D743136, // 'Mt16',
LI_MT32 = 0x4D743332, // 'Mt32',
LI_MTRN = 0x4D74726E // 'Mtrn'
};
struct PSDHeader { struct PSDHeader {
PSDHeader() { PSDHeader() {
memset(this, 0, sizeof(PSDHeader)); memset(this, 0, sizeof(PSDHeader));
@@ -149,11 +114,6 @@ struct PSDHeader {
ushort color_mode; ushort color_mode;
}; };
struct PSDImageResourceBlock {
QString name;
QByteArray data;
};
/*! /*!
* \brief The PSDDuotoneOptions struct * \brief The PSDDuotoneOptions struct
* \note You can decode the duotone data using the "Duotone Options" * \note You can decode the duotone data using the "Duotone Options"
@@ -172,7 +132,6 @@ struct PSDColorModeDataSection {
QList<QRgb> palette; QList<QRgb> palette;
}; };
using PSDImageResourceSection = QHash<quint16, PSDImageResourceBlock>;
struct PSDLayerInfo { struct PSDLayerInfo {
qint64 size = -1; qint64 size = -1;
@@ -273,145 +232,6 @@ static bool skip_section(QDataStream &s, bool psb = false)
return skip_data(s, section_length); return skip_data(s, section_length);
} }
/*!
* \brief readPascalString
* Reads the Pascal string as defined in the PSD specification.
* \param s The stream.
* \param alignBytes Alignment of the string.
* \param size Number of stream bytes used.
* \return The string read.
*/
static QString readPascalString(QDataStream &s, qint32 alignBytes = 1, qint32 *size = nullptr)
{
qint32 tmp = 0;
if (size == nullptr)
size = &tmp;
quint8 stringSize;
s >> stringSize;
*size = sizeof(stringSize);
QString str;
if (stringSize > 0) {
QByteArray ba;
ba.resize(stringSize);
auto read = s.readRawData(ba.data(), ba.size());
if (read > 0) {
*size += read;
str = QString::fromLatin1(ba);
}
}
// align
if (alignBytes > 1)
if (auto pad = *size % alignBytes)
*size += s.skipRawData(alignBytes - pad);
return str;
}
/*!
* \brief readImageResourceSection
* Reads the image resource section.
* \param s The stream.
* \param ok Pointer to the operation result variable.
* \return The image resource section raw data.
*/
static PSDImageResourceSection readImageResourceSection(QDataStream &s, bool *ok = nullptr)
{
PSDImageResourceSection irs;
bool tmp = true;
if (ok == nullptr)
ok = &tmp;
*ok = true;
// Section size
quint32 tmpSize;
s >> tmpSize;
qint64 sectioSize = tmpSize;
// Reading Image resource block
for (auto size = sectioSize; size > 0;) {
#define DEC_SIZE(value) \
if ((size -= qint64(value)) < 0) { \
*ok = false; \
break; }
// Length Description
// -------------------------------------------------------------------
// 4 Signature: '8BIM'
// 2 Unique identifier for the resource. Image resource IDs
// contains a list of resource IDs used by Photoshop.
// Variable Name: Pascal string, padded to make the size even
// (a null name consists of two bytes of 0)
// 4 Actual size of resource data that follows
// Variable The resource data, described in the sections on the
// individual resource types. It is padded to make the size
// even.
quint32 signature;
s >> signature;
DEC_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
qCDebug(LOG_PSDPLUGIN) << "Invalid Image Resource Block Signature!";
*ok = false;
break;
}
// id
quint16 id;
s >> id;
DEC_SIZE(sizeof(id))
// getting data
PSDImageResourceBlock irb;
// name
qint32 bytes = 0;
irb.name = readPascalString(s, 2, &bytes);
DEC_SIZE(bytes)
// data read
quint32 dataSize;
s >> dataSize;
DEC_SIZE(sizeof(dataSize))
if (auto dev = s.device()) {
if (dataSize > size) {
qCDebug(LOG_PSDPLUGIN) << "Invalid Image Resource Block Data Size!";
*ok = false;
break;
}
irb.data = deviceRead(dev, dataSize);
}
auto read = irb.data.size();
if (read > 0) {
DEC_SIZE(read)
}
if (read != qint64(dataSize)) {
qCDebug(LOG_PSDPLUGIN) << "Image Resource Block Read Error!";
*ok = false;
break;
}
if (auto pad = dataSize % 2) {
auto skipped = s.skipRawData(pad);
if (skipped > 0) {
DEC_SIZE(skipped);
}
}
// insert IRB
irs.insert(id, irb);
#undef DEC_SIZE
}
return irs;
}
PSDAdditionalLayerInfo readAdditionalLayer(QDataStream &s, bool *ok = nullptr) PSDAdditionalLayerInfo readAdditionalLayer(QDataStream &s, bool *ok = nullptr)
{ {
PSDAdditionalLayerInfo li; PSDAdditionalLayerInfo li;
+9 -3
View File
@@ -314,6 +314,8 @@ bool SGIImagePrivate::readImage(QImage &img)
_numrows = _ysize * _zsize; _numrows = _ysize * _zsize;
if (_rle) { if (_rle) {
// start table holds file offsets; raster data follows header + both tables
const quint32 dataOff = 512 + _numrows * 2 * sizeof(quint32);
uint l; uint l;
_starttab = new (std::nothrow) quint32[_numrows]; _starttab = new (std::nothrow) quint32[_numrows];
if (_starttab == nullptr) { if (_starttab == nullptr) {
@@ -321,7 +323,10 @@ bool SGIImagePrivate::readImage(QImage &img)
} }
for (l = 0; !_stream.atEnd() && l < _numrows; l++) { for (l = 0; !_stream.atEnd() && l < _numrows; l++) {
_stream >> _starttab[l]; _stream >> _starttab[l];
_starttab[l] -= 512 + _numrows * 2 * sizeof(quint32); if (_starttab[l] < dataOff) {
return false;
}
_starttab[l] -= dataOff;
if (_stream.status() != QDataStream::Ok) { if (_stream.status() != QDataStream::Ok) {
return false; return false;
} }
@@ -353,9 +358,10 @@ bool SGIImagePrivate::readImage(QImage &img)
// sanity check // sanity check
if (_rle) { if (_rle) {
const uint dataSize = uint(_data.size());
for (uint o = 0; o < _numrows; o++) { for (uint o = 0; o < _numrows; o++) {
// don't change to greater-or-equal! // don't add start+length: uint32 wrap would pass a corrupt file
if (_starttab[o] + _lengthtab[o] > (uint)_data.size()) { if (_starttab[o] > dataSize || _lengthtab[o] > dataSize - _starttab[o]) {
// qCDebug(LOG_RGBPLUGIN) << "image corrupt (sanity check failed)"; // qCDebug(LOG_RGBPLUGIN) << "image corrupt (sanity check failed)";
return false; return false;
} }
+16 -9
View File
@@ -165,20 +165,27 @@ inline bool checkImageSize(const QSize& size, qint32 bytesPerPixel)
return checkImageSize(size.width(), size.height(), bytesPerPixel); return checkImageSize(size.width(), size.height(), bytesPerPixel);
} }
/*!
* \brief qRoundOrZero_T
* In images, many float values can only be positive (e.g., resolution). This function calculates
* the roundness of the passed value, returning 0 if the value is negative or invalid.
* \return 0 when \a d is negative, NaN, Inf or std::numeric_limits<TI>::max(). Otherwise the qRound of \a d.
*/
template<class TI, class SF> // SF = source FP, TI = target INT template<class TI, class SF> // SF = source FP, TI = target INT
TI qRoundOrZero_T(SF d, bool *ok = nullptr) TI qRoundOrZero_T(SF d, bool *ok = nullptr)
{ {
bool tmp = false;
if (ok == nullptr) {
ok = &tmp;
}
// checks for undefined behavior // checks for undefined behavior
if (qIsNaN(d) || qIsInf(d) || d < SF() || d > SF(std::numeric_limits<TI>::max())) { if (qIsNaN(d) || qIsInf(d) || d < SF()) {
if (ok) { *ok = false;
*ok = false; } else {
} *ok = d < SF(std::numeric_limits<TI>::max());
return 0;
} }
if (ok) { return *ok ? qRound(d) : 0;
*ok = true;
}
return qRound(d);
} }
inline qint32 qRoundOrZero(double d, bool *ok = nullptr) inline qint32 qRoundOrZero(double d, bool *ok = nullptr)