Compare commits

..
8 Commits
35 changed files with 689 additions and 206 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.29) cmake_minimum_required(VERSION 3.29)
set(KF_VERSION "6.30.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 set(KF_DEP_VERSION "6.30.0") # handled by release scripts
project(KImageFormats VERSION ${KF_VERSION}) project(KImageFormats VERSION ${KF_VERSION})
+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.
+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.
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
+170 -11
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.
@@ -380,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);
}
} }
/*! /*!
@@ -514,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;
@@ -545,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);
@@ -577,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()));
@@ -641,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 doesn’t have a chromaticities attribute, display software should assume that the // If a file doesn’t have a chromaticities attribute, display software should assume that the
@@ -683,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 ||
@@ -699,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));
@@ -743,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
@@ -761,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;
} }
@@ -815,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
+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;