Compare commits

...

52 Commits

Author SHA1 Message Date
7af4eea253 GIT_SILENT Upgrade ECM and KF version requirements for 5.101.0 release. 2022-12-03 09:47:44 +00:00
a3049f6740 Fix missing DCI-P3 color space set 2022-11-24 15:02:31 +01:00
3b1e8f7054 minor tweaks in HEIF and AVIF plugins
It is mostly only about casting between types.
2022-11-19 11:38:49 +00:00
dcab3a06ab raw: LibRaw_QIODevice::read: fixed possible partial reading of an item
- If the size of an item is greater than 1 byte, it must be ensured that it is not partially read.
- In many readings, LibRAW gives an error if a partial number of items are read so I always try to read everything.
2022-11-19 10:14:16 +00:00
361f9e867e PSD multichannel testcases 2022-11-15 16:25:22 +00:00
35883aa604 Support to MCH with 4+ channels (treat as CMYK) 2022-11-15 16:25:22 +00:00
50846f224f avif: Check if encoder/decoder is available in capabilities()
The plugin can be compiled even with decoder libraries only. In a similar way of HEIC plugin I check if an encoder or a decoder is available.
2022-11-15 13:14:09 +00:00
9ad82ed608 Fix condition for installing desktop files 2022-11-07 00:37:09 +01:00
c9f32a226f GIT_SILENT Upgrade ECM and KF version requirements for 5.100.0 release. 2022-11-05 12:28:10 +00:00
a0df142408 Don't install desktop files for image formats when building against Qt6
These are only for kdelibs4support/KServiceTypeTrader
2022-11-03 21:36:38 +01:00
8586bb4719 raw: Don't seek back if we were asked to read too much
Otherwise we will return false when LibRaw::derror
calls for eof() when it is actually that we have reached eof
2022-10-30 23:59:55 +01:00
d734f28727 jxl: indicate when all frames have been read
and return correct loop count
2022-10-18 17:58:40 +02:00
afa7399b36 avif: minor fixes
libavif 0.11.0 uses new default dimension limit 32768 pixels,
we allow 65535 which was used by us previously.
Minor tweaks in jumpToNextImage, jumpToImage
to handle rare situations.
2022-10-18 15:05:15 +02:00
bfb12093ad avif: indicate when all frames have been read 2022-10-15 20:08:39 +02:00
1190e53e9b avif: always indicate endless loop
avif does not support loops but endless loop was the behavior before
460085 was fixed, so a workaround is added.

See also: https://github.com/AOMediaCodec/libavif/issues/347

CCBUG: 460085
2022-10-15 14:11:56 +08:00
350ce1b990 avif: return false in canRead() when imageIndex >= imageCount
Otherwise when `cache: false` is set in AnimatedImage, QMovie will try
to read the image forever.

BUG: 460085
FIXED-IN: 5.100
2022-10-15 14:02:38 +08:00
bcbf45e23a Add JXL test files corresponding to 8 EXIF orientation values 2022-10-13 15:47:04 +02:00
c71a7984d6 Add AVIF test files with rotation and mirror operations 2022-10-12 15:39:27 +02:00
b1f3a87896 Auto-rotate input images in readtest
Currently, there is no difference in the tests but in the future
we will add images with various transformations defined.
2022-10-12 15:33:08 +02:00
8af9a0f9d9 jxl: remove C-style casts 2022-10-11 15:38:55 +02:00
3790a89cd1 avif: Use reinterpret_cast instead C cast
No longer using int for QByteArray size because Qt6 uses qsizetype
2022-10-11 15:05:20 +02:00
f475a4b24a avif: revert 9ac923ad09 commit
Changes to libavif's avifImageRGBToYUV() API were reverted too.
2022-10-11 14:36:17 +02:00
d2f38b8b9c heif: replace C cast with static_cast 2022-10-10 09:28:41 +00:00
9ab64dbf22 heif: use heif_init/heif_deinit with libheif 1.13.0+
In recent libheif, application should use
heif_init/heif_deinit calls. Unfortunately, these calls are
not thread-safe in released 1.13.0 version yet (will be in the
future) and HEIFHandler is often created in different threads.
So we have to guard their use with a mutex.
2022-10-10 09:28:41 +00:00
20f74ce5e6 FindLibRaw: fix include dir, should not contain prefix libraw/
See also the examples at https://www.libraw.org/docs/API-CXX.html

BUG: 460105
2022-10-08 02:15:18 +02:00
54129819d5 Fix duplicated tests 2022-10-02 06:01:23 +00:00
181eb253c6 ANI partial test and PIC test added 2022-10-02 06:01:23 +00:00
c5f7ea7eac PSD: impreved support to sequential access device 2022-10-02 06:01:23 +00:00
ea14882ff7 Fix messages 2022-10-02 06:01:23 +00:00
f8bfdce285 CMakeLists: enable EXR test 2022-10-02 06:01:23 +00:00
524f083ee4 Added EXR test image 2022-10-02 06:01:23 +00:00
c96ad6ba8a Fixes for sequential devices 2022-10-02 06:01:23 +00:00
49bd131eef GIT_SILENT Upgrade ECM and KF version requirements for 5.99.0 release. 2022-10-01 13:17:36 +00:00
2a25ec7a56 Add Qt6 windows CI support 2022-09-27 15:15:36 +00:00
a8a477ae67 pcx: Do not support sequential devices
We need QIODevice::pos() to work in QDataStream &operator>>.

BUG: 459541
2022-09-24 03:45:21 +02:00
2f27dff48b Fix maximum number of channels (testcase added) 2022-09-22 21:17:10 +00:00
72a1cc23b1 Use consistently std::as_const instead of qAsConst
NO_CHANGELOG
2022-09-22 15:59:15 +02:00
6f3a326cf8 LibRaw_QIODevice::seek() avoid seek on a sequential device 2022-09-21 02:14:55 +02:00
d881a7bbb1 LibRaw_QIODevice::seek() bounding checks 2022-09-20 19:44:08 +02:00
65a20b43fc Camera RAW images plugin
Plugin to read RAW camera images based on LibRAW.

- Supersedes MR !86 
- Support to LibRaw 0.20 and 0.21-Beta
- Support to multi-shot images: use imageCount(), jumpToImage() to select the wanted shot
- By default generates 16-bits sRGB images using camera white balance and interpolation AHD
- Should fix CCBUG: 454208: on my Debian with KF5.96 and the pulgin installed, I see the preview of all my RAW files (ARW included) in Dolphin

News compared to V1 (MR !86)

- Fix possible stack overflow due to the huge size of LibRaw class
- Fix image allocation with Qt 6 (make use of QImageIOHandler::allocateImage()) 
- Support to XMP metapacket
- Support to quality option. For e.g. you can focus on quality (q = 10) or speed (q = 1)
- oss-fuzz available [here](https://github.com/mircomir/oss-fuzz/tree/raw_fuzz/projects/kimageformats)
2022-09-19 23:52:43 +00:00
84941b7690 .gitlab-ci.yml: enable static builds 2022-09-18 18:41:54 +00:00
21928300c6 Enables opening of XCF files with Width and/or Height greater than 32K 2022-09-13 17:57:57 +00:00
024d199ed0 Replace C cast with reinterpret_cast 2022-09-11 11:01:55 +02:00
9ac923ad09 avif: adjust for libavif breaking change in YUV<->RGB conversion 2022-09-09 17:54:18 +02:00
feb6d9b20f Fix image allocation with Qt 6
To make the plugins fail to allocate if the image size is greater than QImageReader::allocationLimit() it is necessary to allocate the image with QImageIOHandler::allocateImage().

Note that not all plugins have been changed and some others are not tested in the CI (maybe due to missing libraries).

PS: the following message is printed by QImageIOHandler::allocateImage() if the size is exceeded: "qt.gui.imageio: QImageIOHandler: Rejecting image as it exceeds the current allocation limit of XXX megabytes"
2022-09-07 14:03:33 +00:00
dfbc6e0f8c GIT_SILENT Upgrade ECM and KF version requirements for 5.98.0 release. 2022-09-05 09:27:11 +00:00
43543f96bc Add FreeBSD Qt6 CI support 2022-08-25 08:20:16 +02:00
62e477a6f2 Protect against too big resize for a QByteArray
oss-fuzz/48480
2022-08-15 10:24:40 +00:00
e6955e1f03 GIT_SILENT Upgrade ECM and KF version requirements for 5.97.0 release. 2022-08-07 12:18:50 +00:00
6074c4d6fd Use right type on enums 2022-07-29 07:46:10 +02:00
6f44c5c52a PSD: Improve alpha detection
BUG: 182496
2022-07-25 19:34:57 +00:00
d030c75925 PSD: LAB support
LAB images support:
- Added LAB 8/16-bits support by converting them to sRGB
- The conversion are done using literature formulas (LAB -> XYZ -> sRGB): [sRGB on Wiki](https://en.wikipedia.org/wiki/SRGB), [LAB on wiki](https://en.wikipedia.org/wiki/CIELAB_color_space)
- Removed unused code
2022-07-06 21:30:23 +00:00
90 changed files with 2883 additions and 259 deletions

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
autotests/read/raw/RAW_KODAK_C330_FORMAT_NONE_YRGB.raw binary

View File

@ -3,8 +3,12 @@
include:
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux-static.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/android.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/freebsd.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux-qt6.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/android-qt6.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/windows.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/windows-static.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/freebsd-qt6.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/windows-qt6.yml

View File

@ -3,12 +3,12 @@ cmake_minimum_required(VERSION 3.16)
project(KImageFormats)
include(FeatureSummary)
find_package(ECM 5.96.0 NO_MODULE)
find_package(ECM 5.101.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)
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH})
set(CMAKE_MODULE_PATH ${KImageFormats_SOURCE_DIR}/cmake/find-modules ${ECM_MODULE_PATH})
include(KDEInstallDirs)
include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE)
@ -70,6 +70,13 @@ if(KIMAGEFORMATS_JXL)
endif()
add_feature_info(LibJXL LibJXL_FOUND "required for the QImage plugin for JPEG XL images")
# note: module FindLibRaw missing from https://invent.kde.org/frameworks/extra-cmake-modules
find_package(LibRaw 0.20.2)
set_package_properties(LibRaw PROPERTIES
TYPE OPTIONAL
PURPOSE "Required for the QImage plugin for RAW images"
)
ecm_set_disabled_deprecation_versions(
QT 5.15.2
KF 5.95

View File

@ -18,6 +18,7 @@ The following image formats have read-only support:
- OpenEXR (exr)
- Photoshop documents (psd, psb, pdd, psdt)
- Sun Raster (ras)
- Camera RAW images (arw, cr2, cr3, dcs, dng, ...)
The following image formats have read and write support:

View File

@ -136,7 +136,15 @@ kimageformats_write_tests(
# kimageformats_write_tests(eps)
#endif()
if (OpenEXR_FOUND)
# FIXME: OpenEXR tests
kimageformats_read_tests(
exr
)
endif()
if (LibRaw_FOUND)
kimageformats_read_tests(
raw
)
endif()
find_package(Qt${QT_MAJOR_VERSION}Test ${REQUIRED_QT_VERSION} CONFIG QUIET)

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 MiB

File diff suppressed because one or more lines are too long

View File

@ -9,6 +9,7 @@
#include <QCommandLineParser>
#include <QCoreApplication>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QImage>
#include <QImageReader>
@ -18,6 +19,44 @@
#include "fuzzyeq.cpp"
/**
* @brief The SequentialFile class
* Class to make a file a sequential access device. This class is used to check if the plugins could works
* on a sequential device such as a socket.
*/
class SequentialFile : public QFile
{
public:
SequentialFile()
: QFile()
{
}
explicit SequentialFile(const QString &name)
: QFile(name)
{
}
#ifndef QT_NO_QOBJECT
explicit SequentialFile(QObject *parent)
: QFile(parent)
{
}
SequentialFile(const QString &name, QObject *parent)
: QFile(name, parent)
{
}
#endif
bool isSequential() const override
{
return true;
}
qint64 size() const override
{
return bytesAvailable();
}
};
static void writeImageData(const char *name, const QString &filename, const QImage &image)
{
QFile file(filename);
@ -56,7 +95,7 @@ int main(int argc, char **argv)
QCoreApplication::removeLibraryPath(QStringLiteral(PLUGIN_DIR));
QCoreApplication::addLibraryPath(QStringLiteral(PLUGIN_DIR));
QCoreApplication::setApplicationName(QStringLiteral("readtest"));
QCoreApplication::setApplicationVersion(QStringLiteral("1.0.0"));
QCoreApplication::setApplicationVersion(QStringLiteral("1.1.0"));
QCommandLineParser parser;
parser.setApplicationDescription(QStringLiteral("Performs basic image conversion checking."));
@ -94,11 +133,11 @@ int main(int argc, char **argv)
QByteArray format = suffix.toLatin1();
QDir imgdir(QLatin1String(IMAGEDIR "/") + suffix);
imgdir.setNameFilters(QStringList(QLatin1String("*.") + suffix));
imgdir.setFilter(QDir::Files);
int passed = 0;
int failed = 0;
int skipped = 0;
QTextStream(stdout) << "********* "
<< "Starting basic read tests for " << suffix << " images *********\n";
@ -112,69 +151,91 @@ int main(int argc, char **argv)
QTextStream(stdout) << "QImageReader::supportedImageFormats: " << formatStrings.join(", ") << "\n";
const QFileInfoList lstImgDir = imgdir.entryInfoList();
for (const QFileInfo &fi : lstImgDir) {
int suffixPos = fi.filePath().count() - suffix.count();
QString inputfile = fi.filePath();
QString expfile = fi.filePath().replace(suffixPos, suffix.count(), QStringLiteral("png"));
QString expfilename = QFileInfo(expfile).fileName();
QImageReader inputReader(inputfile, format);
QImageReader expReader(expfile, "png");
QImage inputImage;
QImage expImage;
if (!expReader.read(&expImage)) {
QTextStream(stdout) << "ERROR: " << fi.fileName() << ": could not load " << expfilename << ": " << expReader.errorString() << "\n";
++failed;
continue;
}
if (!inputReader.canRead()) {
QTextStream(stdout) << "FAIL : " << fi.fileName() << ": failed can read: " << inputReader.errorString() << "\n";
++failed;
continue;
}
if (!inputReader.read(&inputImage)) {
QTextStream(stdout) << "FAIL : " << fi.fileName() << ": failed to load: " << inputReader.errorString() << "\n";
++failed;
continue;
}
if (expImage.width() != inputImage.width()) {
QTextStream(stdout) << "FAIL : " << fi.fileName() << ": width was " << inputImage.width() << " but " << expfilename << " width was "
<< expImage.width() << "\n";
++failed;
} else if (expImage.height() != inputImage.height()) {
QTextStream(stdout) << "FAIL : " << fi.fileName() << ": height was " << inputImage.height() << " but " << expfilename << " height was "
<< expImage.height() << "\n";
++failed;
// Launch 2 runs for each test: first run on a random access device, second run on a sequential access device
for (int seq = 0; seq < 2; ++seq) {
if (seq) {
QTextStream(stdout) << "* Run on SEQUENTIAL ACCESS device\n";
} else {
QImage::Format inputFormat = preferredFormat(inputImage.format());
QImage::Format expFormat = preferredFormat(expImage.format());
QImage::Format cmpFormat = inputFormat == expFormat ? inputFormat : QImage::Format_ARGB32;
QTextStream(stdout) << "* Run on RANDOM ACCESS device\n";
}
for (const QFileInfo &fi : lstImgDir) {
if (!fi.suffix().compare("png", Qt::CaseInsensitive)) {
continue;
}
int suffixPos = fi.filePath().count() - suffix.count();
QString inputfile = fi.filePath();
QString expfile = fi.filePath().replace(suffixPos, suffix.count(), QStringLiteral("png"));
QString expfilename = QFileInfo(expfile).fileName();
if (inputImage.format() != cmpFormat) {
QTextStream(stdout) << "INFO : " << fi.fileName() << ": converting " << fi.fileName() << " from " << formatToString(inputImage.format())
<< " to " << formatToString(cmpFormat) << '\n';
inputImage = inputImage.convertToFormat(cmpFormat);
}
if (expImage.format() != cmpFormat) {
QTextStream(stdout) << "INFO : " << fi.fileName() << ": converting " << expfilename << " from " << formatToString(expImage.format()) << " to "
<< formatToString(cmpFormat) << '\n';
expImage = expImage.convertToFormat(cmpFormat);
}
if (fuzzyeq(inputImage, expImage, fuzziness)) {
QTextStream(stdout) << "PASS : " << fi.fileName() << "\n";
++passed;
} else {
QTextStream(stdout) << "FAIL : " << fi.fileName() << ": differs from " << expfilename << "\n";
writeImageData("expected data", fi.fileName() + QLatin1String("-expected.data"), expImage);
writeImageData("actual data", fi.fileName() + QLatin1String("-actual.data"), inputImage);
std::unique_ptr<QIODevice> inputDevice(seq ? new SequentialFile(inputfile) : new QFile(inputfile));
QImageReader inputReader(inputDevice.get(), format);
QImageReader expReader(expfile, "png");
QImage inputImage;
QImage expImage;
// inputImage is auto-rotated to final orientation
inputReader.setAutoTransform(true);
if (!expReader.read(&expImage)) {
QTextStream(stdout) << "ERROR: " << fi.fileName() << ": could not load " << expfilename << ": " << expReader.errorString() << "\n";
++failed;
continue;
}
if (!inputReader.canRead()) {
// All plugins must pass the test on a random device.
// canRead() must also return false if the plugin is unable to run on a sequential device.
if (inputDevice->isSequential()) {
QTextStream(stdout) << "SKIP : " << fi.fileName() << ": cannot read on a sequential device (don't worry, it's ok)\n";
++skipped;
} else {
QTextStream(stdout) << "FAIL : " << fi.fileName() << ": failed can read: " << inputReader.errorString() << "\n";
++failed;
}
continue;
}
if (!inputReader.read(&inputImage)) {
QTextStream(stdout) << "FAIL : " << fi.fileName() << ": failed to load: " << inputReader.errorString() << "\n";
++failed;
continue;
}
if (expImage.width() != inputImage.width()) {
QTextStream(stdout) << "FAIL : " << fi.fileName() << ": width was " << inputImage.width() << " but " << expfilename << " width was "
<< expImage.width() << "\n";
++failed;
} else if (expImage.height() != inputImage.height()) {
QTextStream(stdout) << "FAIL : " << fi.fileName() << ": height was " << inputImage.height() << " but " << expfilename << " height was "
<< expImage.height() << "\n";
++failed;
} else {
QImage::Format inputFormat = preferredFormat(inputImage.format());
QImage::Format expFormat = preferredFormat(expImage.format());
QImage::Format cmpFormat = inputFormat == expFormat ? inputFormat : QImage::Format_ARGB32;
if (inputImage.format() != cmpFormat) {
QTextStream(stdout) << "INFO : " << fi.fileName() << ": converting " << fi.fileName() << " from " << formatToString(inputImage.format())
<< " to " << formatToString(cmpFormat) << '\n';
inputImage = inputImage.convertToFormat(cmpFormat);
}
if (expImage.format() != cmpFormat) {
QTextStream(stdout) << "INFO : " << fi.fileName() << ": converting " << expfilename << " from " << formatToString(expImage.format())
<< " to " << formatToString(cmpFormat) << '\n';
expImage = expImage.convertToFormat(cmpFormat);
}
if (fuzzyeq(inputImage, expImage, fuzziness)) {
QTextStream(stdout) << "PASS : " << fi.fileName() << "\n";
++passed;
} else {
QTextStream(stdout) << "FAIL : " << fi.fileName() << ": differs from " << expfilename << "\n";
writeImageData("expected data", fi.fileName() + QLatin1String("-expected.data"), expImage);
writeImageData("actual data", fi.fileName() + QLatin1String("-actual.data"), inputImage);
++failed;
}
}
}
}
QTextStream(stdout) << "Totals: " << passed << " passed, " << failed << " failed\n";
QTextStream(stdout) << "Totals: " << passed << " passed, " << skipped << " skipped, " << failed << " failed\n";
QTextStream(stdout) << "********* "
<< "Finished basic read tests for " << suffix << " images *********\n";

View File

@ -0,0 +1,86 @@
# - Find LibRaw
# Find the LibRaw library <https://www.libraw.org>
# This module defines
# LibRaw_VERSION, the version string of LibRaw
# LibRaw_INCLUDE_DIR, where to find libraw/libraw.h
# LibRaw_LIBRARIES, the libraries needed to use LibRaw (non-thread-safe)
# LibRaw_r_LIBRARIES, the libraries needed to use LibRaw (thread-safe)
# LibRaw_DEFINITIONS, the definitions needed to use LibRaw (non-thread-safe)
# LibRaw_r_DEFINITIONS, the definitions needed to use LibRaw (thread-safe)
#
# SPDX-FileCopyrightText: 2013 Pino Toscano <pino at kde dot org>
# SPDX-FileCopyrightText: 2013 Gilles Caulier <caulier dot gilles at gmail dot com>
#
# SPDX-License-Identifier: BSD-3-Clause
FIND_PACKAGE(PkgConfig)
IF(PKG_CONFIG_FOUND)
PKG_CHECK_MODULES(PC_LIBRAW libraw)
SET(LibRaw_DEFINITIONS ${PC_LIBRAW_CFLAGS_OTHER})
PKG_CHECK_MODULES(PC_LIBRAW_R libraw_r)
SET(LibRaw_r_DEFINITIONS ${PC_LIBRAW_R_CFLAGS_OTHER})
ENDIF()
FIND_PATH(LibRaw_INCLUDE_DIR libraw/libraw.h
HINTS
${PC_LIBRAW_INCLUDEDIR}
${PC_LibRaw_INCLUDE_DIRS}
)
FIND_LIBRARY(LibRaw_LIBRARIES NAMES raw
HINTS
${PC_LIBRAW_LIBDIR}
${PC_LIBRAW_LIBRARY_DIRS}
)
FIND_LIBRARY(LibRaw_r_LIBRARIES NAMES raw_r
HINTS
${PC_LIBRAW_R_LIBDIR}
${PC_LIBRAW_R_LIBRARY_DIRS}
)
IF(LibRaw_INCLUDE_DIR)
FILE(READ ${LibRaw_INCLUDE_DIR}/libraw/libraw_version.h _libraw_version_content)
STRING(REGEX MATCH "#define LIBRAW_MAJOR_VERSION[ \t]*([0-9]*)\n" _version_major_match ${_libraw_version_content})
SET(_libraw_version_major "${CMAKE_MATCH_1}")
STRING(REGEX MATCH "#define LIBRAW_MINOR_VERSION[ \t]*([0-9]*)\n" _version_minor_match ${_libraw_version_content})
SET(_libraw_version_minor "${CMAKE_MATCH_1}")
STRING(REGEX MATCH "#define LIBRAW_PATCH_VERSION[ \t]*([0-9]*)\n" _version_patch_match ${_libraw_version_content})
SET(_libraw_version_patch "${CMAKE_MATCH_1}")
IF(_version_major_match AND _version_minor_match AND _version_patch_match)
SET(LibRaw_VERSION "${_libraw_version_major}.${_libraw_version_minor}.${_libraw_version_patch}")
ELSE()
IF(NOT LibRaw_FIND_QUIETLY)
MESSAGE(STATUS "Failed to get version information from ${LibRaw_INCLUDE_DIR}/libraw/libraw_version.h")
ENDIF()
ENDIF()
ENDIF()
INCLUDE(FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(LibRaw
REQUIRED_VARS LibRaw_LIBRARIES LibRaw_INCLUDE_DIR
VERSION_VAR LibRaw_VERSION
)
MARK_AS_ADVANCED(LibRaw_VERSION
LibRaw_INCLUDE_DIR
LibRaw_LIBRARIES
LibRaw_r_LIBRARIES
LibRaw_DEFINITIONS
LibRaw_r_DEFINITIONS
)
if(LibRaw_FOUND AND NOT TARGET LibRaw::LibRaw)
add_library(LibRaw::LibRaw UNKNOWN IMPORTED)
set_target_properties(LibRaw::LibRaw PROPERTIES
IMPORTED_LOCATION "${LibRaw_LIBRARIES}"
INTERFACE_COMPILE_OPTIONS "${LibRaw_DEFINITIONS}"
INTERFACE_INCLUDE_DIRECTORIES "${LibRaw_INCLUDE_DIR}"
)
endif()

View File

@ -20,19 +20,26 @@ endfunction()
##################################
kimageformats_add_plugin(kimg_ani SOURCES ani.cpp)
install(FILES ani.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES ani.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
##################################
if (TARGET avif)
kimageformats_add_plugin(kimg_avif SOURCES "avif.cpp")
target_link_libraries(kimg_avif "avif")
install(FILES avif.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES avif.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
endif()
##################################
install(FILES dds-qt.desktop RENAME dds.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES dds-qt.desktop RENAME dds.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
##################################
@ -40,14 +47,18 @@ if (BUILD_EPS_PLUGIN)
if (TARGET Qt${QT_MAJOR_VERSION}::PrintSupport)
kimageformats_add_plugin(kimg_eps SOURCES eps.cpp)
target_link_libraries(kimg_eps Qt${QT_MAJOR_VERSION}::PrintSupport)
install(FILES eps.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES eps.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
endif()
endif()
##################################
# need this for Qt's version of the plugin
install(FILES jp2.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
if (QT_MAJOR_VERSION STREQUAL "5")
# need this for Qt's version of the plugin
install(FILES jp2.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
##################################
@ -65,13 +76,17 @@ if(OpenEXR_FOUND)
endif()
kde_target_enable_exceptions(kimg_exr PRIVATE)
install(FILES exr.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES exr.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
endif()
##################################
kimageformats_add_plugin(kimg_hdr SOURCES hdr.cpp)
install(FILES hdr.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES hdr.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
##################################
@ -79,7 +94,10 @@ if (LibHeif_FOUND)
kimageformats_add_plugin(kimg_heif SOURCES heif.cpp)
target_link_libraries(kimg_heif PkgConfig::LibHeif)
kde_target_enable_exceptions(kimg_heif PRIVATE)
install(FILES heif.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES heif.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
endif()
##################################
@ -90,43 +108,71 @@ if (LibJXL_FOUND AND LibJXLThreads_FOUND)
if (LibJXL_VERSION VERSION_GREATER_EQUAL "0.7.0")
target_compile_definitions(kimg_jxl PRIVATE KIMG_JXL_API_VERSION=70)
endif()
install(FILES jxl.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES jxl.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
endif()
##################################
kimageformats_add_plugin(kimg_pcx SOURCES pcx.cpp)
install(FILES pcx.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES pcx.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
##################################
kimageformats_add_plugin(kimg_pic SOURCES pic.cpp)
install(FILES pic.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES pic.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
##################################
kimageformats_add_plugin(kimg_psd SOURCES psd.cpp)
install(FILES psd.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES psd.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
##################################
kimageformats_add_plugin(kimg_ras SOURCES ras.cpp)
install(FILES ras.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES ras.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
##################################
kimageformats_add_plugin(kimg_rgb SOURCES rgb.cpp)
install(FILES rgb.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES rgb.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
##################################
kimageformats_add_plugin(kimg_tga SOURCES tga.cpp)
install(FILES tga.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES tga.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
##################################
kimageformats_add_plugin(kimg_xcf SOURCES xcf.cpp)
install(FILES xcf.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES xcf.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
##################################
if (LibRaw_FOUND)
kimageformats_add_plugin(kimg_raw SOURCES raw.cpp)
kde_enable_exceptions()
target_link_libraries(kimg_raw LibRaw::LibRaw)
if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES raw.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
endif()
##################################
@ -134,10 +180,14 @@ if (KF5Archive_FOUND)
kimageformats_add_plugin(kimg_kra SOURCES kra.cpp)
target_link_libraries(kimg_kra KF5::Archive)
install(FILES kra.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES kra.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
kimageformats_add_plugin(kimg_ora SOURCES ora.cpp)
target_link_libraries(kimg_ora KF5::Archive)
install(FILES ora.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
if (QT_MAJOR_VERSION STREQUAL "5")
install(FILES ora.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
endif()
endif()

View File

@ -521,6 +521,9 @@ bool ANIHandler::canRead(QIODevice *device)
qWarning("ANIHandler::canRead() called with no device");
return false;
}
if (device->isSequential()) {
return false;
}
const QByteArray riffIntro = device->peek(12);

View File

@ -12,6 +12,8 @@
#include <QColorSpace>
#include "avif_p.h"
#include "util_p.h"
#include <cfloat>
QAVIFHandler::QAVIFHandler()
@ -40,6 +42,11 @@ bool QAVIFHandler::canRead() const
if (m_parseState != ParseAvifError) {
setFormat("avif");
if (m_parseState == ParseAvifFinished) {
return false;
}
return true;
}
return false;
@ -56,7 +63,7 @@ bool QAVIFHandler::canRead(QIODevice *device)
}
avifROData input;
input.data = (const uint8_t *)header.constData();
input.data = reinterpret_cast<const uint8_t *>(header.constData());
input.size = header.size();
if (avifPeekCompatibleFileType(&input)) {
@ -67,7 +74,7 @@ bool QAVIFHandler::canRead(QIODevice *device)
bool QAVIFHandler::ensureParsed() const
{
if (m_parseState == ParseAvifSuccess || m_parseState == ParseAvifMetadata) {
if (m_parseState == ParseAvifSuccess || m_parseState == ParseAvifMetadata || m_parseState == ParseAvifFinished) {
return true;
}
if (m_parseState == ParseAvifError) {
@ -81,7 +88,7 @@ bool QAVIFHandler::ensureParsed() const
bool QAVIFHandler::ensureOpened() const
{
if (m_parseState == ParseAvifSuccess) {
if (m_parseState == ParseAvifSuccess || m_parseState == ParseAvifFinished) {
return true;
}
if (m_parseState == ParseAvifError) {
@ -109,7 +116,7 @@ bool QAVIFHandler::ensureDecoder()
m_rawData = device()->readAll();
m_rawAvifData.data = (const uint8_t *)m_rawData.constData();
m_rawAvifData.data = reinterpret_cast<const uint8_t *>(m_rawData.constData());
m_rawAvifData.size = m_rawData.size();
if (avifPeekCompatibleFileType(&m_rawAvifData) == AVIF_FALSE) {
@ -130,6 +137,10 @@ bool QAVIFHandler::ensureDecoder()
m_decoder->strictFlags = AVIF_STRICT_DISABLED;
#endif
#if AVIF_VERSION >= 110000
m_decoder->imageDimensionLimit = 65535;
#endif
avifResult decodeResult;
decodeResult = avifDecoderSetIOMemory(m_decoder, m_rawAvifData.data, m_rawAvifData.size);
@ -235,8 +246,8 @@ bool QAVIFHandler::decode_one_frame()
resultformat = QImage::Format_RGB32;
}
}
QImage result(m_decoder->image->width, m_decoder->image->height, resultformat);
QImage result = imageAlloc(m_decoder->image->width, m_decoder->image->height, resultformat);
if (result.isNull()) {
qWarning("Memory cannot be allocated");
return false;
@ -244,7 +255,7 @@ bool QAVIFHandler::decode_one_frame()
QColorSpace colorspace;
if (m_decoder->image->icc.data && (m_decoder->image->icc.size > 0)) {
const QByteArray icc_data((const char *)m_decoder->image->icc.data, (int)m_decoder->image->icc.size);
const QByteArray icc_data(reinterpret_cast<const char *>(m_decoder->image->icc.data), m_decoder->image->icc.size);
colorspace = QColorSpace::fromIccProfile(icc_data);
if (!colorspace.isValid()) {
qWarning("AVIF image has Qt-unsupported or invalid ICC profile!");
@ -452,6 +463,13 @@ bool QAVIFHandler::read(QImage *image)
*image = m_current_image;
if (imageCount() >= 2) {
m_must_jump_to_next_image = true;
if (m_decoder->imageIndex >= m_decoder->imageCount - 1) {
// all frames in animation have been read
m_parseState = ParseAvifFinished;
}
} else {
// the static image has been read
m_parseState = ParseAvifFinished;
}
return true;
}
@ -749,7 +767,7 @@ bool QAVIFHandler::write(const QImage &image)
avif->transferCharacteristics = transfer_to_save;
if (iccprofile.size() > 0) {
avifImageSetProfileICC(avif, (const uint8_t *)iccprofile.constData(), iccprofile.size());
avifImageSetProfileICC(avif, reinterpret_cast<const uint8_t *>(iccprofile.constData()), iccprofile.size());
}
avifRGBImage rgb;
@ -800,7 +818,7 @@ bool QAVIFHandler::write(const QImage &image)
avifImageDestroy(avif);
if (res == AVIF_RESULT_OK) {
qint64 status = device()->write((const char *)raw.data, raw.size);
qint64 status = device()->write(reinterpret_cast<const char *>(raw.data), raw.size);
avifRWDataFree(&raw);
if (status > 0) {
@ -903,6 +921,7 @@ bool QAVIFHandler::jumpToNextImage()
if (m_decoder->imageIndex >= 0) {
if (m_decoder->imageCount < 2) {
m_parseState = ParseAvifSuccess;
return true;
}
@ -947,10 +966,12 @@ bool QAVIFHandler::jumpToImage(int imageNumber)
if (m_decoder->imageCount < 2) { // not an animation
if (imageNumber == 0) {
return ensureOpened();
} else {
return false;
if (ensureOpened()) {
m_parseState = ParseAvifSuccess;
return true;
}
}
return false;
}
if (imageNumber < 0 || imageNumber >= m_decoder->imageCount) { // wrong index
@ -959,6 +980,7 @@ bool QAVIFHandler::jumpToImage(int imageNumber)
if (imageNumber == m_decoder->imageIndex) { // we are here already
m_must_jump_to_next_image = false;
m_parseState = ParseAvifSuccess;
return true;
}
@ -1017,7 +1039,8 @@ int QAVIFHandler::loopCount() const
return 0;
}
return 1;
// Endless loop to work around https://github.com/AOMediaCodec/libavif/issues/347
return -1;
}
QPointF QAVIFHandler::CompatibleChromacity(qreal chrX, qreal chrY)
@ -1034,12 +1057,26 @@ QPointF QAVIFHandler::CompatibleChromacity(qreal chrX, qreal chrY)
QImageIOPlugin::Capabilities QAVIFPlugin::capabilities(QIODevice *device, const QByteArray &format) const
{
static const bool isAvifDecoderAvailable(avifCodecName(AVIF_CODEC_CHOICE_AUTO, AVIF_CODEC_FLAG_CAN_DECODE) != nullptr);
static const bool isAvifEncoderAvailable(avifCodecName(AVIF_CODEC_CHOICE_AUTO, AVIF_CODEC_FLAG_CAN_ENCODE) != nullptr);
if (format == "avif") {
return Capabilities(CanRead | CanWrite);
Capabilities format_cap;
if (isAvifDecoderAvailable) {
format_cap |= CanRead;
}
if (isAvifEncoderAvailable) {
format_cap |= CanWrite;
}
return format_cap;
}
if (format == "avifs") {
return Capabilities(CanRead);
Capabilities format_cap;
if (isAvifDecoderAvailable) {
format_cap |= CanRead;
}
return format_cap;
}
if (!format.isEmpty()) {
@ -1050,10 +1087,10 @@ QImageIOPlugin::Capabilities QAVIFPlugin::capabilities(QIODevice *device, const
}
Capabilities cap;
if (device->isReadable() && QAVIFHandler::canRead(device)) {
if (device->isReadable() && QAVIFHandler::canRead(device) && isAvifDecoderAvailable) {
cap |= CanRead;
}
if (device->isWritable()) {
if (device->isWritable() && isAvifEncoderAvailable) {
cap |= CanWrite;
}
return cap;

View File

@ -55,6 +55,7 @@ private:
ParseAvifNotParsed = 0,
ParseAvifSuccess = 1,
ParseAvifMetadata = 2,
ParseAvifFinished = 3,
};
ParseAvifState m_parseState;

View File

@ -8,6 +8,7 @@
*/
#include "exr_p.h"
#include "util_p.h"
#include <IexThrowErrnoExc.h>
#include <ImathBox.h>
@ -191,7 +192,7 @@ bool EXRHandler::read(QImage *outImage)
width = dw.max.x - dw.min.x + 1;
height = dw.max.y - dw.min.y + 1;
QImage image(width, height, QImage::Format_RGB32);
QImage image = imageAlloc(width, height, QImage::Format_RGB32);
if (image.isNull()) {
qWarning() << "Failed to allocate image, invalid size?" << QSize(width, height);
return false;

View File

@ -7,6 +7,7 @@
*/
#include "hdr_p.h"
#include "util_p.h"
#include <QDataStream>
#include <QImage>
@ -93,7 +94,7 @@ static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &i
uchar code;
// Create dst image.
img = QImage(width, height, QImage::Format_RGB32);
img = imageAlloc(width, height, QImage::Format_RGB32);
if (img.isNull()) {
qCDebug(HDRPLUGIN) << "Couldn't create image with size" << width << height << "and format RGB32";
return false;

View File

@ -9,11 +9,13 @@
#include "heif_p.h"
#include "libheif/heif_cxx.h"
#include "util_p.h"
#include <QColorSpace>
#include <QDebug>
#include <QPointF>
#include <QSysInfo>
#include <limits>
#include <string.h>
namespace // Private.
@ -51,6 +53,11 @@ private:
} // namespace
size_t HEIFHandler::m_initialized_count = 0;
bool HEIFHandler::m_plugins_queried = false;
bool HEIFHandler::m_heif_decoder_available = false;
bool HEIFHandler::m_heif_encoder_available = false;
HEIFHandler::HEIFHandler()
: m_parseState(ParseHeicNotParsed)
, m_quality(100)
@ -87,6 +94,21 @@ bool HEIFHandler::write(const QImage &image)
return false;
}
#if LIBHEIF_HAVE_VERSION(1, 13, 0)
startHeifLib();
#endif
bool success = write_helper(image);
#if LIBHEIF_HAVE_VERSION(1, 13, 0)
finishHeifLib();
#endif
return success;
}
bool HEIFHandler::write_helper(const QImage &image)
{
int save_depth; // 8 or 10bit per channel
QImage::Format tmpformat; // format for temporary image
const bool save_alpha = image.hasAlphaChannel();
@ -355,8 +377,18 @@ bool HEIFHandler::ensureParsed() const
HEIFHandler *that = const_cast<HEIFHandler *>(this);
return that->ensureDecoder();
#if LIBHEIF_HAVE_VERSION(1, 13, 0)
startHeifLib();
#endif
bool success = that->ensureDecoder();
#if LIBHEIF_HAVE_VERSION(1, 13, 0)
finishHeifLib();
#endif
return success;
}
bool HEIFHandler::ensureDecoder()
{
if (m_parseState != ParseHeicNotParsed) {
@ -374,7 +406,7 @@ bool HEIFHandler::ensureDecoder()
try {
heif::Context ctx;
ctx.read_from_memory_without_copy((const void *)(buffer.constData()), buffer.size());
ctx.read_from_memory_without_copy(static_cast<const void *>(buffer.constData()), buffer.size());
heif::ImageHandle handle = ctx.get_primary_image_handle();
@ -432,7 +464,7 @@ bool HEIFHandler::ensureDecoder()
return false;
}
m_current_image = QImage(imageSize, target_image_format);
m_current_image = imageAlloc(imageSize, target_image_format);
if (m_current_image.isNull()) {
m_parseState = ParseHeicError;
qWarning() << "Unable to allocate memory!";
@ -606,8 +638,8 @@ bool HEIFHandler::ensureDecoder()
heif_color_profile_type profileType = heif_image_handle_get_color_profile_type(handle.get_raw_image_handle());
struct heif_error err;
if (profileType == heif_color_profile_type_prof || profileType == heif_color_profile_type_rICC) {
int rawProfileSize = (int)heif_image_handle_get_raw_color_profile_size(handle.get_raw_image_handle());
if (rawProfileSize > 0) {
size_t rawProfileSize = heif_image_handle_get_raw_color_profile_size(handle.get_raw_image_handle());
if (rawProfileSize > 0 && rawProfileSize < std::numeric_limits<int>::max()) {
QByteArray ba(rawProfileSize, 0);
err = heif_image_handle_get_raw_color_profile(handle.get_raw_image_handle(), ba.data());
if (err.code) {
@ -619,7 +651,7 @@ bool HEIFHandler::ensureDecoder()
}
}
} else {
qWarning() << "icc profile is empty";
qWarning() << "icc profile is empty or above limits";
}
} else if (profileType == heif_color_profile_type_nclx) {
@ -695,14 +727,100 @@ bool HEIFHandler::ensureDecoder()
return true;
}
bool HEIFHandler::isHeifDecoderAvailable()
{
QMutexLocker locker(&getHEIFHandlerMutex());
if (!m_plugins_queried) {
#if LIBHEIF_HAVE_VERSION(1, 13, 0)
if (m_initialized_count == 0) {
heif_init(nullptr);
}
#endif
m_heif_encoder_available = heif_have_encoder_for_format(heif_compression_HEVC);
m_heif_decoder_available = heif_have_decoder_for_format(heif_compression_HEVC);
m_plugins_queried = true;
#if LIBHEIF_HAVE_VERSION(1, 13, 0)
if (m_initialized_count == 0) {
heif_deinit();
}
#endif
}
return m_heif_decoder_available;
}
bool HEIFHandler::isHeifEncoderAvailable()
{
QMutexLocker locker(&getHEIFHandlerMutex());
if (!m_plugins_queried) {
#if LIBHEIF_HAVE_VERSION(1, 13, 0)
if (m_initialized_count == 0) {
heif_init(nullptr);
}
#endif
m_heif_decoder_available = heif_have_decoder_for_format(heif_compression_HEVC);
m_heif_encoder_available = heif_have_encoder_for_format(heif_compression_HEVC);
m_plugins_queried = true;
#if LIBHEIF_HAVE_VERSION(1, 13, 0)
if (m_initialized_count == 0) {
heif_deinit();
}
#endif
}
return m_heif_encoder_available;
}
void HEIFHandler::startHeifLib()
{
#if LIBHEIF_HAVE_VERSION(1, 13, 0)
QMutexLocker locker(&getHEIFHandlerMutex());
if (m_initialized_count == 0) {
heif_init(nullptr);
}
m_initialized_count++;
#endif
}
void HEIFHandler::finishHeifLib()
{
#if LIBHEIF_HAVE_VERSION(1, 13, 0)
QMutexLocker locker(&getHEIFHandlerMutex());
if (m_initialized_count == 0) {
return;
}
m_initialized_count--;
if (m_initialized_count == 0) {
heif_deinit();
}
#endif
}
QMutex &HEIFHandler::getHEIFHandlerMutex()
{
static QMutex heif_handler_mutex;
return heif_handler_mutex;
}
QImageIOPlugin::Capabilities HEIFPlugin::capabilities(QIODevice *device, const QByteArray &format) const
{
if (format == "heif" || format == "heic") {
Capabilities format_cap;
if (heif_have_decoder_for_format(heif_compression_HEVC)) {
if (HEIFHandler::isHeifDecoderAvailable()) {
format_cap |= CanRead;
}
if (heif_have_encoder_for_format(heif_compression_HEVC)) {
if (HEIFHandler::isHeifEncoderAvailable()) {
format_cap |= CanWrite;
}
return format_cap;
@ -715,11 +833,11 @@ QImageIOPlugin::Capabilities HEIFPlugin::capabilities(QIODevice *device, const Q
}
Capabilities cap;
if (device->isReadable() && HEIFHandler::canRead(device) && heif_have_decoder_for_format(heif_compression_HEVC)) {
if (device->isReadable() && HEIFHandler::canRead(device) && HEIFHandler::isHeifDecoderAvailable()) {
cap |= CanRead;
}
if (device->isWritable() && heif_have_encoder_for_format(heif_compression_HEVC)) {
if (device->isWritable() && HEIFHandler::isHeifEncoderAvailable()) {
cap |= CanWrite;
}
return cap;

View File

@ -13,6 +13,7 @@
#include <QByteArray>
#include <QImage>
#include <QImageIOPlugin>
#include <QMutex>
class HEIFHandler : public QImageIOHandler
{
@ -29,6 +30,9 @@ public:
void setOption(ImageOption option, const QVariant &value) override;
bool supportsOption(ImageOption option) const override;
static bool isHeifDecoderAvailable();
static bool isHeifEncoderAvailable();
private:
static bool isSupportedBMFFType(const QByteArray &header);
bool ensureParsed() const;
@ -43,6 +47,18 @@ private:
ParseHeicState m_parseState;
int m_quality;
QImage m_current_image;
bool write_helper(const QImage &image);
static void startHeifLib();
static void finishHeifLib();
static size_t m_initialized_count;
static bool m_plugins_queried;
static bool m_heif_decoder_available;
static bool m_heif_encoder_available;
static QMutex &getHEIFHandlerMutex();
};
class HEIFPlugin : public QImageIOPlugin

View File

@ -10,6 +10,8 @@
#include <QtGlobal>
#include "jxl_p.h"
#include "util_p.h"
#include <jxl/encode.h>
#include <jxl/thread_parallel_runner.h>
#include <string.h>
@ -46,6 +48,11 @@ bool QJpegXLHandler::canRead() const
if (m_parseState != ParseJpegXLError) {
setFormat("jxl");
if (m_parseState == ParseJpegXLFinished) {
return false;
}
return true;
}
return false;
@ -61,7 +68,7 @@ bool QJpegXLHandler::canRead(QIODevice *device)
return false;
}
JxlSignature signature = JxlSignatureCheck((const uint8_t *)header.constData(), header.size());
JxlSignature signature = JxlSignatureCheck(reinterpret_cast<const uint8_t *>(header.constData()), header.size());
if (signature == JXL_SIG_CODESTREAM || signature == JXL_SIG_CONTAINER) {
return true;
}
@ -70,7 +77,7 @@ bool QJpegXLHandler::canRead(QIODevice *device)
bool QJpegXLHandler::ensureParsed() const
{
if (m_parseState == ParseJpegXLSuccess || m_parseState == ParseJpegXLBasicInfoParsed) {
if (m_parseState == ParseJpegXLSuccess || m_parseState == ParseJpegXLBasicInfoParsed || m_parseState == ParseJpegXLFinished) {
return true;
}
if (m_parseState == ParseJpegXLError) {
@ -88,7 +95,7 @@ bool QJpegXLHandler::ensureALLCounted() const
return false;
}
if (m_parseState == ParseJpegXLSuccess) {
if (m_parseState == ParseJpegXLSuccess || m_parseState == ParseJpegXLFinished) {
return true;
}
@ -109,7 +116,7 @@ bool QJpegXLHandler::ensureDecoder()
return false;
}
JxlSignature signature = JxlSignatureCheck((const uint8_t *)m_rawData.constData(), m_rawData.size());
JxlSignature signature = JxlSignatureCheck(reinterpret_cast<const uint8_t *>(m_rawData.constData()), m_rawData.size());
if (signature != JXL_SIG_CODESTREAM && signature != JXL_SIG_CONTAINER) {
m_parseState = ParseJpegXLError;
return false;
@ -137,7 +144,7 @@ bool QJpegXLHandler::ensureDecoder()
}
}
if (JxlDecoderSetInput(m_decoder, (const uint8_t *)m_rawData.constData(), m_rawData.size()) != JXL_DEC_SUCCESS) {
if (JxlDecoderSetInput(m_decoder, reinterpret_cast<const uint8_t *>(m_rawData.constData()), m_rawData.size()) != JXL_DEC_SUCCESS) {
qWarning("ERROR: JxlDecoderSetInput failed");
m_parseState = ParseJpegXLError;
return false;
@ -271,8 +278,12 @@ bool QJpegXLHandler::countALLFrames()
size_t icc_size = 0;
if (JxlDecoderGetICCProfileSize(m_decoder, &m_input_pixel_format, JXL_COLOR_PROFILE_TARGET_DATA, &icc_size) == JXL_DEC_SUCCESS) {
if (icc_size > 0) {
QByteArray icc_data((int)icc_size, 0);
if (JxlDecoderGetColorAsICCProfile(m_decoder, &m_input_pixel_format, JXL_COLOR_PROFILE_TARGET_DATA, (uint8_t *)icc_data.data(), icc_data.size())
QByteArray icc_data(icc_size, 0);
if (JxlDecoderGetColorAsICCProfile(m_decoder,
&m_input_pixel_format,
JXL_COLOR_PROFILE_TARGET_DATA,
reinterpret_cast<uint8_t *>(icc_data.data()),
icc_data.size())
== JXL_DEC_SUCCESS) {
m_colorspace = QColorSpace::fromIccProfile(icc_data);
@ -359,7 +370,7 @@ bool QJpegXLHandler::decode_one_frame()
return false;
}
m_current_image = QImage(m_basicinfo.xsize, m_basicinfo.ysize, m_input_image_format);
m_current_image = imageAlloc(m_basicinfo.xsize, m_basicinfo.ysize, m_input_image_format);
if (m_current_image.isNull()) {
qWarning("Memory cannot be allocated");
m_parseState = ParseJpegXLError;
@ -395,7 +406,15 @@ bool QJpegXLHandler::decode_one_frame()
if (!rewind()) {
return false;
}
// all frames in animation have been read
m_parseState = ParseJpegXLFinished;
} else {
m_parseState = ParseJpegXLSuccess;
}
} else {
// the static image has been read
m_parseState = ParseJpegXLFinished;
}
return true;
@ -607,7 +626,7 @@ bool QJpegXLHandler::write(const QImage &image)
}
if (!convert_color_profile && iccprofile.size() > 0) {
status = JxlEncoderSetICCProfile(encoder, (const uint8_t *)iccprofile.constData(), iccprofile.size());
status = JxlEncoderSetICCProfile(encoder, reinterpret_cast<const uint8_t *>(iccprofile.constData()), iccprofile.size());
if (status != JXL_ENC_SUCCESS) {
qWarning("JxlEncoderSetICCProfile failed!");
if (runner) {
@ -646,7 +665,7 @@ bool QJpegXLHandler::write(const QImage &image)
#endif
if (image.hasAlphaChannel() || ((save_depth == 8) && (xsize % 4 == 0))) {
status = JxlEncoderAddImageFrame(encoder_options, &pixel_format, (void *)tmpimage.constBits(), buffer_size);
status = JxlEncoderAddImageFrame(encoder_options, &pixel_format, static_cast<const void *>(tmpimage.constBits()), buffer_size);
} else {
if (save_depth > 8) { // 16bit depth without alpha channel
uint16_t *tmp_buffer = new (std::nothrow) uint16_t[3 * xsize * ysize];
@ -677,7 +696,7 @@ bool QJpegXLHandler::write(const QImage &image)
src_pixels += 2; // skipalpha
}
}
status = JxlEncoderAddImageFrame(encoder_options, &pixel_format, (void *)tmp_buffer, buffer_size);
status = JxlEncoderAddImageFrame(encoder_options, &pixel_format, static_cast<const void *>(tmp_buffer), buffer_size);
delete[] tmp_buffer;
} else { // 8bit depth without alpha channel
uchar *tmp_buffer8 = new (std::nothrow) uchar[3 * xsize * ysize];
@ -696,7 +715,7 @@ bool QJpegXLHandler::write(const QImage &image)
memcpy(dest_pixels8, tmpimage.constScanLine(y), rowbytes);
dest_pixels8 += rowbytes;
}
status = JxlEncoderAddImageFrame(encoder_options, &pixel_format, (void *)tmp_buffer8, buffer_size);
status = JxlEncoderAddImageFrame(encoder_options, &pixel_format, static_cast<const void *>(tmp_buffer8), buffer_size);
delete[] tmp_buffer8;
}
}
@ -743,7 +762,7 @@ bool QJpegXLHandler::write(const QImage &image)
compressed.resize(next_out - compressed.data());
if (compressed.size() > 0) {
qint64 write_status = device()->write((const char *)compressed.data(), compressed.size());
qint64 write_status = device()->write(reinterpret_cast<const char *>(compressed.data()), compressed.size());
if (write_status > 0) {
return true;
@ -854,6 +873,7 @@ bool QJpegXLHandler::jumpToNextImage()
}
}
m_parseState = ParseJpegXLSuccess;
return true;
}
@ -868,12 +888,14 @@ bool QJpegXLHandler::jumpToImage(int imageNumber)
}
if (imageNumber == m_currentimage_index) {
m_parseState = ParseJpegXLSuccess;
return true;
}
if (imageNumber > m_currentimage_index) {
JxlDecoderSkipFrames(m_decoder, imageNumber - m_currentimage_index);
m_currentimage_index = imageNumber;
m_parseState = ParseJpegXLSuccess;
return true;
}
@ -885,6 +907,7 @@ bool QJpegXLHandler::jumpToImage(int imageNumber)
JxlDecoderSkipFrames(m_decoder, imageNumber);
}
m_currentimage_index = imageNumber;
m_parseState = ParseJpegXLSuccess;
return true;
}
@ -908,7 +931,7 @@ int QJpegXLHandler::loopCount() const
}
if (m_basicinfo.have_animation) {
return 1;
return (m_basicinfo.animation.num_loops > 0) ? m_basicinfo.animation.num_loops - 1 : -1;
} else {
return 0;
}
@ -928,7 +951,7 @@ bool QJpegXLHandler::rewind()
}
}
if (JxlDecoderSetInput(m_decoder, (const uint8_t *)m_rawData.constData(), m_rawData.size()) != JXL_DEC_SUCCESS) {
if (JxlDecoderSetInput(m_decoder, reinterpret_cast<const uint8_t *>(m_rawData.constData()), m_rawData.size()) != JXL_DEC_SUCCESS) {
qWarning("ERROR: JxlDecoderSetInput failed");
m_parseState = ParseJpegXLError;
return false;

View File

@ -57,6 +57,7 @@ private:
ParseJpegXLNotParsed = 0,
ParseJpegXLSuccess = 1,
ParseJpegXLBasicInfoParsed = 2,
ParseJpegXLFinished = 3,
};
ParseJpegXLState m_parseState;

View File

@ -57,6 +57,9 @@ bool KraHandler::canRead(QIODevice *device)
qWarning("KraHandler::canRead() called with no device");
return false;
}
if (device->isSequential()) {
return false;
}
char buff[57];
if (device->peek(buff, sizeof(buff)) == sizeof(buff)) {

View File

@ -56,6 +56,9 @@ bool OraHandler::canRead(QIODevice *device)
qWarning("OraHandler::canRead() called with no device");
return false;
}
if (device->isSequential()) {
return false;
}
char buff[54];
if (device->peek(buff, sizeof(buff)) == sizeof(buff)) {

View File

@ -6,6 +6,7 @@
*/
#include "pcx_p.h"
#include "util_p.h"
#include <QColor>
#include <QDataStream>
@ -173,7 +174,7 @@ static QDataStream &operator>>(QDataStream &s, PCXHEADER &ph)
// Skip the rest of the header
quint8 byte;
while (s.device()->pos() < 128) {
for (auto i = 0; i < 54; ++i) {
s >> byte;
}
@ -262,7 +263,7 @@ static void readImage1(QImage &img, QDataStream &s, const PCXHEADER &header)
{
QByteArray buf(header.BytesPerLine, 0);
img = QImage(header.width(), header.height(), QImage::Format_Mono);
img = imageAlloc(header.width(), header.height(), QImage::Format_Mono);
img.setColorCount(2);
if (img.isNull()) {
@ -294,7 +295,7 @@ static void readImage4(QImage &img, QDataStream &s, const PCXHEADER &header)
QByteArray buf(header.BytesPerLine * 4, 0);
QByteArray pixbuf(header.width(), 0);
img = QImage(header.width(), header.height(), QImage::Format_Indexed8);
img = imageAlloc(header.width(), header.height(), QImage::Format_Indexed8);
img.setColorCount(16);
if (img.isNull()) {
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());
@ -338,7 +339,7 @@ static void readImage8(QImage &img, QDataStream &s, const PCXHEADER &header)
{
QByteArray buf(header.BytesPerLine, 0);
img = QImage(header.width(), header.height(), QImage::Format_Indexed8);
img = imageAlloc(header.width(), header.height(), QImage::Format_Indexed8);
img.setColorCount(256);
if (img.isNull()) {
@ -388,7 +389,7 @@ static void readImage24(QImage &img, QDataStream &s, const PCXHEADER &header)
QByteArray g_buf(header.BytesPerLine, 0);
QByteArray b_buf(header.BytesPerLine, 0);
img = QImage(header.width(), header.height(), QImage::Format_RGB32);
img = imageAlloc(header.width(), header.height(), QImage::Format_RGB32);
if (img.isNull()) {
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());

View File

@ -14,8 +14,8 @@
*/
#include "pic_p.h"
#include "rle_p.h"
#include "util_p.h"
#include <QDataStream>
#include <QDebug>
@ -238,7 +238,7 @@ bool SoftimagePICHandler::read(QImage *image)
}
}
QImage img(m_header.width, m_header.height, fmt);
QImage img = imageAlloc(m_header.width, m_header.height, fmt);
if (img.isNull()) {
qDebug() << "Failed to allocate image, invalid dimensions?" << QSize(m_header.width, m_header.height) << fmt;
return false;

View File

@ -3,7 +3,7 @@
SPDX-FileCopyrightText: 2003 Ignacio Castaño <castano@ludicon.com>
SPDX-FileCopyrightText: 2015 Alex Merry <alex.merry@kde.org>
SPDX-FileCopyrightText: 2022 Mirco Miranda <mirco.miranda@systemceramics.com>
SPDX-FileCopyrightText: 2022 Mirco Miranda <mircomir@outlook.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
@ -22,17 +22,18 @@
* Limitations of the current code:
* - 32-bit float image are converted to 16-bit integer image.
* NOTE: Qt 6.2 allow 32-bit float images (RGB only)
* - Other color spaces cannot be read due to lack of QImage support for
* color spaces other than RGB (and Grayscale): a conversion to
* RGB must be done.
* - The best way to convert between different color spaces is to use a
* color management engine (e.g. LittleCMS).
* - An approximate way is to ignore the color information and use
* literature formulas (possible but not recommended).
* - Other color spaces cannot directly be read due to lack of QImage support for
* color spaces other than RGB (and Grayscale). Where possible, a conversion
* to RGB is done:
* - CMYK images are converted using an approximated way that ignores the color
* information (ICC profile).
* - LAB images are converted to sRGB using literature formulas.
*
* NOTE: The best way to convert between different color spaces is to use a
* color management engine (e.g. LittleCMS).
*/
#include "psd_p.h"
#include "util_p.h"
#include <QDataStream>
@ -40,13 +41,35 @@
#include <QImage>
#include <QColorSpace>
#include <cmath>
typedef quint32 uint;
typedef quint16 ushort;
typedef quint8 uchar;
/* The fast LAB conversion converts the image to linear sRgb instead to sRgb.
* This should not be a problem because the Qt's QColorSpace supports the linear
* sRgb colorspace.
*
* Using linear conversion, the loading speed is improved by 4x. Anyway, if you are using
* an software that discard color info, you should comment it.
*
* At the time I'm writing (07/2022), Gwenview and Krita supports linear sRgb but KDE
* preview creator does not. This is the why, for now, it is disabled.
*/
//#define PSD_FAST_LAB_CONVERSION
namespace // Private.
{
enum ColorMode {
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,
@ -65,6 +88,12 @@ enum ImageResourceId : quint16 {
IRI_XMPMETADATA = 0x0424
};
enum LayerId : quint32 {
LI_MT16 = 0x4D743136, // 'Mt16',
LI_MT32 = 0x4D743332, // 'Mt32',
LI_MTRN = 0x4D74726E // 'Mtrn'
};
struct PSDHeader {
uint signature;
ushort version;
@ -101,10 +130,58 @@ struct PSDColorModeDataSection {
using PSDImageResourceSection = QHash<quint16, PSDImageResourceBlock>;
struct PSDLayerAndMaskSection {
struct PSDLayerInfo {
qint64 size = -1;
qint16 layerCount = 0;
};
struct PSDGlobalLayerMaskInfo {
qint64 size = -1;
};
struct PSDAdditionalLayerInfo {
Signature signature = Signature();
LayerId id = LayerId();
qint64 size = -1;
};
struct PSDLayerAndMaskSection {
qint64 size = -1;
PSDLayerInfo layerInfo;
PSDGlobalLayerMaskInfo globalLayerMaskInfo;
QHash<LayerId, PSDAdditionalLayerInfo> additionalLayerInfo;
bool isNull() const {
return (size <= 0);
}
bool hasAlpha() const {
return layerInfo.layerCount < 0 ||
additionalLayerInfo.contains(LI_MT16) ||
additionalLayerInfo.contains(LI_MT32) ||
additionalLayerInfo.contains(LI_MTRN);
}
bool atEnd(bool isPsb) const {
qint64 currentSize = 0;
if (layerInfo.size > -1) {
currentSize += layerInfo.size + 4;
if (isPsb)
currentSize += 4;
}
if (globalLayerMaskInfo.size > -1) {
currentSize += globalLayerMaskInfo.size + 4;
}
auto aliv = additionalLayerInfo.values();
for (auto &&v : aliv) {
currentSize += (12 + v.size);
if (v.signature == S_8B64)
currentSize += 4;
}
return (size <= currentSize);
}
};
/*!
* \brief fixedPointToDouble
* Converts a fixed point number to floating point one.
@ -116,21 +193,28 @@ static double fixedPointToDouble(qint32 fixedPoint)
return (i+d);
}
static bool skip_section(QDataStream &s, bool psb = false)
static qint64 readSize(QDataStream &s, bool psb = false)
{
qint64 section_length;
qint64 size = 0;
if (!psb) {
quint32 tmp;
s >> tmp;
section_length = tmp;
size = tmp;
}
else {
s >> section_length;
s >> size;
}
if (s.status() != QDataStream::Ok) {
size = -1;
}
return size;
}
static bool skip_data(QDataStream &s, qint64 size)
{
// Skip mode data.
for (qint32 i32 = 0; section_length; section_length -= i32) {
i32 = std::min(section_length, qint64(std::numeric_limits<qint32>::max()));
for (qint32 i32 = 0; size; size -= i32) {
i32 = std::min(size, qint64(std::numeric_limits<qint32>::max()));
i32 = s.skipRawData(i32);
if (i32 < 1)
return false;
@ -138,6 +222,14 @@ static bool skip_section(QDataStream &s, bool psb = false)
return true;
}
static bool skip_section(QDataStream &s, bool psb = false)
{
auto section_length = readSize(s, psb);
if (section_length < 0)
return false;
return skip_data(s, section_length);
}
/*!
* \brief readPascalString
* Reads the Pascal string as defined in the PSD specification.
@ -195,12 +287,6 @@ static PSDImageResourceSection readImageResourceSection(QDataStream &s, bool *ok
qint32 sectioSize;
s >> sectioSize;
#ifdef QT_DEBUG
auto pos = qint64();
if (auto dev = s.device())
pos = dev->pos();
#endif
// Reading Image resource block
for (auto size = sectioSize; size > 0;) {
// Length Description
@ -219,7 +305,7 @@ static PSDImageResourceSection readImageResourceSection(QDataStream &s, bool *ok
s >> signature;
size -= sizeof(signature);
// NOTE: MeSa signature is not documented but found in some old PSD take from Photoshop 7.0 CD.
if (signature != 0x3842494D && signature != 0x4D655361) { // 8BIM and MeSa
if (signature != S_8BIM && signature != S_MeSa) { // 8BIM and MeSa
qDebug() << "Invalid Image Resource Block Signature!";
*ok = false;
break;
@ -265,17 +351,36 @@ static PSDImageResourceSection readImageResourceSection(QDataStream &s, bool *ok
irs.insert(id, irb);
}
#ifdef QT_DEBUG
if (auto dev = s.device()) {
if ((dev->pos() - pos) != sectioSize) {
*ok = false;
}
}
#endif
return irs;
}
PSDAdditionalLayerInfo readAdditionalLayer(QDataStream &s, bool *ok = nullptr)
{
PSDAdditionalLayerInfo li;
bool tmp = true;
if (ok == nullptr)
ok = &tmp;
s >> li.signature;
*ok = li.signature == S_8BIM || li.signature == S_8B64;
if (!*ok)
return li;
s >> li.id;
*ok = s.status() == QDataStream::Ok;
if (!*ok)
return li;
li.size = readSize(s, li.signature == S_8B64);
*ok = li.size >= 0;
if (!*ok)
return li;
*ok = skip_data(s, li.size);
return li;
}
PSDLayerAndMaskSection readLayerAndMaskSection(QDataStream &s, bool isPsb, bool *ok = nullptr)
{
@ -286,32 +391,38 @@ PSDLayerAndMaskSection readLayerAndMaskSection(QDataStream &s, bool isPsb, bool
ok = &tmp;
*ok = true;
// try to read layerCount: if less than zero, means that there is an alpha channel
if (auto device = s.device()) {
device->startTransaction();
qint64 size = 0;
if (isPsb) {
qint64 tmpSize;
s >> tmpSize; // global size
if (tmpSize >= 8)
s >> size; // layer info size
}
else {
quint32 tmpSize;
s >> tmpSize; // global size
if (tmpSize >= 4) {
s >> tmpSize; // layer info size
size = tmpSize;
}
}
auto device = s.device();
device->startTransaction();
if (s.status() == QDataStream::Ok) {
if (size >= 2)
s >> lms.layerCount;
lms.size = readSize(s, isPsb);
// read layer info
if (s.status() == QDataStream::Ok && !lms.atEnd(isPsb)) {
lms.layerInfo.size = readSize(s, isPsb);
if (lms.layerInfo.size > 0) {
s >> lms.layerInfo.layerCount;
skip_data(s, lms.layerInfo.size - sizeof(lms.layerInfo.layerCount));
}
device->rollbackTransaction();
}
// read global layer mask info
if (s.status() == QDataStream::Ok && !lms.atEnd(isPsb)) {
lms.globalLayerMaskInfo.size = readSize(s, false); // always 32-bits
if (lms.globalLayerMaskInfo.size > 0) {
skip_data(s, lms.globalLayerMaskInfo.size);
}
}
// read additional layer info
if (s.status() == QDataStream::Ok) {
for (bool ok = true; ok && !lms.atEnd(isPsb);) {
auto al = readAdditionalLayer(s, &ok);
if (ok)
lms.additionalLayerInfo.insert(al.id, al);
}
}
device->rollbackTransaction();
*ok = skip_section(s, isPsb);
return lms;
}
@ -519,7 +630,9 @@ static bool IsValid(const PSDHeader &header)
qDebug() << "PSD header: invalid color mode" << header.color_mode;
return false;
}
if (header.channel_count < 1 || header.channel_count > 56) {
// Specs tells: "Supported range is 1 to 56" but the limit is 57:
// Photoshop does not make you add more (see also 53alphas.psd test case).
if (header.channel_count < 1 || header.channel_count > 57) {
qDebug() << "PSD header: invalid number of channels" << header.channel_count;
return false;
}
@ -533,6 +646,9 @@ static bool IsValid(const PSDHeader &header)
// Check that the header is supported by this plugin.
static bool IsSupported(const PSDHeader &header)
{
if (!IsValid(header)) {
return false;
}
if (header.version != 1 && header.version != 2) {
return false;
}
@ -547,9 +663,15 @@ static bool IsSupported(const PSDHeader &header)
header.color_mode != CM_INDEXED &&
header.color_mode != CM_DUOTONE &&
header.color_mode != CM_CMYK &&
header.color_mode != CM_MULTICHANNEL &&
header.color_mode != CM_LABCOLOR &&
header.color_mode != CM_BITMAP) {
return false;
}
if (header.color_mode == CM_MULTICHANNEL &&
header.channel_count < 4) {
return false;
}
return true;
}
@ -573,7 +695,7 @@ qint64 decompress(const char *input, qint64 ilen, char *output, qint64 olen)
if (n >= 0) {
rr = qint64(n) + 1;
if (available < rr) {
ip--;
--ip;
break;
}
@ -585,7 +707,7 @@ qint64 decompress(const char *input, qint64 ilen, char *output, qint64 olen)
else if (ip < ilen) {
rr = qint64(1-n);
if (available < rr) {
ip--;
--ip;
break;
}
memset(output + j, input[ip++], size_t(rr));
@ -601,7 +723,7 @@ qint64 decompress(const char *input, qint64 ilen, char *output, qint64 olen)
* \param header The PSD header.
* \return The Qt image format.
*/
static QImage::Format imageFormat(const PSDHeader &header, qint32 alpha)
static QImage::Format imageFormat(const PSDHeader &header, bool alpha)
{
if (header.channel_count == 0) {
return QImage::Format_Invalid;
@ -611,15 +733,22 @@ static QImage::Format imageFormat(const PSDHeader &header, qint32 alpha)
switch(header.color_mode) {
case CM_RGB:
if (header.depth == 16 || header.depth == 32)
format = header.channel_count < 4 ? QImage::Format_RGBX64 : QImage::Format_RGBA64;
format = header.channel_count < 4 || !alpha ? QImage::Format_RGBX64 : QImage::Format_RGBA64;
else
format = header.channel_count < 4 ? QImage::Format_RGB888 : QImage::Format_RGBA8888;
format = header.channel_count < 4 || !alpha ? QImage::Format_RGB888 : QImage::Format_RGBA8888;
break;
case CM_CMYK: // PSD supports CMYK 8-bits and 16-bits only
case CM_MULTICHANNEL: // Treat MCH as CMYK (number of channel check is done in IsSupported())
case CM_CMYK: // Photoshop supports CMYK/MCH 8-bits and 16-bits only
if (header.depth == 16)
format = header.channel_count < 5 || alpha >= 0 ? QImage::Format_RGBX64 : QImage::Format_RGBA64;
format = header.channel_count < 5 || !alpha ? QImage::Format_RGBX64 : QImage::Format_RGBA64;
else if (header.depth == 8)
format = header.channel_count < 5 || alpha >= 0 ? QImage::Format_RGB888 : QImage::Format_RGBA8888;
format = header.channel_count < 5 || !alpha ? QImage::Format_RGB888 : QImage::Format_RGBA8888;
break;
case CM_LABCOLOR: // Photoshop supports LAB 8-bits and 16-bits only
if (header.depth == 16)
format = header.channel_count < 4 || !alpha ? QImage::Format_RGBX64 : QImage::Format_RGBA64;
else if (header.depth == 8)
format = header.channel_count < 4 || !alpha ? QImage::Format_RGB888 : QImage::Format_RGBA8888;
break;
case CM_GRAYSCALE:
case CM_DUOTONE:
@ -679,14 +808,6 @@ inline quint32 xchg(quint32 v) {
#endif
}
inline qint32 xchg(qint32 v) {
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
return qint32( (quint32(v)>>24) | ((quint32(v) & 0x00FF0000)>>8) | ((quint32(v) & 0x0000FF00)<<8) | (quint32(v)<<24) );
#else
return v; // never tested
#endif
}
template<class T>
inline void planarToChunchy(uchar *target, const char *source, qint32 width, qint32 c, qint32 cn)
{
@ -719,7 +840,7 @@ inline void monoInvert(uchar *target, const char* source, qint32 bytes)
}
template<class T>
inline void cmykToRgb(uchar *target, qint32 targetChannels, const char *source, qint32 sourceChannels, qint32 width, bool noAlpha)
inline void cmykToRgb(uchar *target, qint32 targetChannels, const char *source, qint32 sourceChannels, qint32 width, bool alpha = false)
{
auto s = reinterpret_cast<const T*>(source);
auto t = reinterpret_cast<T*>(target);
@ -732,17 +853,17 @@ inline void cmykToRgb(uchar *target, qint32 targetChannels, const char *source,
for (qint32 w = 0; w < width; ++w) {
auto ps = s + sourceChannels * w;
auto C = 1 - *(ps + 0) / double(max);
auto M = 1 - *(ps + 1) / double(max);
auto Y = 1 - *(ps + 2) / double(max);
auto K = 1 - *(ps + 3) / double(max);
auto C = 1 - *(ps + 0) / max;
auto M = 1 - *(ps + 1) / max;
auto Y = 1 - *(ps + 2) / max;
auto K = 1 - *(ps + 3) / max;
auto pt = t + targetChannels * w;
*(pt + 0) = T(std::min(max - (C * (1 - K) + K) * max + 0.5, max));
*(pt + 1) = T(std::min(max - (M * (1 - K) + K) * max + 0.5, max));
*(pt + 2) = T(std::min(max - (Y * (1 - K) + K) * max + 0.5, max));
if (targetChannels == 4) {
if (sourceChannels >= 5 && !noAlpha)
if (sourceChannels >= 5 && alpha)
*(pt + 3) = *(ps + 4);
else
*(pt + 3) = std::numeric_limits<T>::max();
@ -750,9 +871,73 @@ inline void cmykToRgb(uchar *target, qint32 targetChannels, const char *source,
}
}
inline double finv(double v)
{
return (v > 6.0 / 29.0 ? v * v * v : (v - 16.0 / 116.0) / 7.787);
}
inline double gammaCorrection(double linear)
{
#ifdef PSD_FAST_LAB_CONVERSION
return linear;
#else
// NOTE: pow() slow down the performance by a 4 factor :(
return (linear > 0.0031308 ? 1.055 * std::pow(linear, 1.0 / 2.4) - 0.055 : 12.92 * linear);
#endif
}
template<class T>
inline void labToRgb(uchar *target, qint32 targetChannels, const char *source, qint32 sourceChannels, qint32 width, bool alpha = false)
{
auto s = reinterpret_cast<const T*>(source);
auto t = reinterpret_cast<T*>(target);
auto max = double(std::numeric_limits<T>::max());
if (sourceChannels < 3) {
qDebug() << "labToRgb: image is not a valid LAB!";
return;
}
for (qint32 w = 0; w < width; ++w) {
auto ps = s + sourceChannels * w;
auto L = (*(ps + 0) / max) * 100.0;
auto A = (*(ps + 1) / max) * 255.0 - 128.0;
auto B = (*(ps + 2) / max) * 255.0 - 128.0;
// converting LAB to XYZ (D65 illuminant)
auto Y = (L + 16.0) / 116.0;
auto X = A / 500.0 + Y;
auto Z = Y - B / 200.0;
// NOTE: use the constants of the illuminant of the target RGB color space
X = finv(X) * 0.9504; // D50: * 0.9642
Y = finv(Y) * 1.0000; // D50: * 1.0000
Z = finv(Z) * 1.0888; // D50: * 0.8251
// converting XYZ to sRGB (sRGB illuminant is D65)
auto r = gammaCorrection( 3.24071 * X - 1.53726 * Y - 0.498571 * Z);
auto g = gammaCorrection(- 0.969258 * X + 1.87599 * Y + 0.0415557 * Z);
auto b = gammaCorrection( 0.0556352 * X - 0.203996 * Y + 1.05707 * Z);
auto pt = t + targetChannels * w;
*(pt + 0) = T(std::max(std::min(r * max + 0.5, max), 0.0));
*(pt + 1) = T(std::max(std::min(g * max + 0.5, max), 0.0));
*(pt + 2) = T(std::max(std::min(b * max + 0.5, max), 0.0));
if (targetChannels == 4) {
if (sourceChannels >= 4 && alpha)
*(pt + 3) = *(ps + 3);
else
*(pt + 3) = std::numeric_limits<T>::max();
}
}
}
bool readChannel(QByteArray& target, QDataStream &stream, quint32 compressedSize, quint16 compression)
{
if (compression) {
if (compressedSize > kMaxQVectorSize) {
return false;
}
QByteArray tmp;
tmp.resize(compressedSize);
if (stream.readRawData(tmp.data(), tmp.size()) != tmp.size()) {
@ -815,7 +1000,9 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
// Try to identify the nature of spots: note that this is just one of many ways to identify the presence
// of alpha channels: should work in most cases where colorspaces != RGB/Gray
auto alpha = lms.layerCount; // < 0 alpha present, > 0 spots are not alpha, 0 does not decide
auto alpha = header.color_mode == CM_RGB;
if (!lms.isNull())
alpha = lms.hasAlpha();
const QImage::Format format = imageFormat(header, alpha);
if (format == QImage::Format_Invalid) {
@ -823,7 +1010,7 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
return false;
}
img = QImage(header.width, header.height, format);
img = imageAlloc(header.width, header.height, format);
if (img.isNull()) {
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width, header.height);
return false;
@ -895,22 +1082,22 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
planarToChunchy<quint16>(scanLine, rawStride.data(), header.width, c, header.channel_count);
}
else if (header.depth == 32) { // Not currently used
// LAB float uses LAB real values: L(0 to 100), a/b(-128 to 127)
if (header.color_mode == CM_LABCOLOR && c == 0)
planarToChunchyFloat<quint32, 0, 100>(scanLine, rawStride.data(), header.width, c, header.channel_count);
else if (header.color_mode == CM_LABCOLOR && c < 3)
planarToChunchyFloat<qint32, -128, 127>(scanLine, rawStride.data(), header.width, c, header.channel_count);
else // RGB, gray, spots, etc...
planarToChunchyFloat<quint32>(scanLine, rawStride.data(), header.width, c, header.channel_count);
planarToChunchyFloat<quint32>(scanLine, rawStride.data(), header.width, c, header.channel_count);
}
}
// Conversion to RGB
if (header.color_mode == CM_CMYK) {
if (header.color_mode == CM_CMYK || header.color_mode == CM_MULTICHANNEL) {
if (header.depth == 8)
cmykToRgb<quint8>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha >= 0);
cmykToRgb<quint8>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha);
else
cmykToRgb<quint16>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha >= 0);
cmykToRgb<quint16>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha);
}
if (header.color_mode == CM_LABCOLOR) {
if (header.depth == 8)
labToRgb<quint8>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha);
else
labToRgb<quint16>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha);
}
}
}
@ -941,6 +1128,15 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
}
}
// LAB conversion generates a sRGB image
if (header.color_mode == CM_LABCOLOR) {
#ifdef PSD_FAST_LAB_CONVERSION
img.setColorSpace(QColorSpace(QColorSpace::SRgbLinear));
#else
img.setColorSpace(QColorSpace(QColorSpace::SRgb));
#endif
}
// Resolution info
if (!setResolution(img, irs)) {
// qDebug() << "No resolution info found!";
@ -1050,35 +1246,27 @@ bool PSDHandler::canRead(QIODevice *device)
return false;
}
qint64 oldPos = device->pos();
device->startTransaction();
char head[4];
qint64 readBytes = device->read(head, sizeof(head));
if (readBytes < 0) {
qWarning() << "Read failed" << device->errorString();
return false;
}
QDataStream s(device);
s.setByteOrder(QDataStream::BigEndian);
if (readBytes != sizeof(head)) {
if (device->isSequential()) {
while (readBytes > 0) {
device->ungetChar(head[readBytes-- - 1]);
}
} else {
device->seek(oldPos);
}
PSDHeader header;
s >> header;
device->rollbackTransaction();
if (s.status() != QDataStream::Ok) {
return false;
}
if (device->isSequential()) {
while (readBytes > 0) {
device->ungetChar(head[readBytes-- - 1]);
if (header.color_mode == CM_CMYK || header.color_mode == CM_LABCOLOR || header.color_mode == CM_MULTICHANNEL) {
return false;
}
} else {
device->seek(oldPos);
}
return qstrncmp(head, "8BPS", 4) == 0;
return IsSupported(header);
}
QImageIOPlugin::Capabilities PSDPlugin::capabilities(QIODevice *device, const QByteArray &format) const

View File

@ -1,7 +1,7 @@
[Desktop Entry]
Type=Service
X-KDE-ServiceTypes=QImageIOPlugins
X-KDE-ImageFormat=psd
X-KDE-ImageFormat=psd,psb,pdd,psdt
X-KDE-MimeType=image/vnd.adobe.photoshop
X-KDE-Read=true
X-KDE-Write=false

View File

@ -8,7 +8,6 @@
*/
#include "ras_p.h"
#include "util_p.h"
#include <QDataStream>
@ -152,8 +151,7 @@ static bool LoadRAS(QDataStream &s, const RasHeader &ras, QImage &img)
}
// Allocate image
img = QImage(ras.Width, ras.Height, QImage::Format_ARGB32);
img = imageAlloc(ras.Width, ras.Height, QImage::Format_ARGB32);
if (img.isNull()) {
return false;
}

913
src/imageformats/raw.cpp Normal file
View File

@ -0,0 +1,913 @@
/*
RAW support for QImage.
SPDX-FileCopyrightText: 2022 Mirco Miranda <mircomir@outlook.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "raw_p.h"
#include "util_p.h"
#include <QColorSpace>
#include <QDateTime>
#include <QDebug>
#include <QImage>
#include <QSet>
#if defined(Q_OS_WINDOWS) && !defined(NOMINMAX)
#define NOMINMAX
#endif
#include <libraw/libraw.h>
#ifdef QT_DEBUG
// This should be used to exclude the local QIODevice wrapper of the LibRaw_abstract_datastream interface.
// If the result changes then the problem is in the local wrapper and must be corrected.
// WARNING: using the LibRaw's streams instead of the local wrapper from a Qt program falls in the LOCALE
// bug when the RAW file needs the scanf_one() function (e.g. *.MOS files). See also raw_scanf_one().
//#define EXCLUDE_LibRaw_QIODevice // Uncomment this code if you think that the problem is LibRaw_QIODevice (default commented)
#endif
namespace // Private.
{
// smart pointer for processed image
using pi_unique_ptr = std::unique_ptr<libraw_processed_image_t, std::function<void(libraw_processed_image_t *)>>;
// clang-format off
// Known formats supported by LibRaw (in alphabetical order and lower case)
const auto supported_formats = QSet<QByteArray>{
"3fr",
"arw", "arq",
"bay", "bmq",
"cr2", "cr3", "cap", "cine", "cs1", "crw",
"dcs", "dc2", "dcr", "dng", "drf", "dxo",
"eip", "erf",
"fff",
"hdr",
"iiq",
"k25", "kc2", "kdc",
"mdc", "mef", "mfw", "mos", "mrw",
"nef", "nrw",
"obm", "orf", "ori",
"pef", "ptx", "pxn",
"qtk",
"r3d", "raf", "raw", "rdc", "rw2", "rwl", "rwz",
"sr2", "srf", "srw", "sti",
"x3f"
};
// clang-format on
inline int raw_scanf_one(const QByteArray &ba, const char *fmt, void *val)
{
// WARNING: Here it would be nice to use sscanf like LibRaw does but there
// is a Big Trouble: THE LOCALE! LibRaw is also affected by the
// issue if used in a Qt program:
// If you use sscanf here the conversion is wrong when performed
// with Italian locale (where the decimal separator is the comma).
// The solution should be to use std::locale::global() but it's global!
// I don't want to do it. So, don't use code like that:
// return sscanf(QByteArray(ba).append('\0').data(), fmt, val);
// LibRaw is asking only "%d" and "%f" for now. This code is not affected
// by the LOCALE bug.
auto s = QString::fromLatin1(ba);
if (strcmp(fmt, "%d") == 0) {
auto ok = false;
auto d = QLocale::c().toInt(s, &ok);
if (!ok) {
return EOF;
}
*(static_cast<int *>(val)) = d;
} else {
auto ok = false;
auto f = QLocale::c().toFloat(s, &ok);
if (!ok) {
return EOF;
}
*(static_cast<float *>(val)) = f;
}
return 1;
}
/**
* @brief The LibRaw_QIODevice class
* Implementation of the LibRaw stream interface over a QIODevice.
*/
class LibRaw_QIODevice : public LibRaw_abstract_datastream
{
public:
explicit LibRaw_QIODevice(QIODevice *device)
{
m_device = device;
}
virtual ~LibRaw_QIODevice() override
{
}
virtual int valid() override
{
return m_device != nullptr;
}
virtual int read(void *ptr, size_t sz, size_t nmemb) override
{
qint64 read = 0;
if (sz == 0) {
return 0;
}
auto data = reinterpret_cast<char*>(ptr);
for (qint64 r = 0, size = sz * nmemb; read < size; read += r) {
if (m_device->atEnd()) {
break;
}
r = m_device->read(data + read, size - read);
if (r < 1) {
break;
}
}
return read / sz;
}
virtual int eof() override
{
return m_device->atEnd() ? 1 : 0;
}
virtual int seek(INT64 o, int whence) override
{
auto pos = o;
auto size = m_device->size();
if (whence == SEEK_CUR) {
pos = m_device->pos() + o;
}
if (whence == SEEK_END) {
pos = size + o;
}
if (pos < 0 || pos > size || m_device->isSequential()) {
return -1;
}
return m_device->seek(pos) ? 0 : -1;
}
virtual INT64 tell() override
{
return m_device->pos();
}
virtual INT64 size() override
{
return m_device->size();
}
virtual char *gets(char *s, int sz) override
{
if (m_device->readLine(s, sz) > 0) {
return s;
}
return nullptr;
}
virtual int scanf_one(const char *fmt, void *val) override
{
QByteArray ba;
for (int xcnt = 0; xcnt < 24 && !m_device->atEnd(); ++xcnt) {
char c;
if (!m_device->getChar(&c)) {
return EOF;
}
if (ba.isEmpty() && (c == ' ' || c == '\t')) {
continue;
}
if (c == '\0' || c == ' ' || c == '\t' || c == '\n') {
break;
}
ba.append(c);
}
return raw_scanf_one(ba, fmt, val);
}
virtual int get_char() override
{
unsigned char c;
if (!m_device->getChar(reinterpret_cast<char *>(&c))) {
return EOF;
}
return int(c);
}
#if (LIBRAW_VERSION < LIBRAW_MAKE_VERSION(0, 21, 0)) || defined(LIBRAW_OLD_VIDEO_SUPPORT)
virtual void *make_jas_stream() override
{
return nullptr;
}
#endif
private:
QIODevice *m_device;
};
bool addTag(const QString &tag, QStringList &lines)
{
auto ok = !tag.isEmpty();
if (ok) {
lines << tag;
}
return ok;
}
QString createTag(QString value, const char *tag)
{
if (!value.isEmpty()) {
value = QStringLiteral("<%1>%2</%1>").arg(QString::fromLatin1(tag), value);
}
return value;
}
QString createTag(char *asciiz, const char *tag)
{
auto value = QString::fromUtf8(asciiz);
return createTag(value, tag);
}
QString createTimeTag(time_t time, const char *tag)
{
auto value = QDateTime::fromSecsSinceEpoch(time, Qt::UTC);
if (value.isValid() && time > 0) {
return createTag(value.toString(Qt::ISODate), tag);
}
return QString();
}
QString createFlashTag(short flash, const char *tag)
{
QStringList l;
auto lc = QLocale::c();
// EXIF specifications
auto t = QStringLiteral("true");
auto f = QStringLiteral("false");
l << QStringLiteral("<exif:Fired>%1</exif:Fired>").arg((flash & 1) ? t : f);
l << QStringLiteral("<exif:Function>%1</exif:Function>").arg((flash & (1 << 5)) ? t : f);
l << QStringLiteral("<exif:RedEyeMode>%1</exif:RedEyeMode>").arg((flash & (1 << 6)) ? t : f);
l << QStringLiteral("<exif:Mode>%1</exif:Mode>").arg(lc.toString((int(flash) >> 3) & 3));
l << QStringLiteral("<exif:Return>%1</exif:Return>").arg(lc.toString((int(flash) >> 1) & 3));
return createTag(l.join(QChar()), tag);
}
QString createTag(quint64 n, const char *tag, quint64 invalid = 0)
{
if (n != invalid) {
return createTag(QLocale::c().toString(n), tag);
}
return QString();
}
QString createTag(qint16 n, const char *tag, qint16 invalid = 0)
{
if (n != invalid) {
return createTag(QLocale::c().toString(n), tag);
}
return QString();
}
QString createTag(quint16 n, const char *tag, quint16 invalid = 0)
{
if (n != invalid) {
return createTag(QLocale::c().toString(n), tag);
}
return QString();
}
QString createTag(float f, const char *tag, qint32 mul = 1)
{
if (f != 0) {
if (mul > 1)
return QStringLiteral("<%1>%2/%3</%1>").arg(QString::fromLatin1(tag), QLocale::c().toString(int(f * mul))).arg(mul);
return QStringLiteral("<%1>%2</%1>").arg(QString::fromLatin1(tag), QLocale::c().toString(f));
}
return QString();
}
QString createTag(libraw_gps_info_t gps, const char *tag)
{
auto tmp = QString::fromLatin1(tag);
if (tmp.contains(QStringLiteral("Latitude"), Qt::CaseInsensitive)) {
if (gps.latref != '\0') {
auto lc = QLocale::c();
auto value = QStringLiteral("%1,%2%3")
.arg(lc.toString(gps.latitude[0], 'f', 0))
.arg(lc.toString(gps.latitude[1] + gps.latitude[2] / 60, 'f', 4))
.arg(QChar::fromLatin1(gps.latref));
return createTag(value, tag);
}
}
if (tmp.contains(QStringLiteral("Longitude"), Qt::CaseInsensitive)) {
if (gps.longref != '\0') {
auto lc = QLocale::c();
auto value = QStringLiteral("%1,%2%3")
.arg(lc.toString(gps.longitude[0], 'f', 0))
.arg(lc.toString(gps.longitude[1] + gps.longitude[2] / 60, 'f', 4))
.arg(QChar::fromLatin1(gps.longref));
return createTag(value, tag);
}
}
if (tmp.contains(QStringLiteral("Altitude"), Qt::CaseInsensitive)) {
if (gps.altitude != 0) {
return createTag(gps.altitude, tag, 1000);
}
}
return QString();
}
QString createXmpPacket()
{
QStringList lines;
lines << QStringLiteral("<?xpacket begin=\"\" id=\"W5M0MpCehiHzreSzNTczkc9d\"?>");
lines << QStringLiteral("<x:xmpmeta xmlns:x=\"adobe:ns:meta/\" x:xmptk=\"KIMG RAW Plugin\">");
lines << QStringLiteral("<rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">");
lines << QStringLiteral("</rdf:RDF>");
lines << QStringLiteral("</x:xmpmeta>");
for (auto i = 30; i > 0; --i)
lines << QString::fromLatin1(QByteArray(80, ' '));
lines << QStringLiteral("<?xpacket end=\"w\"?>");
return lines.join(QChar::fromLatin1('\n'));
}
QString updateXmpPacket(const QString &xmpPacket, LibRaw *rawProcessor)
{
auto rdfEnd = xmpPacket.indexOf(QStringLiteral("</rdf:RDF>"), Qt::CaseInsensitive);
if (rdfEnd < 0) {
return updateXmpPacket(createXmpPacket(), rawProcessor);
}
auto lines = QStringList() << xmpPacket.left(rdfEnd);
lines << QStringLiteral("<rdf:Description rdf:about=\"\"");
lines << QStringLiteral(" xmlns:xmp=\"http://ns.adobe.com/xap/1.0/\"");
lines << QStringLiteral(" xmlns:dc=\"http://purl.org/dc/elements/1.1/\"");
lines << QStringLiteral(" xmlns:aux=\"http://ns.adobe.com/exif/1.0/aux/\"");
lines << QStringLiteral(" xmlns:xmpMM=\"http://ns.adobe.com/xap/1.0/mm/\"");
lines << QStringLiteral(" xmlns:stEvt=\"http://ns.adobe.com/xap/1.0/sType/ResourceEvent#\"");
lines << QStringLiteral(" xmlns:stRef=\"http://ns.adobe.com/xap/1.0/sType/ResourceRef#\"");
lines << QStringLiteral(" xmlns:tiff=\"http://ns.adobe.com/tiff/1.0/\"");
lines << QStringLiteral(" xmlns:exif=\"http://ns.adobe.com/exif/1.0/\"");
lines << QStringLiteral(" xmlns:xmpRights=\"http://ns.adobe.com/xap/1.0/rights/\">");
lines << QStringLiteral("<xmpMM:History>");
lines << QStringLiteral("<rdf:Seq>");
lines << QStringLiteral("<rdf:li rdf:parseType=\"Resource\">");
lines << QStringLiteral("<stEvt:action>converted</stEvt:action>");
lines << QStringLiteral("<stEvt:parameters>Converted from RAW to Qt Image using KIMG RAW plugin</stEvt:parameters>");
lines << QStringLiteral("<stEvt:softwareAgent>LibRaw %1</stEvt:softwareAgent>").arg(QString::fromLatin1(LibRaw::version()));
lines << QStringLiteral("<stEvt:when>%1</stEvt:when>").arg(QDateTime::currentDateTimeUtc().toString(Qt::ISODate));
lines << QStringLiteral("</rdf:li>");
lines << QStringLiteral("</rdf:Seq>");
lines << QStringLiteral("</xmpMM:History>");
auto &&iparams = rawProcessor->imgdata.idata;
addTag(createTag(iparams.normalized_model, "tiff:Model"), lines);
addTag(createTag(iparams.normalized_make, "tiff:Make"), lines);
addTag(createTag(iparams.software, "xmp:CreatorTool"), lines);
auto &&iother = rawProcessor->imgdata.other;
addTag(createTag(createTag(createTag(iother.desc, "rdf:li"), "rdf:Alt"), "dc:description"), lines);
addTag(createTag(createTag(createTag(iother.artist, "rdf:li"), "rdf:Seq"), "dc:creator"), lines);
addTag(createTag(createTag(createTag(iother.iso_speed, "rdf:li"), "rdf:Seq"), "exif:ISOSpeedRatings"), lines);
addTag(createTag(iother.shutter, "exif:ExposureTime", 1000), lines);
addTag(createTag(iother.aperture, "exif:ApertureValue", 1000), lines);
addTag(createTag(iother.focal_len, "exif:FocalLength", 1000), lines);
addTag(createTimeTag(iother.timestamp, "xmp:CreateDate"), lines);
addTag(createTag(iother.parsed_gps, "exif:GPSLatitude"), lines);
addTag(createTag(iother.parsed_gps, "exif:GPSLongitude"), lines);
addTag(createTag(iother.parsed_gps, "exif:GPSAltitude"), lines);
auto &&shotinfo = rawProcessor->imgdata.shootinginfo;
addTag(createTag(shotinfo.ExposureMode, "exif:ExposureMode", short(-1)), lines);
addTag(createTag(shotinfo.MeteringMode, "exif:MeteringMode", short(-1)), lines);
addTag(createTag(shotinfo.BodySerial, "aux:SerialNumber"), lines);
auto &&color = rawProcessor->imgdata.color;
addTag(createFlashTag(color.flash_used, "exif:Flash"), lines);
auto &&lens = rawProcessor->imgdata.lens;
addTag(createTag(lens.FocalLengthIn35mmFormat, "exif:FocalLengthIn35mmFilm"), lines);
addTag(createTag(lens.Lens, "aux:Lens"), lines);
addTag(createTag(lens.LensSerial, "aux:LensSerialNumber"), lines);
addTag(createTag(lens.nikon.LensIDNumber ? quint64(lens.nikon.LensIDNumber) : lens.makernotes.LensID, "aux:LensID"), lines);
auto &&makernotes = rawProcessor->imgdata.makernotes;
addTag(createTag(makernotes.common.firmware, "aux:Firmware"), lines);
lines << QStringLiteral("</rdf:Description>");
lines << xmpPacket.mid(rdfEnd);
return lines.join(QChar::fromLatin1('\n'));
}
template<class T>
inline void rgbToRgbX(uchar *target, const uchar *source, qint32 targetSize, qint32 sourceSize)
{
auto s = reinterpret_cast<const T *>(source);
auto t = reinterpret_cast<T *>(target);
auto width = std::min(targetSize / 4, sourceSize / 3) / qint32(sizeof(T));
for (qint32 x = 0; x < width; ++x) {
t[x * 4 + 0] = s[x * 3 + 0];
t[x * 4 + 1] = s[x * 3 + 1];
t[x * 4 + 2] = s[x * 3 + 2];
t[x * 4 + 3] = std::numeric_limits<T>::max();
}
}
// clang-format off
#define C_IQ(a) (((a) & 0xF) << 4)
#define C_OC(a) (((a) & 0xF) << 8)
#define C_CW(a) (((a) & 0x1) << 12)
#define C_AW(a) (((a) & 0x1) << 13)
#define C_BT(a) (((a) & 0x1) << 14)
#define C_HS(a) (((a) & 0x1) << 15)
#define C_CE(a) (((a) & 0x1) << 16)
#define C_NR(a) (((a) & 0x3) << 17)
#define C_FC(a) (((a) & 0x1) << 19)
#define C_SR(a) (((a) & 0x1) << 20)
#define C_PRESET(a) ((a) & 0xF)
#define T_IQ(a) (((a) >> 4) & 0xF)
#define T_OC(a) (((a) >> 8) & 0xF)
#define T_CW(a) (((a) >> 12) & 0x1)
#define T_AW(a) (((a) >> 13) & 0x1)
#define T_BT(a) (((a) >> 14) & 0x1)
#define T_HS(a) (((a) >> 15) & 0x1)
#define T_CE(a) (((a) >> 16) & 0x1)
#define T_NR(a) (((a) >> 17) & 0x3)
#define T_FC(a) (((a) >> 19) & 0x1)
#define T_SR(a) (((a) >> 20) & 0x1)
#define T_PRESET(a) ((a) & 0xF)
// clang-format on
#define DEFAULT_QUALITY (C_IQ(3) | C_OC(1) | C_CW(1) | C_AW(1) | C_BT(1) | C_HS(0))
void setParams(QImageIOHandler *handler, LibRaw *rawProcessor)
{
// *** Set raw params
#if (LIBRAW_VERSION < LIBRAW_MAKE_VERSION(0, 21, 0))
auto &&rawparams = rawProcessor->imgdata.params;
#else
auto &&rawparams = rawProcessor->imgdata.rawparams;
#endif
// Select one raw image from input file (0 - first, ...)
rawparams.shot_select = handler->currentImageNumber();
// *** Set processing parameters
// NOTE: The default value set below are the ones that gave the best results
// on a large sample of images (https://raw.pixls.us/data/)
/**
* @brief quality
* Plugin quality option.
*/
qint32 quality = -1;
if (handler->supportsOption(QImageIOHandler::Quality)) {
quality = handler->option(QImageIOHandler::Quality).toInt();
}
if (quality < 0) {
quality = DEFAULT_QUALITY;
}
switch (T_PRESET(quality)) {
case 0:
break;
case 1:
quality = C_IQ(0) | C_OC(1) | C_CW(1) | C_AW(1) | C_BT(0) | C_HS(1);
break;
case 2:
quality = C_IQ(0) | C_OC(1) | C_CW(1) | C_AW(1) | C_BT(0) | C_HS(0);
break;
case 3:
quality = C_IQ(3) | C_OC(1) | C_CW(1) | C_AW(1) | C_BT(0) | C_HS(0);
break;
case 4:
quality = C_IQ(3) | C_OC(1) | C_CW(1) | C_AW(1) | C_BT(1) | C_HS(0);
break;
case 5:
quality = C_IQ(3) | C_OC(2) | C_CW(1) | C_AW(1) | C_BT(1) | C_HS(0);
break;
case 6:
quality = C_IQ(3) | C_OC(4) | C_CW(1) | C_AW(1) | C_BT(1) | C_HS(0);
break;
case 7:
quality = C_IQ(11) | C_OC(1) | C_CW(1) | C_AW(1) | C_BT(0) | C_HS(0);
break;
case 8:
quality = C_IQ(11) | C_OC(1) | C_CW(1) | C_AW(1) | C_BT(1) | C_HS(0);
break;
case 9:
quality = C_IQ(11) | C_OC(2) | C_CW(1) | C_AW(1) | C_BT(1) | C_HS(0);
break;
case 10:
quality = C_IQ(11) | C_OC(4) | C_CW(1) | C_AW(1) | C_BT(1) | C_HS(0);
break;
default:
quality = DEFAULT_QUALITY;
break;
}
auto &&params = rawProcessor->imgdata.params;
/**
* @brief use_camera_wb
* Use camera white balance, if possible (0 - off, 1 - on)
*
* This should to be set. Alternatively, a calibrated white balance should be set on each camera.
*/
params.use_camera_wb = T_CW(quality);
/*!
* \brief use_auto_wb
* Average the whole image for white balance (0 - off, 1 - on)
*
* This is usefull if no camera white balance is available.
*/
params.use_auto_wb = T_AW(quality);
/**
* @brief output_bps
* Bits per pixel (8 or 16)
*
* Professional cameras (and even some smartphones) generate images at 10 or more bits per sample.
* When using 16-bit images, the highest quality should be maintained.
*/
params.output_bps = T_BT(quality) ? 16 : 8;
/**
* @brief output_color
* Output colorspace (0 - raw, 1 - sRGB, 2 - Adobe, 3 - Wide, 4 - ProPhoto, 5 - XYZ, 6 - ACES, 7 - DCI-P3, 8 - Rec2020)
*
* sRGB allows you to view images correctly on programs that do not support ICC profiles. When most
* Programs will support icc profiles, ProPhoto may be a better choice.
* @note sRgb is the LibRaw default: if grayscale image is loaded, LibRaw switches to 0 (Raw) automatically.
*/
params.output_color = T_OC(quality);
/**
* @brief user_qual
* Interpolation quality (0 - linear, 1 - VNG, 2 - PPG, 3 - AHD, 4 - DCB, 11 - DHT, 12 - AAHD)
*
* DHT seems the best option - See In-Depth Demosaicing Algorithm Analysis (https://www.libraw.org/node/2306)
* but, when used, some FUJI RAF files of my library are poorly rendered (e.g. completely green). This is the
* why I used AHD: a good compromise between quality and performance with no rendering errors.
*/
params.user_qual = T_IQ(quality);
/**
* @brief half_size
* Generate an half-size image (0 - off, 1 - on)
*
* Very fast and useful for generating previews.
*/
params.half_size = T_HS(quality);
/**
* @dcb_enhance_fl
* DCB color enhance filter (0 - off, 1 - on)
*/
params.dcb_enhance_fl = T_CE(quality);
/**
* @fbdd_noiserd
* FBDD noise reduction (0 - off, 1 - light, 2 - full)
*/
params.fbdd_noiserd = std::min(2, T_NR(quality));
/**
* @four_color_rgb
* Interpolate RGGB as four colors (0 - off, 1 - on)
*/
params.four_color_rgb = T_FC(quality);
/**
* @use_fuji_rotate
* Don't stretch or rotate raw pixels (0 - off, 1 - on)
*/
params.use_fuji_rotate = T_SR(quality) ? 0 : 1;
}
bool LoadRAW(QImageIOHandler *handler, QImage &img)
{
std::unique_ptr<LibRaw> rawProcessor(new LibRaw);
// *** Set parameters
setParams(handler, rawProcessor.get());
// *** Open the stream
auto device = handler->device();
#ifndef EXCLUDE_LibRaw_QIODevice
LibRaw_QIODevice stream(device);
if (rawProcessor->open_datastream(&stream) != LIBRAW_SUCCESS) {
return false;
}
#else
auto ba = device->readAll();
if (rawProcessor->open_buffer(ba.data(), ba.size()) != LIBRAW_SUCCESS) {
return false;
}
#endif
// *** Unpacking selected image
if (rawProcessor->unpack() != LIBRAW_SUCCESS) {
return false;
}
// *** Process selected image
if (rawProcessor->dcraw_process() != LIBRAW_SUCCESS) {
return false;
}
// *** Convert to QImage
pi_unique_ptr processedImage(rawProcessor->dcraw_make_mem_image(), LibRaw::dcraw_clear_mem);
if (processedImage == nullptr) {
return false;
}
// clang-format off
if ((processedImage->type != LIBRAW_IMAGE_BITMAP) ||
(processedImage->colors != 1 && processedImage->colors != 3 && processedImage->colors != 4) ||
(processedImage->bits != 8 && processedImage->bits != 16)) {
return false;
}
// clang-format on
auto format = QImage::Format_Invalid;
switch (processedImage->colors) {
case 1: // Gray images (tested with image attached on https://bugs.kde.org/show_bug.cgi?id=401371)
format = processedImage->bits == 8 ? QImage::Format_Grayscale8 : QImage::Format_Grayscale16;
break;
case 3: // Images with R G B components
format = processedImage->bits == 8 ? QImage::Format_RGB888 : QImage::Format_RGBX64;
break;
case 4: // Images with R G B components + Alpha (never seen)
format = processedImage->bits == 8 ? QImage::Format_RGBA8888 : QImage::Format_RGBA64;
break;
}
if (format == QImage::Format_Invalid) {
return false;
}
img = imageAlloc(processedImage->width, processedImage->height, format);
if (img.isNull()) {
return false;
}
auto rawBytesPerLine = qint32(processedImage->width * processedImage->bits * processedImage->colors + 7) / 8;
auto lineSize = std::min(qint32(img.bytesPerLine()), rawBytesPerLine);
for (int y = 0, h = img.height(); y < h; ++y) {
auto scanline = img.scanLine(y);
if (format == QImage::Format_RGBX64)
rgbToRgbX<quint16>(scanline, processedImage->data + rawBytesPerLine * y, img.bytesPerLine(), rawBytesPerLine);
else
memcpy(scanline, processedImage->data + rawBytesPerLine * y, lineSize);
}
// *** Set the color space
auto &&params = rawProcessor->imgdata.params;
if (params.output_color == 0) {
auto &&color = rawProcessor->imgdata.color;
if (auto profile = reinterpret_cast<char *>(color.profile)) {
img.setColorSpace(QColorSpace::fromIccProfile(QByteArray(profile, color.profile_length)));
}
}
if (processedImage->colors >= 3) {
if (params.output_color == 1) {
img.setColorSpace(QColorSpace(QColorSpace::SRgb));
}
if (params.output_color == 2) {
img.setColorSpace(QColorSpace(QColorSpace::AdobeRgb));
}
if (params.output_color == 4) {
img.setColorSpace(QColorSpace(QColorSpace::ProPhotoRgb));
}
if (params.output_color == 7) {
img.setColorSpace(QColorSpace(QColorSpace::DisplayP3));
}
}
// *** Set the metadata
auto &&iparams = rawProcessor->imgdata.idata;
auto xmpPacket = QString();
if (auto xmpdata = iparams.xmpdata) {
if (auto xmplen = iparams.xmplen)
xmpPacket = QString::fromUtf8(xmpdata, xmplen);
}
// Add info from LibRAW structs (e.g. GPS position, info about lens, info about shot and flash, etc...)
img.setText(QStringLiteral("XML:com.adobe.xmp"), updateXmpPacket(xmpPacket, rawProcessor.get()));
auto model = QString::fromUtf8(iparams.normalized_model);
if (!model.isEmpty()) {
img.setText(QStringLiteral("Model"), model);
}
auto manufacturer = QString::fromUtf8(iparams.normalized_make);
if (!manufacturer.isEmpty()) {
img.setText(QStringLiteral("Manufacturer"), manufacturer);
}
auto software = QString::fromUtf8(iparams.software);
if (!software.isEmpty()) {
img.setText(QStringLiteral("Software"), software);
}
auto &&iother = rawProcessor->imgdata.other;
auto description = QString::fromUtf8(iother.desc);
if (!description.isEmpty()) {
img.setText(QStringLiteral("Description"), description);
}
auto artist = QString::fromUtf8(iother.artist);
if (!artist.isEmpty()) {
img.setText(QStringLiteral("Author"), artist);
}
return true;
}
} // Private
RAWHandler::RAWHandler()
: m_imageNumber(0)
, m_imageCount(0)
, m_quality(-1)
{
}
bool RAWHandler::canRead() const
{
if (canRead(device())) {
setFormat("raw");
return true;
}
return false;
}
bool RAWHandler::read(QImage *image)
{
auto dev = device();
// Check image file format.
if (dev->atEnd()) {
return false;
}
QImage img;
if (!LoadRAW(this, img)) {
return false;
}
*image = img;
return true;
}
void RAWHandler::setOption(ImageOption option, const QVariant &value)
{
if (option == QImageIOHandler::Quality) {
bool ok = false;
auto q = value.toInt(&ok);
if (ok) {
m_quality = q;
}
}
}
bool RAWHandler::supportsOption(ImageOption option) const
{
#ifndef EXCLUDE_LibRaw_QIODevice
if (option == QImageIOHandler::Size) {
return true;
}
#endif
if (option == QImageIOHandler::Quality) {
return true;
}
return false;
}
QVariant RAWHandler::option(ImageOption option) const
{
QVariant v;
#ifndef EXCLUDE_LibRaw_QIODevice
if (option == QImageIOHandler::Size) {
auto d = device();
d->startTransaction();
std::unique_ptr<LibRaw> rawProcessor(new LibRaw);
LibRaw_QIODevice stream(d);
#if (LIBRAW_VERSION < LIBRAW_MAKE_VERSION(0, 21, 0))
rawProcessor->imgdata.params.shot_select = currentImageNumber();
#else
rawProcessor->imgdata.rawparams.shot_select = currentImageNumber();
#endif
if (rawProcessor->open_datastream(&stream) == LIBRAW_SUCCESS) {
if (rawProcessor->unpack() == LIBRAW_SUCCESS) {
auto w = libraw_get_iwidth(&rawProcessor->imgdata);
auto h = libraw_get_iheight(&rawProcessor->imgdata);
// flip & 4: taken from LibRaw code
v = (rawProcessor->imgdata.sizes.flip & 4) ? QSize(h, w) : QSize(w, h);
}
}
d->rollbackTransaction();
}
#endif
if (option == QImageIOHandler::Quality) {
v = m_quality;
}
return v;
}
bool RAWHandler::jumpToNextImage()
{
return jumpToImage(m_imageNumber + 1);
}
bool RAWHandler::jumpToImage(int imageNumber)
{
if (imageNumber >= imageCount()) {
return false;
}
m_imageNumber = imageNumber;
return true;
}
int RAWHandler::imageCount() const
{
// NOTE: image count is cached for performance reason
auto &&count = m_imageCount;
if (count > 0) {
return count;
}
count = QImageIOHandler::imageCount();
#ifndef EXCLUDE_LibRaw_QIODevice
auto d = device();
d->startTransaction();
std::unique_ptr<LibRaw> rawProcessor(new LibRaw);
LibRaw_QIODevice stream(d);
if (rawProcessor->open_datastream(&stream) == LIBRAW_SUCCESS) {
count = rawProcessor->imgdata.rawdata.iparams.raw_count;
}
d->rollbackTransaction();
#endif
return count;
}
int RAWHandler::currentImageNumber() const
{
return m_imageNumber;
}
bool RAWHandler::canRead(QIODevice *device)
{
if (!device) {
qWarning("RAWHandler::canRead() called with no device");
return false;
}
if (device->isSequential()) {
return false;
}
device->startTransaction();
std::unique_ptr<LibRaw> rawProcessor(new LibRaw);
#ifndef EXCLUDE_LibRaw_QIODevice
LibRaw_QIODevice stream(device);
auto ok = rawProcessor->open_datastream(&stream) == LIBRAW_SUCCESS;
#else
auto ba = device->readAll();
auto ok = rawProcessor->open_buffer(ba.data(), ba.size()) == LIBRAW_SUCCESS;
#endif
device->rollbackTransaction();
return ok;
}
QImageIOPlugin::Capabilities RAWPlugin::capabilities(QIODevice *device, const QByteArray &format) const
{
if (supported_formats.contains(QByteArray(format).toLower())) {
return Capabilities(CanRead);
}
if (!format.isEmpty()) {
return {};
}
if (!device->isOpen()) {
return {};
}
Capabilities cap;
if (device->isReadable() && RAWHandler::canRead(device)) {
cap |= CanRead;
}
return cap;
}
QImageIOHandler *RAWPlugin::create(QIODevice *device, const QByteArray &format) const
{
QImageIOHandler *handler = new RAWHandler;
handler->setDevice(device);
handler->setFormat(format);
return handler;
}

View File

@ -0,0 +1,7 @@
[Desktop Entry]
Type=Service
X-KDE-ServiceTypes=QImageIOPlugins
X-KDE-ImageFormat=3fr,arw,arq,bay,bmq,crw,cr2,cr3,cap,cine,cs1,dcs,dc2,dcr,dng,drf,dxo,eip,erf,fff,hdr,iiq,k25,kdc,kc2,mdc,mef,mfw,mos,mrw,nef,nrw,obm,orf,ori,pef,ptx,pxn,qtk,r3d,raf,raw,rdc,rwl,rw2,rwz,sr2,srf,srw,sti,x3f
X-KDE-MimeType=image/x-hasselblad-3fr,image/x-sony-arw,image/x-arq,image/x-bay,image/x-bmq,image/x-canon-crw,image/x-canon-cr2,image/x-canon-cr3,image/x-cap,image/x-cine,image/x-cs1,image/x-kodak-dcs,image/x-dc2,image/x-kodak-dcr,image/x-adobe-dng,image/x-drf,image/x-dxo,image/x-epson-eip,image/x-epson-erf,image/x-fff,image/x-hdr,image/x-iiq,image/x-kodak-k25,image/x-kodak-kdc,image/x-kodak-kc2,image/x-minolta-mdc,image/x-mamiya-mef,image/x-mfw,image/x-aptus-mos,image/x-minolta-mrw,image/x-nikon-nef,image/x-nikon-nrw,image/x-obm,image/x-olympus-orf,image/x-ori,image/x-pentax-pef,image/x-ptx,image/x-pxn,image/x-qtk,image/x-r3d,image/x-fuji-raf,image/x-raw,image/x-rdc,image/x-rwl,image/x-panasonic-rw2,image/x-rwz,image/x-sony-sr2,image/x-sony-srf,image/x-samsung-srw,image/x-sti,image/x-sigma-x3f
X-KDE-Read=true
X-KDE-Write=false

42
src/imageformats/raw.json Normal file
View File

@ -0,0 +1,42 @@
{
"Keys": [
"3fr",
"arw", "arq",
"bay", "bmq",
"crw", "cr2", "cr3", "cap", "cine", "cs1",
"dcs", "dc2", "dcr", "dng", "drf", "dxo",
"eip", "erf",
"fff",
"hdr",
"iiq",
"k25", "kdc", "kc2",
"mdc", "mef", "mfw", "mos", "mrw",
"nef", "nrw",
"obm", "orf", "ori",
"pef", "ptx", "pxn",
"qtk",
"r3d", "raf", "raw", "rdc", "rwl", "rw2", "rwz",
"sr2", "srf", "srw", "sti",
"x3f"
],
"MimeTypes": [
"image/x-hasselblad-3fr",
"image/x-sony-arw", "image/x-arq",
"image/x-bay", "image/x-bmq",
"image/x-canon-crw", "image/x-canon-cr2", "image/x-canon-cr3", "image/x-cap", "image/x-cine", "image/x-cs1",
"image/x-kodak-dcs", "image/x-dc2", "image/x-kodak-dcr", "image/x-adobe-dng", "image/x-drf", "image/x-dxo",
"image/x-epson-eip", "image/x-epson-erf",
"image/x-fff",
"image/x-hdr",
"image/x-iiq",
"image/x-kodak-k25", "image/x-kodak-kdc", "image/x-kodak-kc2",
"image/x-minolta-mdc", "image/x-mamiya-mef", "image/x-mfw", "image/x-aptus-mos", "image/x-minolta-mrw",
"image/x-nikon-nef", "image/x-nikon-nrw",
"image/x-obm", "image/x-olympus-orf", "image/x-ori",
"image/x-pentax-pef", "image/x-ptx", "image/x-pxn",
"image/x-qtk",
"image/x-r3d", "image/x-fuji-raf", "image/x-raw", "image/x-rdc", "image/x-rwl", "image/x-panasonic-rw2", "image/x-rwz",
"image/x-sony-sr2", "image/x-sony-srf", "image/x-samsung-srw", "image/x-sti",
"image/x-sigma-x3f"
]
}

93
src/imageformats/raw_p.h Normal file
View File

@ -0,0 +1,93 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2022 Mirco Miranda <mircomir@outlook.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KIMG_RAW_P_H
#define KIMG_RAW_P_H
#include <QImageIOPlugin>
class RAWHandler : public QImageIOHandler
{
public:
RAWHandler();
bool canRead() const override;
bool read(QImage *image) override;
void setOption(ImageOption option, const QVariant &value) override;
bool supportsOption(QImageIOHandler::ImageOption option) const override;
QVariant option(QImageIOHandler::ImageOption option) const override;
bool jumpToNextImage() override;
bool jumpToImage(int imageNumber) override;
int imageCount() const override;
int currentImageNumber() const override;
static bool canRead(QIODevice *device);
private:
qint32 m_imageNumber;
mutable qint32 m_imageCount;
/**
* @brief m_quality
* Change the quality of the conversion. If -1, default quality is used.
* @note Verify that the quality change support has been compiled with supportsOption()
*
* 3 2 1 0
* 1 0 9 8 7 6 5 4 3 2 1 0 9 87 6 5 4 3 2 1098 7654 3210
* _ _ _ _ _ _ _ _ _ _ _ S F NN E H B A W CCCC IIII PPPP
*
* Where:
*
* _: reserved
* P: preset values: *** if set, other flags are ignored! ***
* - 0: Use other flags (no preset)
* - 1: I = 0, C = 1, B = 0, W = 1, A = 1, H = 1 (Linear, sRGB, 8-bits, Camera White, Auto White, Half-size)
* - 2: I = 0, C = 1, B = 0, W = 1, A = 1, H = 0 (Linear, sRGB, 8-bits, Camera White, Auto White)
* - 3: I = 3, C = 1, B = 0, W = 1, A = 1, H = 0 (AHD, sRGB, 8-bits, Camera White, Auto White)
* - 4: I = 3, C = 1, B = 1, W = 1, A = 1, H = 0 (AHD, sRGB, 16-bits, Camera White, Auto White)
* - 5: I = 3, C = 2, B = 1, W = 1, A = 1, H = 0 (AHD, Adobe, 16-bits, Camera White, Auto White)
* - 6: I = 3, C = 4, B = 1, W = 1, A = 1, H = 0 (AHD, ProPhoto, 16-bits, Camera White, Auto White)
* - 7: I = 11, C = 1, B = 0, W = 1, A = 1, H = 0 (DHT, sRGB, 8-bits, Camera White, Auto White)
* - 8: I = 11, C = 1, B = 1, W = 1, A = 1, H = 0 (DHT, sRGB, 16-bits, Camera White, Auto White)
* - 9: I = 11, C = 2, B = 1, W = 1, A = 1, H = 0 (DHT, Adobe, 16-bits, Camera White, Auto White)
* - 10: I = 11, C = 4, B = 1, W = 1, A = 1, H = 0 (DHT, ProPhoto, 16-bits, Camera White, Auto White)
* - 11: reserved
* - 12: reserved
* - 13: reserved
* - 14: reserved
* - 15: reserved
* I: interpolation quality (0 - linear, 1 - VNG, 2 - PPG, 3 - AHD, 4 - DCB, 11 - DHT, 12 - AAHD)
* C: output colorspace (0 - raw, 1 - sRGB, 2 - Adobe, 3 - Wide, 4 - ProPhoto, 5 - XYZ, 6 - ACES, 7 - DCI-P3, 8 - Rec2020)
* W: use camera white balace (0 - off, 1 - on)
* A: use auto white balance (0 - off, 1 - on)
* B: output bit per sample (0 - 8-bits, 1 - 16-bits)
* H: half size image (0 - off, 1 - on)
* E: DCB color enhance (0 - off, 1 - on)
* N: FBDD noise reduction (0 - off, 1 - light, 2 - full)
* F: Interpolate RGGB as four colors (0 - off, 1 - on)
* S: Don't stretch or rotate raw pixels (0 - rotate and stretch, 1 - don't rotate and stretch)
*
* @note It is safe to set both W and A: W is used if camera white balance is found, otherwise A is used.
*/
qint32 m_quality;
};
class RAWPlugin : public QImageIOPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QImageIOHandlerFactoryInterface" FILE "raw.json")
public:
Capabilities capabilities(QIODevice *device, const QByteArray &format) const override;
QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const override;
};
#endif // KIMG_RAW_P_H

View File

@ -20,6 +20,7 @@
*/
#include "rgb_p.h"
#include "util_p.h"
#include <QMap>
#include <QVector>
@ -324,7 +325,7 @@ bool SGIImage::readImage(QImage &img)
return false;
}
img = QImage(_xsize, _ysize, QImage::Format_RGB32);
img = imageAlloc(_xsize, _ysize, QImage::Format_RGB32);
if (img.isNull()) {
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(_xsize, _ysize);
return false;

View File

@ -17,6 +17,7 @@
*/
#include "tga_p.h"
#include "util_p.h"
#include <assert.h>
@ -172,7 +173,7 @@ struct TgaHeaderInfo {
static bool LoadTGA(QDataStream &s, const TgaHeader &tga, QImage &img)
{
// Create image.
img = QImage(tga.width, tga.height, QImage::Format_RGB32);
img = imageAlloc(tga.width, tga.height, QImage::Format_RGB32);
if (img.isNull()) {
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(tga.width, tga.height);
return false;
@ -184,7 +185,7 @@ static bool LoadTGA(QDataStream &s, const TgaHeader &tga, QImage &img)
const int numAlphaBits = tga.flags & 0xf;
// However alpha exists only in the 32 bit format.
if ((tga.pixel_size == 32) && (tga.flags & 0xf)) {
img = QImage(tga.width, tga.height, QImage::Format_ARGB32);
img = imageAlloc(tga.width, tga.height, QImage::Format_ARGB32);
if (img.isNull()) {
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(tga.width, tga.height);
return false;

View File

@ -1,10 +1,42 @@
/*
SPDX-FileCopyrightText: 2022 Albert Astals Cid <aacid@kde.org>
SPDX-FileCopyrightText: 2022 Mirco Miranda <mircomir@outlook.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef UTIL_P_H
#define UTIL_P_H
#include <limits>
#include <QImage>
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
#include <QImageIOHandler>
#endif
// QVector uses some extra space for stuff, hence the 32 here suggested by Thiago Macieira
static constexpr int kMaxQVectorSize = std::numeric_limits<int>::max() - 32;
// On Qt 6 to make the plugins fail to allocate if the image size is greater than QImageReader::allocationLimit()
// it is necessary to allocate the image with QImageIOHandler::allocateImage().
inline QImage imageAlloc(const QSize &size, const QImage::Format &format)
{
QImage img;
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
img = QImage(size, format);
#else
if (!QImageIOHandler::allocateImage(size, format, &img)) {
img = QImage(); // paranoia
}
#endif
return img;
}
inline QImage imageAlloc(qint32 width, qint32 height, const QImage::Format &format)
{
return imageAlloc(QSize(width, height), format);
}
#endif // UTIL_P_H

View File

@ -6,6 +6,7 @@
SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "util_p.h"
#include "xcf_p.h"
#include <QDebug>
@ -17,6 +18,9 @@
#include <QVector>
#include <QtEndian>
#include <QColorSpace>
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
#include <QImageReader>
#endif
#include <stdlib.h>
#include <string.h>
@ -1093,12 +1097,27 @@ bool XCFImageFormat::composeTiles(XCFImage &xcf_image)
qCDebug(XCFPLUGIN) << "LAYER: height=" << layer.height << ", width=" << layer.width;
qCDebug(XCFPLUGIN) << "LAYER: rows=" << layer.nrows << ", columns=" << layer.ncols;
// NOTE: starting from GIMP 2.10, images can be very large. The 32K limit for width and height is obsolete
// and it was changed to 300000 (the same as Photoshop Big image). This plugin was able to open an RGB
// image of 108000x40000 pixels saved with GIMP 2.10
// SANITY CHECK: Catch corrupted XCF image file where the width or height
// of a tile is reported are bogus. See Bug# 234030.
if (layer.width > 32767 || layer.height > 32767 || (sizeof(void *) == 4 && layer.width * layer.height > 16384 * 16384)) {
if (layer.width > 300000 || layer.height > 300000 || (sizeof(void *) == 4 && layer.width * layer.height > 16384 * 16384)) {
return false;
}
// Qt 6 image allocation limit calculation: we have to check the limit here because the image is splitted in
// tiles of 64x64 pixels. The required memory to build the image is at least doubled because tiles are loaded
// and then the final image is created by copying the tiles inside it.
// NOTE: on Windows to open a 10GiB image the plugin uses 28GiB of RAM
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
qint64 channels = 1 + (layer.type == RGB_GIMAGE ? 2 : 0) + (layer.type == RGBA_GIMAGE ? 3 : 0);
if (qint64(layer.width) * qint64(layer.height) * channels * 2ll / 1024ll / 1024ll > QImageReader::allocationLimit()) {
qCDebug(XCFPLUGIN) << "Rejecting image as it exceeds the current allocation limit of" << QImageReader::allocationLimit() << "megabytes";
return false;
}
#endif
layer.image_tiles.resize(layer.nrows);
if (layer.type == GRAYA_GIMAGE || layer.type == INDEXEDA_GIMAGE) {
@ -1251,7 +1270,7 @@ void XCFImageFormat::setImageParasites(const XCFImage &xcf_image, QImage &image)
{
auto&& p = xcf_image.parasites;
auto keys = p.keys();
for (auto&& key : qAsConst(keys)) {
for (auto &&key : std::as_const(keys)) {
auto value = p.value(key);
if(value.isEmpty())
continue;
@ -1917,7 +1936,7 @@ bool XCFImageFormat::initializeImage(XCFImage &xcf_image)
switch (layer.type) {
case RGB_GIMAGE:
if (layer.opacity == OPAQUE_OPACITY) {
image = QImage(xcf_image.width, xcf_image.height, QImage::Format_RGB32);
image = imageAlloc(xcf_image.width, xcf_image.height, QImage::Format_RGB32);
if (image.isNull()) {
return false;
}
@ -1926,7 +1945,7 @@ bool XCFImageFormat::initializeImage(XCFImage &xcf_image)
} // else, fall through to 32-bit representation
Q_FALLTHROUGH();
case RGBA_GIMAGE:
image = QImage(xcf_image.width, xcf_image.height, QImage::Format_ARGB32);
image = imageAlloc(xcf_image.width, xcf_image.height, QImage::Format_ARGB32);
if (image.isNull()) {
return false;
}
@ -1935,7 +1954,7 @@ bool XCFImageFormat::initializeImage(XCFImage &xcf_image)
case GRAY_GIMAGE:
if (layer.opacity == OPAQUE_OPACITY) {
image = QImage(xcf_image.width, xcf_image.height, QImage::Format_Indexed8);
image = imageAlloc(xcf_image.width, xcf_image.height, QImage::Format_Indexed8);
image.setColorCount(256);
if (image.isNull()) {
return false;
@ -1946,7 +1965,7 @@ bool XCFImageFormat::initializeImage(XCFImage &xcf_image)
} // else, fall through to 32-bit representation
Q_FALLTHROUGH();
case GRAYA_GIMAGE:
image = QImage(xcf_image.width, xcf_image.height, QImage::Format_ARGB32);
image = imageAlloc(xcf_image.width, xcf_image.height, QImage::Format_ARGB32);
if (image.isNull()) {
return false;
}
@ -1967,7 +1986,7 @@ bool XCFImageFormat::initializeImage(XCFImage &xcf_image)
// or two-color palette. Have to ask about this...
if (xcf_image.num_colors <= 2) {
image = QImage(xcf_image.width, xcf_image.height, QImage::Format_MonoLSB);
image = imageAlloc(xcf_image.width, xcf_image.height, QImage::Format_MonoLSB);
image.setColorCount(xcf_image.num_colors);
if (image.isNull()) {
return false;
@ -1975,7 +1994,7 @@ bool XCFImageFormat::initializeImage(XCFImage &xcf_image)
image.fill(0);
setPalette(xcf_image, image);
} else if (xcf_image.num_colors <= 256) {
image = QImage(xcf_image.width, xcf_image.height, QImage::Format_Indexed8);
image = imageAlloc(xcf_image.width, xcf_image.height, QImage::Format_Indexed8);
image.setColorCount(xcf_image.num_colors);
if (image.isNull()) {
return false;
@ -1993,7 +2012,7 @@ bool XCFImageFormat::initializeImage(XCFImage &xcf_image)
xcf_image.palette[1] = xcf_image.palette[0];
xcf_image.palette[0] = qRgba(255, 255, 255, 0);
image = QImage(xcf_image.width, xcf_image.height, QImage::Format_MonoLSB);
image = imageAlloc(xcf_image.width, xcf_image.height, QImage::Format_MonoLSB);
image.setColorCount(xcf_image.num_colors);
if (image.isNull()) {
return false;
@ -2009,7 +2028,7 @@ bool XCFImageFormat::initializeImage(XCFImage &xcf_image)
}
xcf_image.palette[0] = qRgba(255, 255, 255, 0);
image = QImage(xcf_image.width, xcf_image.height, QImage::Format_Indexed8);
image = imageAlloc(xcf_image.width, xcf_image.height, QImage::Format_Indexed8);
image.setColorCount(xcf_image.num_colors);
if (image.isNull()) {
return false;
@ -2020,7 +2039,7 @@ bool XCFImageFormat::initializeImage(XCFImage &xcf_image)
// No room for a transparent color, so this has to be promoted to
// true color. (There is no equivalent PNG representation output
// from The GIMP as of v1.2.)
image = QImage(xcf_image.width, xcf_image.height, QImage::Format_ARGB32);
image = imageAlloc(xcf_image.width, xcf_image.height, QImage::Format_ARGB32);
if (image.isNull()) {
return false;
}
@ -2031,11 +2050,11 @@ bool XCFImageFormat::initializeImage(XCFImage &xcf_image)
if (xcf_image.x_resolution > 0 && xcf_image.y_resolution > 0) {
const float dpmx = xcf_image.x_resolution * INCHESPERMETER;
if (dpmx > std::numeric_limits<int>::max()) {
if (dpmx > float(std::numeric_limits<int>::max())) {
return false;
}
const float dpmy = xcf_image.y_resolution * INCHESPERMETER;
if (dpmy > std::numeric_limits<int>::max()) {
if (dpmy > float(std::numeric_limits<int>::max())) {
return false;
}
image.setDotsPerMeterX((int)dpmx);
@ -3322,6 +3341,9 @@ bool XCFHandler::canRead(QIODevice *device)
qCDebug(XCFPLUGIN) << "XCFHandler::canRead() called with no device";
return false;
}
if (device->isSequential()) {
return false;
}
qint64 oldPos = device->pos();