mirror of
https://invent.kde.org/frameworks/kimageformats.git
synced 2026-09-14 05:07:00 -04:00
Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e5072d590 | ||
|
|
6a3c7f2c86 | ||
|
|
43e6efb0e5 | ||
|
|
b7a407a752 | ||
|
|
62c45582df | ||
|
|
a738553dc9 | ||
|
|
0862db49b8 | ||
|
|
05af59e819 |
+1
-1
@@ -1,6 +1,6 @@
|
||||
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
|
||||
project(KImageFormats VERSION ${KF_VERSION})
|
||||
|
||||
|
||||
@@ -384,14 +384,33 @@ The following defines can be defined in cmake to modify the behavior of the
|
||||
plugin:
|
||||
- `EXR_CONVERT_TO_SRGB`: the linear data is converted to sRGB on read to
|
||||
accommodate programs that do not support color profiles.
|
||||
- `EXR_DISABLE_XMP_ATTRIBUTE`: disables the stores XMP values in a non-standard
|
||||
attribute named "xmp". Note that Gimp reads the "xmp" attribute and Darktable
|
||||
writes it as well.
|
||||
- `EXR_DISABLE_XMP_ATTRIBUTE`: disables the storage of XMP values in a
|
||||
non-standard attribute named "xmp". Note that Gimp reads the "xmp" attribute
|
||||
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:
|
||||
- `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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Binary file not shown.
|
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."
|
||||
}
|
||||
]
|
||||
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",
|
||||
"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
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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
|
||||
|
||||
+170
-11
@@ -8,6 +8,8 @@
|
||||
*/
|
||||
|
||||
#include "exr_p.h"
|
||||
#include "microexif_p.h"
|
||||
#include "photoshop_p.h"
|
||||
#include "scanlineconverter_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
|
||||
|
||||
/* *** EXR_STORE_XMP_ATTRIBUTE ***
|
||||
* If defined, disables the stores XMP values in a non-standard attribute named "xmp".
|
||||
/* *** EXR_DISABLE_XMP_ATTRIBUTE ***
|
||||
* If defined, disables the storage of XMP values in the non-standard attribute named "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,
|
||||
* 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
|
||||
|
||||
/* *** 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 ***
|
||||
* 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)
|
||||
#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
|
||||
{
|
||||
public:
|
||||
@@ -200,12 +217,18 @@ 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)
|
||||
, m_transformation(QImageIOHandler::TransformationNone)
|
||||
{
|
||||
// Set the number of threads to use (0 is allowed)
|
||||
Imf::setGlobalThreadCount(QThread::idealThreadCount() / 2);
|
||||
@@ -225,7 +248,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);
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -285,6 +308,27 @@ static void printAttributes(const Imf::Header &h)
|
||||
}
|
||||
#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
|
||||
* 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")) {
|
||||
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;
|
||||
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;
|
||||
@@ -545,8 +603,11 @@ bool makePreview(const QImage &image, Imf::Array2D<Imf::PreviewRgba> &pixels)
|
||||
* \brief setMetadata
|
||||
* 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();
|
||||
for (auto &&key : image.textKeys()) {
|
||||
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)) {
|
||||
header.insert("xmp", Imf::StringAttribute(text.toStdString()));
|
||||
xmpData = text;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!key.compare(QStringLiteral(META_KEY_MANUFACTURER), Qt::CaseInsensitive)) {
|
||||
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())));
|
||||
}
|
||||
|
||||
#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)
|
||||
// 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
|
||||
@@ -683,11 +777,15 @@ bool EXRHandler::write(const QImage &image)
|
||||
}
|
||||
|
||||
// set metadata (EXR attributes)
|
||||
setMetadata(image, header);
|
||||
setMetadata(image, header, m_transformation);
|
||||
|
||||
// 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 ||
|
||||
@@ -699,7 +797,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));
|
||||
@@ -743,6 +841,24 @@ 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;
|
||||
}
|
||||
}
|
||||
#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
|
||||
@@ -761,6 +877,17 @@ bool EXRHandler::supportsOption(ImageOption option) const
|
||||
if (option == QImageIOHandler::Quality) {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -815,6 +942,38 @@ QVariant EXRHandler::option(ImageOption option) const
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -118,6 +118,20 @@ 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;
|
||||
|
||||
/*!
|
||||
* \brief m_transformation
|
||||
* The Qt image transformation.
|
||||
*/
|
||||
QImageIOHandler::Transformation m_transformation;
|
||||
};
|
||||
|
||||
class EXRPlugin : public QImageIOPlugin
|
||||
|
||||
@@ -84,6 +84,8 @@
|
||||
#define EXIF_TAG_SIZEOF(dataType) (quint16(dataType) & 0x3F)
|
||||
#define EXIF_TAG_DATATYPE(dataType) (quint16(dataType) >> 6)
|
||||
|
||||
#define GPS_GPSVERSION_VALUE QList<quint8>{0x02, 0x04, 0x00, 0x00}
|
||||
|
||||
enum class ExifTagType : quint16 {
|
||||
// Base data types
|
||||
Byte = EXIF_TAG_VALUE(1, 1),
|
||||
@@ -1325,7 +1327,7 @@ QByteArray MicroExif::gpsIfdByteArray(const QDataStream::ByteOrder &byteOrder, c
|
||||
QDataStream ds(&ba, QIODevice::WriteOnly);
|
||||
ds.setByteOrder(byteOrder);
|
||||
auto gpsTags = m_gpsTags;
|
||||
gpsTags.insert(GPS_GPSVERSION, QByteArray("2400"));
|
||||
gpsTags.insert(GPS_GPSVERSION, QVariant::fromValue(GPS_GPSVERSION_VALUE));
|
||||
TagPos positions;
|
||||
if (!writeIfd(ds, version, gpsTags, positions, 0, staticGpsTagTypes))
|
||||
return {};
|
||||
@@ -1723,7 +1725,7 @@ void MicroExif::updateTags(Tags &tiffTags, Tags &exifTags, Tags &gpsTags, const
|
||||
tiffTags.remove(EXIF_GPSIFD);
|
||||
} else {
|
||||
tiffTags.insert(EXIF_GPSIFD, quint32());
|
||||
gpsTags.insert(GPS_GPSVERSION, QByteArray("2400"));
|
||||
gpsTags.insert(GPS_GPSVERSION, QVariant::fromValue(GPS_GPSVERSION_VALUE));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user