Compare commits

..

18 Commits

Author SHA1 Message Date
Nicolas Fella
ae279c55f4 Update dependency version to 6.24.0 2026-03-07 21:18:31 +01:00
Mirco Miranda
836e0a53bb JP2: fix possible Undefined-shift 2026-03-04 08:15:48 +01:00
Mirco Miranda
5eb09116b0 IFF: fix buffer read overflow 2026-02-24 08:57:45 +01:00
Mirco Miranda
92368ca58f Fix Heap-buffer-overflow WRITE 2026-02-22 18:27:34 +00:00
Mirco Miranda
a91c7ef72f Fixed excessively frequent warning messages 2026-02-22 14:56:21 +01:00
Daniel Novomeský
ea2a4aafab ossfuzz: update aom, libavif, openjpeg 2026-02-19 11:31:12 +01:00
Mirco Miranda
c254875780 ANI: fix possible QByteArray allocation exception 2026-02-16 10:12:05 +01:00
Daniel Novomeský
f3de2e77c1 jxl: adjust metadata size limits
Previously there was no limit for uncompressed metadata,
but when compressed metadata required buffer larger than 4MB,
image decoding stopped.

Now the plugin discards/skips metadata boxes larger than 8MB
without stopping image decoding.
If size of compressed box is above 8MB,
we do not attempt to decompress it.
File with compressed metadata could be rejected only in rare cases
when the decompression buffer grows above 32MB (four times 8M).
2026-02-13 15:44:39 +01:00
Mirco Miranda
1ef779f370 RGB: fix a possible exception on the new 2026-02-12 13:42:58 +01:00
Laurent Montel
169a874cba GIT_SILENT: Bump kf ecm_set_disabled_deprecation_versions. Make sure that it compiles fine without kf 6.23 deprecated methods 2026-02-10 07:52:45 +01:00
Mirco Miranda
ebf77ccdf5 TGA: fix Undefined-shift 2026-02-09 23:02:06 +00:00
Mirco Miranda
359cb039d2 PSD: improve conversion sanity checks 2026-02-09 22:50:51 +00:00
Mirco Miranda
f4b91d8a54 IFF: fix compilation warnings 2026-02-09 22:46:04 +00:00
Mirco Miranda
263b5a88e2 ANI: check for array allocation size 2026-02-09 08:41:40 +01:00
Nicolas Fella
8d07f7db1b Update version to 6.24.0 2026-02-06 13:31:01 +01:00
Nicolas Fella
1c2210c100 Update dependency version to 6.23.0 2026-02-06 13:01:56 +01:00
Azhar Momin
b7b438f903 Fix oss-fuzz AFL build (again) 2026-02-03 14:35:56 +05:30
Azhar Momin
336b8906aa Fix OSS-Fuzz AFL builds 2026-02-02 23:05:09 +00:00
26 changed files with 84 additions and 602 deletions

View File

@@ -1,11 +1,11 @@
cmake_minimum_required(VERSION 3.27)
set(KF_VERSION "6.23.0") # handled by release scripts
set(KF_DEP_VERSION "6.22.0") # handled by release scripts
set(KF_VERSION "6.24.0") # handled by release scripts
set(KF_DEP_VERSION "6.24.0") # handled by release scripts
project(KImageFormats VERSION ${KF_VERSION})
include(FeatureSummary)
find_package(ECM 6.22.0 NO_MODULE)
find_package(ECM 6.24.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)
@@ -106,7 +106,7 @@ add_feature_info(LibJXR LibJXR_FOUND "required for the QImage plugin for JPEG XR
ecm_set_disabled_deprecation_versions(
QT 6.11.0
KF 6.21.0
KF 6.23.0
)
add_subdirectory(src)

View File

@@ -247,8 +247,3 @@ add_executable(anitest anitest.cpp)
target_link_libraries(anitest Qt6::Gui Qt6::Test)
ecm_mark_as_test(anitest)
add_test(NAME kimageformats-ani COMMAND anitest)
add_executable(xcursortest xcursortest.cpp)
target_link_libraries(xcursortest Qt6::Gui Qt6::Test)
ecm_mark_as_test(xcursortest)
add_test(NAME kimageformats-xcursortest COMMAND xcursortest)

View File

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

View File

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

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 533 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 906 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 943 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

View File

@@ -1,129 +0,0 @@
/*
* SPDX-FileCopyrightText: 2026 Kai Uwe Broulik <kde@broulik.de>
*
* SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include <QImage>
#include <QImageReader>
#include <QTest>
using namespace Qt::StringLiterals;
static bool imgEquals(const QImage &im1, const QImage &im2)
{
const int height = im1.height();
const int width = im1.width();
for (int i = 0; i < height; ++i) {
const auto *line1 = reinterpret_cast<const quint8 *>(im1.scanLine(i));
const auto *line2 = reinterpret_cast<const quint8 *>(im2.scanLine(i));
for (int j = 0; j < width; ++j) {
if (line1[j] - line2[j] != 0) {
return false;
}
}
}
return true;
}
class XCursorTests : public QObject
{
Q_OBJECT
private Q_SLOTS:
void initTestCase()
{
QCoreApplication::addLibraryPath(QStringLiteral(PLUGIN_DIR));
}
void testReadMetadata()
{
QImageReader reader(QFINDTESTDATA("xcursor/wait"));
QVERIFY(reader.canRead());
QCOMPARE(reader.imageCount(), 18);
// By default it chooses the largest size
QCOMPARE(reader.size(), QSize(72, 72));
QCOMPARE(reader.text(u"Sizes"_s), u"24,48,72"_s);
}
void testRead_data()
{
QTest::addColumn<int>("size");
QTest::addColumn<int>("reference");
// It prefers downsampling over upsampling.
QTest::newRow("12px") << 12 << 24;
QTest::newRow("24px") << 24 << 24;
QTest::newRow("48px") << 48 << 48;
QTest::newRow("50px") << 50 << 72;
QTest::newRow("72px") << 72 << 72;
QTest::newRow("default") << 0 << 72;
}
void testRead()
{
QFETCH(int, size);
QFETCH(int, reference);
QImageReader reader(QFINDTESTDATA("xcursor/wait"));
QVERIFY(reader.canRead());
QCOMPARE(reader.currentImageNumber(), 0);
if (size) {
reader.setScaledSize(QSize(size, size));
}
QCOMPARE(reader.size(), QSize(reference, reference));
QImage aniFrame;
QVERIFY(reader.read(&aniFrame));
QImage img1(QFINDTESTDATA(u"xcursor/wait_%1_1.png"_s.arg(reference)));
img1.convertTo(aniFrame.format());
QVERIFY(imgEquals(aniFrame, img1));
QCOMPARE(reader.nextImageDelay(), 40);
QCOMPARE(reader.text(u"HotspotX"_s), u"48"_s);
QCOMPARE(reader.text(u"HotspotY"_s), u"48"_s);
QVERIFY(reader.canRead());
// that read() above should have advanced us to the next frame
QCOMPARE(reader.currentImageNumber(), 1);
QVERIFY(reader.read(&aniFrame));
QImage img2(QFINDTESTDATA(u"xcursor/wait_%1_2.png"_s.arg(reference)));
img2.convertTo(aniFrame.format());
QVERIFY(imgEquals(aniFrame, img2));
// Would be nice to have a cursor with variable delay and hotspot :-)
QCOMPARE(reader.nextImageDelay(), 40);
QCOMPARE(reader.text(u"HotspotX"_s), u"48"_s);
QCOMPARE(reader.text(u"HotspotY"_s), u"48"_s);
QVERIFY(reader.canRead());
QCOMPARE(reader.currentImageNumber(), 2);
QVERIFY(reader.read(&aniFrame));
QImage img3(QFINDTESTDATA(u"xcursor/wait_%1_3.png"_s.arg(reference)));
img3.convertTo(aniFrame.format());
QVERIFY(imgEquals(aniFrame, img3));
QCOMPARE(reader.text(u"HotspotX"_s), u"48"_s);
QCOMPARE(reader.text(u"HotspotY"_s), u"48"_s);
QCOMPARE(reader.nextImageDelay(), 40);
QVERIFY(reader.canRead());
QCOMPARE(reader.currentImageNumber(), 3);
}
};
QTEST_MAIN(XCursorTests)
#include "xcursortest.moc"

View File

@@ -145,10 +145,6 @@ kimageformats_add_plugin(kimg_xcf SOURCES xcf.cpp)
##################################
kimageformats_add_plugin(kimg_xcursor SOURCES xcursor.cpp)
##################################
if (LibRaw_FOUND)
kimageformats_add_plugin(kimg_raw SOURCES raw.cpp)
kde_enable_exceptions()

View File

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

View File

@@ -1080,6 +1080,7 @@ QByteArray BODYChunk::deinterleave(const QByteArray &planes, qint32 y, const BMH
// (red and green modify operations are unavailable)
auto ctlbits = bitplanes > 5 ? 2 : 1;
auto max = (1 << (bitplanes - ctlbits)) - 1;
auto wrongIdx = false;
quint8 prev[3] = {};
for (qint32 i = 0, cnt = 0; i < rowLen; ++i) {
for (qint32 j = 0; j < 8; ++j, ++cnt) {
@@ -1111,7 +1112,7 @@ QByteArray BODYChunk::deinterleave(const QByteArray &planes, qint32 y, const BMH
prev[1] = qGreen(pal.at(idx));
prev[2] = qBlue(pal.at(idx));
} else {
qCWarning(LOG_IFFPLUGIN) << "BODYChunk::deinterleave(): palette index" << idx << "is out of range";
wrongIdx = true;
}
break;
}
@@ -1121,6 +1122,9 @@ QByteArray BODYChunk::deinterleave(const QByteArray &planes, qint32 y, const BMH
ba[cnt3 + 2] = char(prev[2]);
}
}
if (wrongIdx) {
qCWarning(LOG_IFFPLUGIN) << "BODYChunk::deinterleave(): HAM palette index out of range!";
}
} else if ((modeId & CAMGChunk::ModeId::HalfBrite) && (cmap) &&
(bitplanes >= BITPLANES_HALFBRIDE_MIN && bitplanes <= BITPLANES_HALFBRIDE_MAX)) {
// From A Quick Introduction to IFF.txt:
@@ -1133,6 +1137,7 @@ QByteArray BODYChunk::deinterleave(const QByteArray &planes, qint32 y, const BMH
// absolute colors.
ba = QByteArray(rowLen * 8, char());
auto palSize = cmap->count();
auto wrongIdx = false;
for (qint32 i = 0, cnt = 0; i < rowLen; ++i) {
for (qint32 j = 0; j < 8; ++j, ++cnt) {
quint8 idx = 0, ctl = 0;
@@ -1147,10 +1152,13 @@ QByteArray BODYChunk::deinterleave(const QByteArray &planes, qint32 y, const BMH
if (idx < palSize) {
ba[cnt] = ctl ? idx + palSize : idx;
} else {
qCWarning(LOG_IFFPLUGIN) << "BODYChunk::deinterleave(): palette index" << idx << "is out of range";
wrongIdx = true;
}
}
}
if (wrongIdx) {
qCWarning(LOG_IFFPLUGIN) << "BODYChunk::deinterleave(): HalfBrite palette index out of range!";
}
} else {
// From A Quick Introduction to IFF.txt:
//
@@ -2538,7 +2546,7 @@ static QByteArray decompressVdat(const QByteArray &comp)
static QByteArray vdatToIlbmPlane(const QByteArray &vdatData, const BMHDChunk *header)
{
QByteArray ba(vdatData.size(), char());
auto rowLen = header->rowLen();
auto rowLen = qint32(header->rowLen());
for (auto x = 0, n = 0; x < rowLen; x += 2) {
for (auto y = 0, off = x, h = header->height(); y < h; y++, off += rowLen) {
if ((off + 1 >= ba.size()) || n + 1 >= vdatData.size()) {
@@ -2844,7 +2852,7 @@ bool PLTEChunk::isValid() const
if (dataBytes() < 4) {
return false;
}
if (dataBytes() - 4 < total() * 3) {
if (dataBytes() - 4 < quint32(total()) * 3) {
return false;
}
return chunkId() == PLTEChunk::defaultChunkId();
@@ -3004,7 +3012,7 @@ QByteArray IDATChunk::strideRead(QIODevice *d, qint32 y, const IHDRChunk *header
}
if (header->model() == IHDRChunk::CLut4) {
if (rr.size() < header->width() / 2) {
if (rr.size() < (qint64(header->width()) + 1) / 2) {
return {};
}
QByteArray tmp(header->width(), char());

View File

@@ -253,20 +253,27 @@ public:
bool jp2ToImage(QImage *img) const
{
Q_ASSERT(img->depth() == 8 * sizeof(T) || img->depth() == 32 * sizeof(T));
for (qint32 c = 0, cc = m_jp2_image->numcomps; c < cc; ++c) {
auto cs = cc == 1 ? 1 : 4;
if (img->width() < 1 || img->height() < 1) {
return false;
}
auto maxChannels = qint32(img->bytesPerLine() / sizeof(T) / img->width());
for (qint32 c = 0, cc = std::min(qint32(m_jp2_image->numcomps), maxChannels); c < cc; ++c) {
auto cs = std::min(cc == 1 ? 1 : 4, maxChannels);
auto &&jc = m_jp2_image->comps[c];
if (jc.data == nullptr)
if (jc.data == nullptr) {
return false;
if (qint32(jc.w) != img->width() || qint32(jc.h) != img->height())
}
if (qint32(jc.w) != img->width() || qint32(jc.h) != img->height()) {
return false;
}
// discriminate between int and float (avoid complicating things by creating classes with template specializations)
if (std::numeric_limits<T>::is_integer) {
auto divisor = 1;
if (jc.prec > sizeof(T) * 8) {
auto divisor = 1ull;
auto prec = std::min(size_t(jc.prec), sizeof(*jc.data) * 8);
if (prec > sizeof(T) * 8 && prec < 64) {
// convert to the wanted precision (e.g. 16-bit -> 8-bit: divisor = 65535 / 255 = 257)
divisor = std::max(1, int(((1ll << jc.prec) - 1) / ((1ll << (sizeof(T) * 8)) - 1)));
divisor = std::max(1ull, (((1ull << prec) - 1) / ((1ull << (sizeof(T) * 8)) - 1)));
}
for (qint32 y = 0, h = img->height(); y < h; ++y) {
auto ptr = reinterpret_cast<T *>(img->scanLine(y));

View File

@@ -2008,6 +2008,11 @@ bool QJpegXLHandler::extractBox(QByteArray &output, size_t container_size)
return false;
}
if (rawboxsize > 8388608) { // 8MB limit
qCWarning(LOG_JXLPLUGIN, "Skipped decoding of big JXL metadata box");
return true;
}
output.resize(rawboxsize);
status = JxlDecoderSetBoxBuffer(m_decoder, reinterpret_cast<uint8_t *>(output.data()), output.size());
if (status != JXL_DEC_SUCCESS) {
@@ -2021,7 +2026,7 @@ bool QJpegXLHandler::extractBox(QByteArray &output, size_t container_size)
if (status == JXL_DEC_BOX_NEED_MORE_OUTPUT) {
size_t bytes_remains = JxlDecoderReleaseBoxBuffer(m_decoder);
if (output.size() > 4194304) { // approx. 4MB limit for decompressed metadata box
if (output.size() > 33554432) { // approx. 32MB (4*8) limit for decompressed metadata box
qCWarning(LOG_JXLPLUGIN, "JXL metadata box is too large");
m_parseState = ParseJpegXLError;
return false;

View File

@@ -1017,7 +1017,7 @@ inline void rawChannelCopy(uchar *target, qint32 targetChannels, qint32 targetCh
template<class T>
inline void cmykToRgb(uchar *target, qint32 targetChannels, const char *source, qint32 sourceChannels, qint32 width, bool alpha = false)
inline bool cmykToRgb(uchar *target, qint32 targetChannels, const char *source, qint32 sourceChannels, qint32 width, bool alpha = false)
{
auto s = reinterpret_cast<const T*>(source);
auto t = reinterpret_cast<T*>(target);
@@ -1026,7 +1026,7 @@ inline void cmykToRgb(uchar *target, qint32 targetChannels, const char *source,
if (sourceChannels < 2) {
qCDebug(LOG_PSDPLUGIN) << "cmykToRgb: image is not a valid MCH/CMYK!";
return;
return false;
}
for (qint32 w = 0; w < width; ++w) {
@@ -1047,6 +1047,7 @@ inline void cmykToRgb(uchar *target, qint32 targetChannels, const char *source,
*(pt + 3) = std::numeric_limits<T>::max();
}
}
return true;
}
inline double finv(double v)
@@ -1066,7 +1067,7 @@ inline double gammaCorrection(double linear)
}
template<class T>
inline void labToRgb(uchar *target, qint32 targetChannels, const char *source, qint32 sourceChannels, qint32 width, bool alpha = false)
inline bool labToRgb(uchar *target, qint32 targetChannels, const char *source, qint32 sourceChannels, qint32 width, bool alpha = false)
{
auto s = reinterpret_cast<const T*>(source);
auto t = reinterpret_cast<T*>(target);
@@ -1075,7 +1076,7 @@ inline void labToRgb(uchar *target, qint32 targetChannels, const char *source, q
if (sourceChannels < 3) {
qCDebug(LOG_PSDPLUGIN) << "labToRgb: image is not a valid LAB!";
return;
return false;
}
for (qint32 w = 0; w < width; ++w) {
@@ -1110,6 +1111,7 @@ inline void labToRgb(uchar *target, qint32 targetChannels, const char *source, q
*(pt + 3) = std::numeric_limits<T>::max();
}
}
return true;
}
bool readChannel(QByteArray &target, QDataStream &stream, quint32 compressedSize, quint16 compression)
@@ -1450,10 +1452,13 @@ bool PSDHandler::read(QImage *image)
// Conversion to RGB
if (header.color_mode == CM_CMYK || header.color_mode == CM_MULTICHANNEL) {
if (tmpCmyk.isNull()) {
if (header.depth == 8)
cmykToRgb<quint8>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha);
else if (header.depth == 16)
cmykToRgb<quint16>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha);
if (header.depth == 8) {
if (!cmykToRgb<quint8>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha))
return false;
} else if (header.depth == 16) {
if (!cmykToRgb<quint16>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha))
return false;
}
} else if (header.depth == 8) {
rawChannelsCopyToCMYK<quint8>(tmpCmyk.bits(), 4, psdScanline.data(), header.channel_count, header.width);
if (auto rgbPtr = iccConv.convertedScanLine(tmpCmyk, 0))
@@ -1469,10 +1474,13 @@ bool PSDHandler::read(QImage *image)
}
}
if (header.color_mode == CM_LABCOLOR) {
if (header.depth == 8)
labToRgb<quint8>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha);
else if (header.depth == 16)
labToRgb<quint16>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha);
if (header.depth == 8) {
if (!labToRgb<quint8>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha))
return false;
} else if (header.depth == 16) {
if (!labToRgb<quint16>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha))
return false;
}
}
if (header.color_mode == CM_RGB) {
if (header.depth == 8)

View File

@@ -319,7 +319,10 @@ bool SGIImagePrivate::readImage(QImage &img)
if (_rle) {
uint l;
_starttab = new quint32[_numrows];
_starttab = new (std::nothrow) quint32[_numrows];
if (_starttab == nullptr) {
return false;
}
for (l = 0; !_stream.atEnd() && l < _numrows; l++) {
_stream >> _starttab[l];
_starttab[l] -= 512 + _numrows * 2 * sizeof(quint32);
@@ -331,7 +334,10 @@ bool SGIImagePrivate::readImage(QImage &img)
_starttab[l] = 0;
}
_lengthtab = new quint32[_numrows];
_lengthtab = new (std::nothrow) quint32[_numrows];
if (_lengthtab == nullptr) {
return false;
}
for (l = 0; !_stream.atEnd() && l < _numrows; l++) {
_stream >> _lengthtab[l];
if (_stream.status() != QDataStream::Ok) {
@@ -794,7 +800,10 @@ bool SGIImagePrivate::writeImage(const QImage &image)
_pixmax = 0;
_colormap = NORMAL;
_numrows = _ysize * _zsize;
_starttab = new quint32[_numrows];
_starttab = new (std::nothrow) quint32[_numrows];
if (_starttab == nullptr) {
return false;
}
_rlemap.setBaseOffset(512 + _numrows * 2 * sizeof(quint32));
if (!scanData(image, tfmt, tcs)) {

View File

@@ -731,7 +731,7 @@ static bool LoadTGA(QIODevice *dev, const TgaHeader &tga, QImage &img)
if (div == 0)
hasAlpha = false;
for (int x = x_start; x != x_end; x += x_step) {
const int alpha = hasAlpha ? int((src[3]) << (8 - numAlphaBits)) * 255 / div : 255;
const int alpha = hasAlpha ? int(quint8(src[3]) << (8 - numAlphaBits)) * 255 / div : 255;
scanline[x] = qRgba(src[2], src[1], src[0], alpha);
src += 4;
}

View File

@@ -1,349 +0,0 @@
/*
* SPDX-FileCopyrightText: 2026 Kai Uwe Broulik <kde@broulik.de>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "xcursor_p.h"
#include <QImage>
#include <QLoggingCategory>
#include <QScopeGuard>
#include <QVariant>
#include <algorithm>
#include "util_p.h"
#ifdef QT_DEBUG
Q_LOGGING_CATEGORY(LOG_XCURSORPLUGIN, "kf.imageformats.plugins.xcursor", QtDebugMsg)
#else
Q_LOGGING_CATEGORY(LOG_XCURSORPLUGIN, "kf.imageformats.plugins.xcursor", QtWarningMsg)
#endif
using namespace Qt::StringLiterals;
static constexpr quint32 XCURSOR_MAGIC = 0x72756358; // "Xcur"
static constexpr quint32 XCURSOR_IMAGE_TYPE = 0xfffd0002;
XCursorHandler::XCursorHandler() = default;
bool XCursorHandler::canRead() const
{
if (canRead(device())) {
setFormat("xcursor");
return true;
}
// Check if there's another frame coming.
QDataStream stream(device());
stream.setByteOrder(QDataStream::LittleEndian);
// no peek on QDataStream...
const auto oldPos = device()->pos();
auto cleanup = qScopeGuard([this, oldPos] {
device()->seek(oldPos);
});
quint32 headerSize, type, subtype, version, width, height, xhot, yhot, delay;
stream >> headerSize >> type >> subtype >> version >> width >> height >> xhot >> yhot >> delay;
if (type != XCURSOR_IMAGE_TYPE || width == 0 || height == 0) {
return false;
}
return true;
}
bool XCursorHandler::read(QImage *outImage)
{
if (!ensureScanned()) {
return false;
}
const auto firstFrameOffset = m_images.value(m_currentSize).first();
if (device()->pos() < firstFrameOffset) {
device()->seek(firstFrameOffset);
}
QDataStream stream(device());
stream.setByteOrder(QDataStream::LittleEndian);
quint32 headerSize, type, subtype, version, width, height, xhot, yhot, delay;
stream >> headerSize >> type >> subtype >> version >> width >> height >> xhot >> yhot >> delay;
if (type != XCURSOR_IMAGE_TYPE || width == 0 || height == 0) {
return false;
}
QImage image = imageAlloc(width, height, QImage::Format_ARGB32);
if (image.isNull()) {
return false;
}
const qsizetype byteCount = width * height * sizeof(quint32);
if (stream.readRawData(reinterpret_cast<char *>(image.bits()), byteCount) != byteCount) {
return false;
}
*outImage = image;
++m_currentImageNumber;
m_nextImageDelay = delay;
m_hotspot = QPoint(xhot, yhot);
return !image.isNull();
}
int XCursorHandler::currentImageNumber() const
{
if (!ensureScanned()) {
return 0;
}
return m_currentImageNumber;
}
int XCursorHandler::imageCount() const
{
if (!ensureScanned()) {
return 0;
}
return m_images.value(m_currentSize).count();
}
bool XCursorHandler::jumpToImage(int imageNumber)
{
if (!ensureScanned()) {
return false;
}
if (imageNumber < 0) {
return false;
}
if (imageNumber == m_currentImageNumber) {
return true;
}
if (imageNumber >= imageCount()) {
return false;
}
if (!device()->seek(m_images.value(m_currentSize).at(imageNumber))) {
return false;
}
return true;
}
bool XCursorHandler::jumpToNextImage()
{
if (!ensureScanned()) {
return false;
}
return jumpToImage(m_currentImageNumber + 1);
}
int XCursorHandler::loopCount() const
{
if (!ensureScanned()) {
return 0;
}
return -1;
}
int XCursorHandler::nextImageDelay() const
{
if (!ensureScanned()) {
return 0;
}
return m_nextImageDelay;
}
bool XCursorHandler::supportsOption(ImageOption option) const
{
return option == Size || option == ScaledSize || option == Description || option == Animation;
}
QVariant XCursorHandler::option(ImageOption option) const
{
if (!supportsOption(option) || !ensureScanned()) {
return QVariant();
}
switch (option) {
case QImageIOHandler::Size:
return QSize(m_currentSize, m_currentSize);
case QImageIOHandler::Description: {
QString description;
if (m_hotspot.has_value()) {
description.append(u"HotspotX: %1\n\n"_s.arg(m_hotspot->x()));
description.append(u"HotspotY: %1\n\n"_s.arg(m_hotspot->y()));
}
// TODO std::transform...
QStringList stringSizes;
stringSizes.reserve(m_images.size());
for (auto it = m_images.keyBegin(); it != m_images.keyEnd(); ++it) {
stringSizes.append(QString::number(*it));
}
description.append(u"Sizes: %1\n\n"_s.arg(stringSizes.join(','_L1)));
return description;
}
case QImageIOHandler::Animation:
return imageCount() > 1;
default:
break;
}
return QVariant();
}
void XCursorHandler::setOption(ImageOption option, const QVariant &value)
{
switch (option) {
case QImageIOHandler::ScaledSize:
m_scaledSize = value.toSize();
pickSize();
break;
default:
break;
}
}
bool XCursorHandler::ensureScanned() const
{
if (m_scanned) {
return true;
}
if (device()->isSequential()) {
return false;
}
auto *mutableThis = const_cast<XCursorHandler *>(this);
const auto oldPos = device()->pos();
auto cleanup = qScopeGuard([this, oldPos] {
device()->seek(oldPos);
});
device()->seek(0);
const QByteArray intro = device()->read(4);
if (intro != "Xcur") {
return false;
}
QDataStream stream(device());
stream.setByteOrder(QDataStream::LittleEndian);
quint32 headerSize, version, ntoc;
stream >> headerSize >> version >> ntoc;
// TODO headerSize
// TODO version
if (!ntoc) {
return false;
}
mutableThis->m_images.clear();
for (quint32 i = 0; i < ntoc; ++i) {
quint32 type, size, position;
stream >> type >> size >> position;
if (type != XCURSOR_IMAGE_TYPE) {
continue;
}
mutableThis->m_images[size].append(position);
}
mutableThis->pickSize();
return !m_images.isEmpty();
}
void XCursorHandler::pickSize()
{
if (m_images.isEmpty()) {
return;
}
// If a scaled size was requested, find the closest match.
const auto sizes = m_images.keys();
// If no scaled size is specified, use the biggest one available.
m_currentSize = sizes.last();
if (!m_scaledSize.isEmpty()) {
// TODO Use some clever algo iterator thing instead of keys()...
const int wantedSize = std::max(m_scaledSize.width(), m_scaledSize.height());
// Prefer downsampling over upsampling.
for (int i = sizes.size() - 1; i >= 0; --i) {
const int size = sizes.at(i);
if (size < wantedSize) {
break;
}
m_currentSize = size;
}
}
}
bool XCursorHandler::canRead(QIODevice *device)
{
if (!device) {
qCWarning(LOG_XCURSORPLUGIN) << "XCurosorHandler::canRead() called with no device";
return false;
}
if (device->isSequential()) {
return false;
}
const QByteArray intro = device->peek(4 * 4);
if (intro.length() != 4 * 4) {
return false;
}
if (!intro.startsWith("Xcur")) {
return false;
}
// TODO sanity check sizes?
return true;
}
QImageIOPlugin::Capabilities XCursorPlugin::capabilities(QIODevice *device, const QByteArray &format) const
{
if (format == "xcursor") {
return Capabilities(CanRead);
}
if (!format.isEmpty()) {
return {};
}
if (!device->isOpen()) {
return {};
}
Capabilities cap;
if (device->isReadable() && XCursorHandler::canRead(device)) {
cap |= CanRead;
}
return cap;
}
QImageIOHandler *XCursorPlugin::create(QIODevice *device, const QByteArray &format) const
{
QImageIOHandler *handler = new XCursorHandler;
handler->setDevice(device);
handler->setFormat(format);
return handler;
}
#include "moc_xcursor_p.cpp"

View File

@@ -1,8 +0,0 @@
{
"Keys": [
"xcursor"
],
"MimeTypes": [
"image/x-xcursor"
]
}

View File

@@ -1,69 +0,0 @@
/*
* SPDX-FileCopyrightText: 2026 Kai Uwe Broulik <kde@broulik.de>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KIMG_XCURSOR_P_H
#define KIMG_XCURSOR_P_H
#include <QImageIOPlugin>
#include <QSize>
#include <optional>
struct XCursorImage {
qint64 offset;
quint32 delay;
};
class XCursorHandler : public QImageIOHandler
{
public:
XCursorHandler();
bool canRead() const override;
bool read(QImage *image) override;
int currentImageNumber() const override;
int imageCount() const override;
bool jumpToImage(int imageNumber) override;
bool jumpToNextImage() override;
int loopCount() const override;
int nextImageDelay() const override;
bool supportsOption(ImageOption option) const override;
QVariant option(ImageOption option) const override;
void setOption(ImageOption option, const QVariant &value) override;
static bool canRead(QIODevice *device);
private:
bool ensureScanned() const;
void pickSize();
bool m_scanned = false;
int m_currentImageNumber = 0;
QSize m_scaledSize;
int m_currentSize = 0;
QMap<int /*size*/, QVector<qint64 /*offset*/>> m_images;
int m_nextImageDelay = 0;
std::optional<QPoint> m_hotspot;
};
class XCursorPlugin : public QImageIOPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QImageIOHandlerFactoryInterface" FILE "xcursor.json")
public:
Capabilities capabilities(QIODevice *device, const QByteArray &format) const override;
QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const override;
};
#endif // KIMG_XCURSOR_P_H