Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a3c7f2c86 | ||
|
|
43e6efb0e5 | ||
|
|
b7a407a752 | ||
|
|
62c45582df | ||
|
|
a738553dc9 | ||
|
|
0862db49b8 | ||
|
|
05af59e819 | ||
|
|
5d6dfb9012 | ||
|
|
72518dcc1a | ||
|
|
a6027264d4 | ||
|
|
47ea774621 | ||
|
|
5ed0affc95 | ||
|
|
42e9733060 | ||
|
|
bae45287ad | ||
|
|
458257b7a3 | ||
|
|
09452b0964 | ||
|
|
c7baa057a6 | ||
|
|
c57af25d36 | ||
|
|
320ded493a |
@@ -1,11 +1,11 @@
|
||||
cmake_minimum_required(VERSION 3.29)
|
||||
|
||||
set(KF_VERSION "6.29.0") # handled by release scripts
|
||||
set(KF_DEP_VERSION "6.29.0") # handled by release scripts
|
||||
set(KF_VERSION "6.31.0") # handled by release scripts
|
||||
set(KF_DEP_VERSION "6.30.0") # handled by release scripts
|
||||
project(KImageFormats VERSION ${KF_VERSION})
|
||||
|
||||
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")
|
||||
feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES)
|
||||
|
||||
|
||||
@@ -392,6 +392,22 @@ The plugin can set the following additional metadata:
|
||||
- `EXRLayerName`: A string containing the name of the EXR layer used to decode
|
||||
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 plugin uses `Ghostscript` to convert the raster image. When reading it
|
||||
|
||||
@@ -101,7 +101,7 @@ cd $SRC/aom
|
||||
mkdir build.libavif
|
||||
cd build.libavif
|
||||
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
|
||||
|
||||
# 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
|
||||
header=`ls $SRC/kimageformats/src/imageformats/$format*.h`
|
||||
/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.
|
||||
|
||||
|
||||
@@ -125,7 +125,27 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
|
||||
b.open(QIODevice::ReadOnly);
|
||||
handler->setDevice(&b);
|
||||
handler->canRead();
|
||||
|
||||
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;
|
||||
|
||||
|
||||
@@ -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/qttools.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 https://github.com/strukturag/libde265.git
|
||||
git clone --depth 1 -b v2.5.4 https://github.com/uclouvain/openjpeg.git
|
||||
|
||||
|
After Width: | Height: | Size: 255 KiB |
@@ -0,0 +1,7 @@
|
||||
[
|
||||
{
|
||||
"fileName": "grid.png",
|
||||
"fuzziness": 2,
|
||||
"description": "Minimum fuzziness value to pass the test on all architectures."
|
||||
}
|
||||
]
|
||||
|
After Width: | Height: | Size: 277 KiB |
@@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -7,9 +7,19 @@
|
||||
"transferFunction" : "Linear",
|
||||
"gamma" : 1
|
||||
},
|
||||
"metadata" : [
|
||||
{
|
||||
"key" : "ModificationDate",
|
||||
"value" : "2026-04-15T10:48:48"
|
||||
},
|
||||
{
|
||||
"key" : "Software" ,
|
||||
"value" : "Adobe Photoshop 27.5 (Windows)"
|
||||
}
|
||||
],
|
||||
"resolution" : {
|
||||
"dotsPerMeterX" : 3937,
|
||||
"dotsPerMeterY" : 3937
|
||||
"dotsPerMeterX" : 11811,
|
||||
"dotsPerMeterY" : 11811
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
|
After Width: | Height: | Size: 199 B |
|
After Width: | Height: | Size: 242 B |
|
After Width: | Height: | Size: 234 B |
|
After Width: | Height: | Size: 232 B |
|
After Width: | Height: | Size: 221 B |
|
After Width: | Height: | Size: 222 B |
|
After Width: | Height: | Size: 243 B |
|
After Width: | Height: | Size: 228 B |
|
After Width: | Height: | Size: 244 B |
@@ -0,0 +1,7 @@
|
||||
[
|
||||
{
|
||||
"fileName": "konqi.jpg",
|
||||
"fuzziness": 72,
|
||||
"description": "Minimum fuzziness value to pass the test on all architectures."
|
||||
}
|
||||
]
|
||||
|
After Width: | Height: | Size: 362 KiB |
@@ -57,7 +57,7 @@ endif()
|
||||
##################################
|
||||
|
||||
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)
|
||||
target_link_libraries(kimg_exr PRIVATE OpenEXR::OpenEXR)
|
||||
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,6 +12,7 @@
|
||||
#include <QScopeGuard>
|
||||
#include <QVariant>
|
||||
#include <QtEndian>
|
||||
#include <QtNumeric>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
@@ -267,7 +268,17 @@ int ANIHandler::nextImageDelay() const
|
||||
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
|
||||
|
||||
@@ -1160,25 +1160,19 @@ bool QAVIFHandler::jumpToNextImage()
|
||||
return false;
|
||||
}
|
||||
|
||||
avifResult decodeResult;
|
||||
|
||||
if (m_decoder->imageIndex >= 0) {
|
||||
if (m_decoder->imageCount < 2) {
|
||||
m_parseState = ParseAvifSuccess;
|
||||
// single image, there is no next image
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_decoder->imageIndex >= m_decoder->imageCount - 1) { // start from beginning
|
||||
decodeResult = avifDecoderReset(m_decoder);
|
||||
if (decodeResult != AVIF_RESULT_OK) {
|
||||
qCWarning(LOG_AVIFPLUGIN, "ERROR in avifDecoderReset: %s", avifResultToString(decodeResult));
|
||||
m_parseState = ParseAvifError;
|
||||
if (m_decoder->imageIndex >= m_decoder->imageCount - 1) {
|
||||
// do not start from beginning when at the end already
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
decodeResult = avifDecoderNextImage(m_decoder);
|
||||
avifResult decodeResult = avifDecoderNextImage(m_decoder);
|
||||
|
||||
if (decodeResult != AVIF_RESULT_OK) {
|
||||
qCWarning(LOG_AVIFPLUGIN, "ERROR: Failed to decode Next image in sequence: %s", avifResultToString(decodeResult));
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
*/
|
||||
|
||||
#include "exr_p.h"
|
||||
#include "microexif_p.h"
|
||||
#include "photoshop_p.h"
|
||||
#include "scanlineconverter_p.h"
|
||||
#include "util_p.h"
|
||||
|
||||
@@ -200,12 +202,17 @@ void K_OStream::seekg(Imf::Int64 pos)
|
||||
m_dev->seek(pos);
|
||||
}
|
||||
|
||||
#define EXR_SUBFORMAT_RGB QByteArray("RGB")
|
||||
|
||||
#define EXR_SUBFORMAT_YC QByteArray("YC")
|
||||
|
||||
EXRHandler::EXRHandler()
|
||||
: m_compressionRatio(-1)
|
||||
, m_quality(-1)
|
||||
, m_imageNumber(0)
|
||||
, m_imageCount(0)
|
||||
, m_startPos(-1)
|
||||
, m_subType(EXR_SUBFORMAT_RGB)
|
||||
{
|
||||
// Set the number of threads to use (0 is allowed)
|
||||
Imf::setGlobalThreadCount(QThread::idealThreadCount() / 2);
|
||||
@@ -225,7 +232,7 @@ bool EXRHandler::canRead() const
|
||||
static QImage::Format imageFormat(const Imf::RgbaInputFile &file)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -368,7 +375,8 @@ static void readMetadata(const Imf::Header &header, QImage &image)
|
||||
|
||||
// shot metadata
|
||||
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")) {
|
||||
image.setText(QStringLiteral(META_KEY_EXPOSURETIME), QLocale::c().toString(expTime->value()));
|
||||
@@ -379,6 +387,34 @@ static void readMetadata(const Imf::Header &header, QImage &image)
|
||||
if (auto focalLen = header.findTypedAttribute<Imf::FloatAttribute>("effectiveFocalLength")) {
|
||||
image.setText(QStringLiteral(META_KEY_FOCALLENGTH), QLocale::c().toString(focalLen->value()));
|
||||
}
|
||||
|
||||
// Photoshop image resource section
|
||||
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) {
|
||||
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 +549,9 @@ bool makePreview(const QImage &image, Imf::Array2D<Imf::PreviewRgba> &pixels)
|
||||
|
||||
QImage preview;
|
||||
if (w > h) {
|
||||
preview = image.scaledToWidth(256).convertToFormat(QImage::Format_ARGB32);
|
||||
preview = image.scaledToWidth(256).convertToFormat(QImage::Format_ARGB32_Premultiplied);
|
||||
} else {
|
||||
preview = image.scaledToHeight(256).convertToFormat(QImage::Format_ARGB32);
|
||||
preview = image.scaledToHeight(256).convertToFormat(QImage::Format_ARGB32_Premultiplied);
|
||||
}
|
||||
if (preview.isNull()) {
|
||||
return false;
|
||||
@@ -687,6 +723,10 @@ bool EXRHandler::write(const QImage &image)
|
||||
// write the EXR
|
||||
K_OStream ostr(device());
|
||||
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 ||
|
||||
image.format() == QImage::Format_MonoLSB ||
|
||||
image.format() == QImage::Format_Grayscale16 ||
|
||||
@@ -698,7 +738,7 @@ bool EXRHandler::write(const QImage &image)
|
||||
pixels.resizeErase(EXR_LINES_PER_BLOCK, width);
|
||||
|
||||
// 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);
|
||||
slc.setDefaultSourceColorSpace(QColorSpace(QColorSpace::SRgb));
|
||||
slc.setTargetColorSpace(QColorSpace(QColorSpace::SRgbLinear));
|
||||
@@ -742,6 +782,15 @@ void EXRHandler::setOption(ImageOption option, const QVariant &value)
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool EXRHandler::supportsOption(ImageOption option) const
|
||||
@@ -760,6 +809,12 @@ bool EXRHandler::supportsOption(ImageOption option) const
|
||||
if (option == QImageIOHandler::Quality) {
|
||||
return true;
|
||||
}
|
||||
if (option == QImageIOHandler::SubType) {
|
||||
return true;
|
||||
}
|
||||
if (option == QImageIOHandler::SupportedSubTypes) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -814,6 +869,14 @@ QVariant EXRHandler::option(ImageOption option) const
|
||||
v = QVariant(m_quality);
|
||||
}
|
||||
|
||||
if (option == QImageIOHandler::SupportedSubTypes) {
|
||||
return QVariant::fromValue(QList<QByteArray>() << EXR_SUBFORMAT_RGB << EXR_SUBFORMAT_YC);
|
||||
}
|
||||
|
||||
if (option == QImageIOHandler::SubType) {
|
||||
return QVariant::fromValue(m_subType);
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
|
||||
@@ -118,6 +118,14 @@ private:
|
||||
* The initial device position to allow multi image load (cache value).
|
||||
*/
|
||||
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;
|
||||
};
|
||||
|
||||
class EXRPlugin : public QImageIOPlugin
|
||||
|
||||
@@ -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) {
|
||||
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);
|
||||
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)
|
||||
@@ -930,8 +951,8 @@ bool HEIFHandler::ensureDecoder()
|
||||
struct heif_image *img = nullptr;
|
||||
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")) {
|
||||
qCWarning(LOG_HEIFPLUGIN) << "Non-standard HEIF image with invalid matrix_coefficients, probably made by a Xiaomi device!";
|
||||
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!";
|
||||
|
||||
// second try to decode with strict decoding disabled
|
||||
decoder_option->strict_decoding = 0;
|
||||
|
||||
@@ -622,21 +622,20 @@ public:
|
||||
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]);
|
||||
for (int i = 0; i < ncomp; ++i) {
|
||||
auto &&p = cmptparm.get() + i;
|
||||
memset(p, 0, sizeof(opj_image_cmptparm_t));
|
||||
p->dx = m_cparameters.subsampling_dx;
|
||||
p->dy = m_cparameters.subsampling_dy;
|
||||
p->w = image.width();
|
||||
p->h = image.height();
|
||||
p->x0 = 0;
|
||||
p->y0 = 0;
|
||||
p->prec = prec;
|
||||
p->sgnd = 0;
|
||||
std::vector<opj_image_cmptparm_t> cmptparm(ncomp);
|
||||
for (opj_image_cmptparm_t &p : cmptparm) {
|
||||
memset(&p, 0, sizeof(opj_image_cmptparm_t));
|
||||
p.dx = m_cparameters.subsampling_dx;
|
||||
p.dy = m_cparameters.subsampling_dy;
|
||||
p.w = image.width();
|
||||
p.h = image.height();
|
||||
p.x0 = 0;
|
||||
p.y0 = 0;
|
||||
p.prec = prec;
|
||||
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) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -75,8 +75,7 @@ void QtJXLMemoryManagerFree(void *, void *address)
|
||||
QJpegXLHandler::QJpegXLHandler()
|
||||
: m_parseState(ParseJpegXLNotParsed)
|
||||
, m_quality(90)
|
||||
, m_currentimage_index(0)
|
||||
, m_previousimage_index(-1)
|
||||
, m_currentimage_index(-1)
|
||||
, m_transformations(QImageIOHandler::TransformationNone)
|
||||
, m_decoder(nullptr)
|
||||
, m_runner(nullptr)
|
||||
@@ -183,11 +182,7 @@ bool QJpegXLHandler::ensureDecoder()
|
||||
}
|
||||
|
||||
// Creating a simple memory manager
|
||||
JxlMemoryManager memory_manager = {
|
||||
.opaque = &m_maxBytes,
|
||||
.alloc = QtJXLMemoryManagerAlloc,
|
||||
.free = QtJXLMemoryManagerFree
|
||||
};
|
||||
JxlMemoryManager memory_manager = {.opaque = &m_maxBytes, .alloc = QtJXLMemoryManagerAlloc, .free = QtJXLMemoryManagerFree};
|
||||
// Creating the decoder (it makes a deep copy of memory manager)
|
||||
m_decoder = JxlDecoderCreate(&memory_manager);
|
||||
if (!m_decoder) {
|
||||
@@ -817,17 +812,11 @@ bool QJpegXLHandler::decode_one_frame()
|
||||
exif.updateImageMetadata(m_current_image);
|
||||
}
|
||||
|
||||
m_currentimage_index++;
|
||||
m_next_image_delay = m_framedelays[m_currentimage_index];
|
||||
m_previousimage_index = m_currentimage_index;
|
||||
|
||||
if (m_framedelays.count() > 1) {
|
||||
m_currentimage_index++;
|
||||
|
||||
if (m_currentimage_index >= m_framedelays.count()) {
|
||||
if (!rewind()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_currentimage_index >= m_framedelays.count() - 1) {
|
||||
// all frames in animation have been read
|
||||
m_parseState = ParseJpegXLFinished;
|
||||
} else {
|
||||
@@ -847,9 +836,9 @@ bool QJpegXLHandler::read(QImage *image)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_currentimage_index == m_previousimage_index) {
|
||||
*image = m_current_image;
|
||||
return jumpToNextImage();
|
||||
if (m_parseState == ParseJpegXLFinished) {
|
||||
// at the end already
|
||||
return false;
|
||||
}
|
||||
|
||||
if (decode_one_frame()) {
|
||||
@@ -1757,19 +1746,26 @@ bool QJpegXLHandler::jumpToNextImage()
|
||||
}
|
||||
|
||||
if (m_framedelays.count() > 1) {
|
||||
m_currentimage_index++;
|
||||
|
||||
if (m_currentimage_index >= m_framedelays.count()) {
|
||||
if (!rewind()) {
|
||||
if (m_currentimage_index >= (m_framedelays.count() - 1)) {
|
||||
// we are already at the last frame
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
m_currentimage_index++;
|
||||
m_next_image_delay = m_framedelays[m_currentimage_index];
|
||||
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;
|
||||
return true;
|
||||
// static image, there is no next frame
|
||||
return false;
|
||||
}
|
||||
|
||||
bool QJpegXLHandler::jumpToImage(int imageNumber)
|
||||
@@ -1782,14 +1778,17 @@ bool QJpegXLHandler::jumpToImage(int imageNumber)
|
||||
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;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (imageNumber > m_currentimage_index) {
|
||||
JxlDecoderSkipFrames(m_decoder, imageNumber - m_currentimage_index);
|
||||
m_currentimage_index = imageNumber;
|
||||
if (imageNumber > next_image_index) {
|
||||
JxlDecoderSkipFrames(m_decoder, imageNumber - next_image_index);
|
||||
m_currentimage_index = imageNumber - 1;
|
||||
m_parseState = ParseJpegXLSuccess;
|
||||
return true;
|
||||
}
|
||||
@@ -1801,7 +1800,7 @@ bool QJpegXLHandler::jumpToImage(int imageNumber)
|
||||
if (imageNumber > 0) {
|
||||
JxlDecoderSkipFrames(m_decoder, imageNumber);
|
||||
}
|
||||
m_currentimage_index = imageNumber;
|
||||
m_currentimage_index = imageNumber - 1;
|
||||
m_parseState = ParseJpegXLSuccess;
|
||||
return true;
|
||||
}
|
||||
@@ -1834,7 +1833,7 @@ int QJpegXLHandler::loopCount() const
|
||||
|
||||
bool QJpegXLHandler::rewind()
|
||||
{
|
||||
m_currentimage_index = 0;
|
||||
m_currentimage_index = -1;
|
||||
|
||||
JxlDecoderReleaseInput(m_decoder);
|
||||
JxlDecoderRewind(m_decoder);
|
||||
|
||||
@@ -65,7 +65,6 @@ private:
|
||||
ParseJpegXLState m_parseState;
|
||||
int m_quality;
|
||||
int m_currentimage_index;
|
||||
int m_previousimage_index;
|
||||
QImageIOHandler::Transformations m_transformations;
|
||||
|
||||
QByteArray m_rawData;
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
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 <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;
|
||||
};
|
||||
|
||||
using PSDImageResourceSection = QHash<ImageResourceId, PSDImageResourceBlock>;
|
||||
|
||||
|
||||
/*!
|
||||
* \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 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);
|
||||
|
||||
#endif // PHOTOSHOP_P_H
|
||||
@@ -29,12 +29,12 @@
|
||||
#include "fastmath_p.h"
|
||||
#include "microexif_p.h"
|
||||
#include "packbits_p.h"
|
||||
#include "photoshop_p.h"
|
||||
#include "psd_p.h"
|
||||
#include "scanlineconverter_p.h"
|
||||
#include "util_p.h"
|
||||
|
||||
#include <QColorSpace>
|
||||
#include <QDataStream>
|
||||
#include <QImage>
|
||||
#include <QLoggingCategory>
|
||||
|
||||
@@ -99,41 +99,6 @@ namespace // Private.
|
||||
|
||||
#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 {
|
||||
PSDHeader() {
|
||||
memset(this, 0, sizeof(PSDHeader));
|
||||
@@ -149,11 +114,6 @@ struct PSDHeader {
|
||||
ushort color_mode;
|
||||
};
|
||||
|
||||
struct PSDImageResourceBlock {
|
||||
QString name;
|
||||
QByteArray data;
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief The PSDDuotoneOptions struct
|
||||
* \note You can decode the duotone data using the "Duotone Options"
|
||||
@@ -172,7 +132,6 @@ struct PSDColorModeDataSection {
|
||||
QList<QRgb> palette;
|
||||
};
|
||||
|
||||
using PSDImageResourceSection = QHash<quint16, PSDImageResourceBlock>;
|
||||
|
||||
struct PSDLayerInfo {
|
||||
qint64 size = -1;
|
||||
@@ -273,145 +232,6 @@ static bool skip_section(QDataStream &s, bool psb = false)
|
||||
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 li;
|
||||
|
||||
@@ -314,6 +314,8 @@ bool SGIImagePrivate::readImage(QImage &img)
|
||||
_numrows = _ysize * _zsize;
|
||||
|
||||
if (_rle) {
|
||||
// start table holds file offsets; raster data follows header + both tables
|
||||
const quint32 dataOff = 512 + _numrows * 2 * sizeof(quint32);
|
||||
uint l;
|
||||
_starttab = new (std::nothrow) quint32[_numrows];
|
||||
if (_starttab == nullptr) {
|
||||
@@ -321,7 +323,10 @@ bool SGIImagePrivate::readImage(QImage &img)
|
||||
}
|
||||
for (l = 0; !_stream.atEnd() && l < _numrows; 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) {
|
||||
return false;
|
||||
}
|
||||
@@ -353,9 +358,10 @@ bool SGIImagePrivate::readImage(QImage &img)
|
||||
|
||||
// sanity check
|
||||
if (_rle) {
|
||||
const uint dataSize = uint(_data.size());
|
||||
for (uint o = 0; o < _numrows; o++) {
|
||||
// don't change to greater-or-equal!
|
||||
if (_starttab[o] + _lengthtab[o] > (uint)_data.size()) {
|
||||
// don't add start+length: uint32 wrap would pass a corrupt file
|
||||
if (_starttab[o] > dataSize || _lengthtab[o] > dataSize - _starttab[o]) {
|
||||
// qCDebug(LOG_RGBPLUGIN) << "image corrupt (sanity check failed)";
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -165,20 +165,27 @@ inline bool checkImageSize(const QSize& size, qint32 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
|
||||
TI qRoundOrZero_T(SF d, bool *ok = nullptr)
|
||||
{
|
||||
bool tmp = false;
|
||||
if (ok == nullptr) {
|
||||
ok = &tmp;
|
||||
}
|
||||
|
||||
// checks for undefined behavior
|
||||
if (qIsNaN(d) || qIsInf(d) || d < SF() || d > SF(std::numeric_limits<TI>::max())) {
|
||||
if (ok) {
|
||||
if (qIsNaN(d) || qIsInf(d) || d < SF()) {
|
||||
*ok = false;
|
||||
} else {
|
||||
*ok = d < SF(std::numeric_limits<TI>::max());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (ok) {
|
||||
*ok = true;
|
||||
}
|
||||
return qRound(d);
|
||||
return *ok ? qRound(d) : 0;
|
||||
}
|
||||
|
||||
inline qint32 qRoundOrZero(double d, bool *ok = nullptr)
|
||||
|
||||