Compare commits
87 Commits
Author | SHA1 | Date | |
---|---|---|---|
55227815d5 | |||
64d51ed610 | |||
2ca57c9c59 | |||
f7fd14d418 | |||
c9aa1ff629 | |||
91d3bd5227 | |||
bb66367bc8 | |||
14770318a3 | |||
9b1fafe29b | |||
fa673b5df8 | |||
e96b43aef5 | |||
64f3303ef0 | |||
63056c52f9 | |||
2997f7ae8d | |||
0b4741f4b7 | |||
bc52c03981 | |||
c1c57d9a11 | |||
4c6d2b92b6 | |||
05bd9397b3 | |||
f4ca3f6783 | |||
a30f043e5d | |||
7af4eea253 | |||
a3049f6740 | |||
3b1e8f7054 | |||
dcab3a06ab | |||
361f9e867e | |||
35883aa604 | |||
50846f224f | |||
9ad82ed608 | |||
c9f32a226f | |||
a0df142408 | |||
8586bb4719 | |||
d734f28727 | |||
afa7399b36 | |||
bfb12093ad | |||
1190e53e9b | |||
350ce1b990 | |||
bcbf45e23a | |||
c71a7984d6 | |||
b1f3a87896 | |||
8af9a0f9d9 | |||
3790a89cd1 | |||
f475a4b24a | |||
d2f38b8b9c | |||
9ab64dbf22 | |||
20f74ce5e6 | |||
54129819d5 | |||
181eb253c6 | |||
c5f7ea7eac | |||
ea14882ff7 | |||
f8bfdce285 | |||
524f083ee4 | |||
c96ad6ba8a | |||
49bd131eef | |||
2a25ec7a56 | |||
a8a477ae67 | |||
2f27dff48b | |||
72a1cc23b1 | |||
6f3a326cf8 | |||
d881a7bbb1 | |||
65a20b43fc | |||
84941b7690 | |||
21928300c6 | |||
024d199ed0 | |||
9ac923ad09 | |||
feb6d9b20f | |||
dfbc6e0f8c | |||
43543f96bc | |||
62e477a6f2 | |||
e6955e1f03 | |||
6074c4d6fd | |||
6f44c5c52a | |||
d030c75925 | |||
9b3133ac92 | |||
b0a0bb1294 | |||
3d5090593c | |||
d4966d169b | |||
bf52896347 | |||
c52ffa2227 | |||
e4e386babf | |||
b47a9d7022 | |||
2cbf815d1f | |||
6cd0056f3b | |||
83374f390e | |||
5e59d950bd | |||
de320447f6 | |||
cf375a207f |
1
.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
autotests/read/raw/RAW_KODAK_C330_FORMAT_NONE_YRGB.raw binary
|
@ -3,8 +3,12 @@
|
|||||||
|
|
||||||
include:
|
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.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/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/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/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/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.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
|
||||||
|
@ -6,3 +6,4 @@ Dependencies:
|
|||||||
|
|
||||||
Options:
|
Options:
|
||||||
test-before-installing: True
|
test-before-installing: True
|
||||||
|
require-passing-tests-on: [ 'Linux', 'FreeBSD', 'Windows' ]
|
||||||
|
@ -3,19 +3,19 @@ cmake_minimum_required(VERSION 3.16)
|
|||||||
project(KImageFormats)
|
project(KImageFormats)
|
||||||
|
|
||||||
include(FeatureSummary)
|
include(FeatureSummary)
|
||||||
find_package(ECM 5.95.0 NO_MODULE)
|
find_package(ECM 5.107.0 NO_MODULE)
|
||||||
set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://commits.kde.org/extra-cmake-modules")
|
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)
|
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(KDEInstallDirs)
|
||||||
include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE)
|
include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE)
|
||||||
include(KDECMakeSettings)
|
include(KDECMakeSettings)
|
||||||
|
|
||||||
include(KDEGitCommitHooks)
|
include(KDEGitCommitHooks)
|
||||||
|
include(ECMDeprecationSettings)
|
||||||
|
|
||||||
include(CheckIncludeFiles)
|
include(CheckIncludeFiles)
|
||||||
include(FindPkgConfig)
|
include(FindPkgConfig)
|
||||||
|
|
||||||
@ -70,8 +70,18 @@ if(KIMAGEFORMATS_JXL)
|
|||||||
endif()
|
endif()
|
||||||
add_feature_info(LibJXL LibJXL_FOUND "required for the QImage plugin for JPEG XL images")
|
add_feature_info(LibJXL LibJXL_FOUND "required for the QImage plugin for JPEG XL images")
|
||||||
|
|
||||||
add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x050f02)
|
# note: module FindLibRaw missing from https://invent.kde.org/frameworks/extra-cmake-modules
|
||||||
add_definitions(-DKF_DISABLE_DEPRECATED_BEFORE_AND_AT=0x055900)
|
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
|
||||||
|
)
|
||||||
|
|
||||||
add_subdirectory(src)
|
add_subdirectory(src)
|
||||||
if (BUILD_TESTING)
|
if (BUILD_TESTING)
|
||||||
add_subdirectory(autotests)
|
add_subdirectory(autotests)
|
||||||
|
@ -18,6 +18,7 @@ The following image formats have read-only support:
|
|||||||
- OpenEXR (exr)
|
- OpenEXR (exr)
|
||||||
- Photoshop documents (psd, psb, pdd, psdt)
|
- Photoshop documents (psd, psb, pdd, psdt)
|
||||||
- Sun Raster (ras)
|
- Sun Raster (ras)
|
||||||
|
- Camera RAW images (arw, cr2, cr3, dcs, dng, ...)
|
||||||
|
|
||||||
The following image formats have read and write support:
|
The following image formats have read and write support:
|
||||||
|
|
||||||
|
@ -136,7 +136,15 @@ kimageformats_write_tests(
|
|||||||
# kimageformats_write_tests(eps)
|
# kimageformats_write_tests(eps)
|
||||||
#endif()
|
#endif()
|
||||||
if (OpenEXR_FOUND)
|
if (OpenEXR_FOUND)
|
||||||
# FIXME: OpenEXR tests
|
kimageformats_read_tests(
|
||||||
|
exr
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if (LibRaw_FOUND)
|
||||||
|
kimageformats_read_tests(
|
||||||
|
raw
|
||||||
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
find_package(Qt${QT_MAJOR_VERSION}Test ${REQUIRED_QT_VERSION} CONFIG QUIET)
|
find_package(Qt${QT_MAJOR_VERSION}Test ${REQUIRED_QT_VERSION} CONFIG QUIET)
|
||||||
|
BIN
autotests/read/avif/rotated090.avif
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
autotests/read/avif/rotated090.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
autotests/read/avif/rotated090_left-to-right.avif
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
autotests/read/avif/rotated090_left-to-right.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
autotests/read/avif/rotated090_top-to-bottom.avif
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
autotests/read/avif/rotated090_top-to-bottom.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
autotests/read/avif/rotated180.avif
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
autotests/read/avif/rotated180.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
autotests/read/avif/rotated180_left-to-right.avif
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
autotests/read/avif/rotated180_left-to-right.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
autotests/read/avif/rotated180_top-to-bottom.avif
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
autotests/read/avif/rotated180_top-to-bottom.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
autotests/read/avif/rotated270.avif
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
autotests/read/avif/rotated270.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
autotests/read/avif/rotated270_left-to-right.avif
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
autotests/read/avif/rotated270_left-to-right.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
autotests/read/avif/rotated270_top-to-bottom.avif
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
autotests/read/avif/rotated270_top-to-bottom.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
autotests/read/avif/unrotated.avif
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
autotests/read/avif/unrotated.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
autotests/read/avif/unrotated_left-to-right.avif
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
autotests/read/avif/unrotated_left-to-right.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
autotests/read/avif/unrotated_top-to-bottom.avif
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
autotests/read/avif/unrotated_top-to-bottom.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
autotests/read/exr/rgb-gimp.exr
Normal file
BIN
autotests/read/exr/rgb-gimp.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
autotests/read/jxl/orientation1.jxl
Normal file
BIN
autotests/read/jxl/orientation1.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
autotests/read/jxl/orientation2.jxl
Normal file
BIN
autotests/read/jxl/orientation2.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
autotests/read/jxl/orientation3.jxl
Normal file
BIN
autotests/read/jxl/orientation3.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
autotests/read/jxl/orientation4.jxl
Normal file
BIN
autotests/read/jxl/orientation4.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
autotests/read/jxl/orientation5.jxl
Normal file
BIN
autotests/read/jxl/orientation5.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
autotests/read/jxl/orientation6.jxl
Normal file
BIN
autotests/read/jxl/orientation6.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
autotests/read/jxl/orientation7.jxl
Normal file
BIN
autotests/read/jxl/orientation7.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
autotests/read/jxl/orientation8.jxl
Normal file
BIN
autotests/read/jxl/orientation8.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
autotests/read/pcx/ccbug_463951.pcx
Normal file
BIN
autotests/read/pcx/ccbug_463951.png
Normal file
After Width: | Height: | Size: 8.5 KiB |
BIN
autotests/read/pcx/indexed8.pcx
Normal file
BIN
autotests/read/pcx/indexed8.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
autotests/read/pcx/mono.pcx
Normal file
BIN
autotests/read/pcx/mono.png
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
autotests/read/psd/53alphas.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
autotests/read/psd/53alphas.psd
Normal file
Before Width: | Height: | Size: 983 B After Width: | Height: | Size: 4.0 KiB |
Before Width: | Height: | Size: 94 KiB |
BIN
autotests/read/psd/birthday.tif
Normal file
BIN
autotests/read/psd/ccbug182496.png
Normal file
After Width: | Height: | Size: 114 KiB |
BIN
autotests/read/psd/ccbug182496.psd
Normal file
BIN
autotests/read/psd/cmyka-16bits.png
Normal file
After Width: | Height: | Size: 112 KiB |
BIN
autotests/read/psd/cmyka-16bits.psd
Normal file
BIN
autotests/read/psd/cmyka-8bits.png
Normal file
After Width: | Height: | Size: 72 KiB |
BIN
autotests/read/psd/cmyka-8bits.psd
Normal file
BIN
autotests/read/psd/laba_16bit.png
Normal file
After Width: | Height: | Size: 189 KiB |
BIN
autotests/read/psd/laba_16bit.psd
Normal file
BIN
autotests/read/psd/laba_8bit.png
Normal file
After Width: | Height: | Size: 114 KiB |
BIN
autotests/read/psd/laba_8bit.psd
Normal file
BIN
autotests/read/psd/mch-16bits.png
Normal file
After Width: | Height: | Size: 104 KiB |
BIN
autotests/read/psd/mch-16bits.psd
Normal file
BIN
autotests/read/psd/mch-8bits.png
Normal file
After Width: | Height: | Size: 68 KiB |
BIN
autotests/read/psd/mch-8bits.psd
Normal file
BIN
autotests/read/raw/RAW_KODAK_C330_FORMAT_NONE_YRGB.png
Normal file
After Width: | Height: | Size: 5.2 MiB |
901
autotests/read/raw/RAW_KODAK_C330_FORMAT_NONE_YRGB.raw
Normal file
@ -9,6 +9,7 @@
|
|||||||
#include <QCommandLineParser>
|
#include <QCommandLineParser>
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
|
#include <QFile>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
#include <QImageReader>
|
#include <QImageReader>
|
||||||
@ -18,6 +19,44 @@
|
|||||||
|
|
||||||
#include "fuzzyeq.cpp"
|
#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)
|
static void writeImageData(const char *name, const QString &filename, const QImage &image)
|
||||||
{
|
{
|
||||||
QFile file(filename);
|
QFile file(filename);
|
||||||
@ -56,7 +95,7 @@ int main(int argc, char **argv)
|
|||||||
QCoreApplication::removeLibraryPath(QStringLiteral(PLUGIN_DIR));
|
QCoreApplication::removeLibraryPath(QStringLiteral(PLUGIN_DIR));
|
||||||
QCoreApplication::addLibraryPath(QStringLiteral(PLUGIN_DIR));
|
QCoreApplication::addLibraryPath(QStringLiteral(PLUGIN_DIR));
|
||||||
QCoreApplication::setApplicationName(QStringLiteral("readtest"));
|
QCoreApplication::setApplicationName(QStringLiteral("readtest"));
|
||||||
QCoreApplication::setApplicationVersion(QStringLiteral("1.0.0"));
|
QCoreApplication::setApplicationVersion(QStringLiteral("1.1.0"));
|
||||||
|
|
||||||
QCommandLineParser parser;
|
QCommandLineParser parser;
|
||||||
parser.setApplicationDescription(QStringLiteral("Performs basic image conversion checking."));
|
parser.setApplicationDescription(QStringLiteral("Performs basic image conversion checking."));
|
||||||
@ -94,11 +133,11 @@ int main(int argc, char **argv)
|
|||||||
QByteArray format = suffix.toLatin1();
|
QByteArray format = suffix.toLatin1();
|
||||||
|
|
||||||
QDir imgdir(QLatin1String(IMAGEDIR "/") + suffix);
|
QDir imgdir(QLatin1String(IMAGEDIR "/") + suffix);
|
||||||
imgdir.setNameFilters(QStringList(QLatin1String("*.") + suffix));
|
|
||||||
imgdir.setFilter(QDir::Files);
|
imgdir.setFilter(QDir::Files);
|
||||||
|
|
||||||
int passed = 0;
|
int passed = 0;
|
||||||
int failed = 0;
|
int failed = 0;
|
||||||
|
int skipped = 0;
|
||||||
|
|
||||||
QTextStream(stdout) << "********* "
|
QTextStream(stdout) << "********* "
|
||||||
<< "Starting basic read tests for " << suffix << " images *********\n";
|
<< "Starting basic read tests for " << suffix << " images *********\n";
|
||||||
@ -112,69 +151,96 @@ int main(int argc, char **argv)
|
|||||||
QTextStream(stdout) << "QImageReader::supportedImageFormats: " << formatStrings.join(", ") << "\n";
|
QTextStream(stdout) << "QImageReader::supportedImageFormats: " << formatStrings.join(", ") << "\n";
|
||||||
|
|
||||||
const QFileInfoList lstImgDir = imgdir.entryInfoList();
|
const QFileInfoList lstImgDir = imgdir.entryInfoList();
|
||||||
for (const QFileInfo &fi : lstImgDir) {
|
// Launch 2 runs for each test: first run on a random access device, second run on a sequential access device
|
||||||
int suffixPos = fi.filePath().count() - suffix.count();
|
for (int seq = 0; seq < 2; ++seq) {
|
||||||
QString inputfile = fi.filePath();
|
if (seq) {
|
||||||
QString expfile = fi.filePath().replace(suffixPos, suffix.count(), QStringLiteral("png"));
|
QTextStream(stdout) << "* Run on SEQUENTIAL ACCESS device\n";
|
||||||
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;
|
|
||||||
} else {
|
} else {
|
||||||
QImage::Format inputFormat = preferredFormat(inputImage.format());
|
QTextStream(stdout) << "* Run on RANDOM ACCESS device\n";
|
||||||
QImage::Format expFormat = preferredFormat(expImage.format());
|
}
|
||||||
QImage::Format cmpFormat = inputFormat == expFormat ? inputFormat : QImage::Format_ARGB32;
|
for (const QFileInfo &fi : lstImgDir) {
|
||||||
|
if (!fi.suffix().compare("png", Qt::CaseInsensitive) || !fi.suffix().compare("tif", Qt::CaseInsensitive)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int suffixPos = fi.filePath().count() - suffix.count();
|
||||||
|
QString inputfile = fi.filePath();
|
||||||
|
QString fmt = QStringLiteral("png");
|
||||||
|
QString expfile = fi.filePath().replace(suffixPos, suffix.count(), fmt);
|
||||||
|
if (!QFile::exists(expfile)) { // try with tiff
|
||||||
|
fmt = QStringLiteral("tif");
|
||||||
|
expfile = fi.filePath().replace(suffixPos, suffix.count(), fmt);
|
||||||
|
}
|
||||||
|
QString expfilename = QFileInfo(expfile).fileName();
|
||||||
|
|
||||||
if (inputImage.format() != cmpFormat) {
|
std::unique_ptr<QIODevice> inputDevice(seq ? new SequentialFile(inputfile) : new QFile(inputfile));
|
||||||
QTextStream(stdout) << "INFO : " << fi.fileName() << ": converting " << fi.fileName() << " from " << formatToString(inputImage.format())
|
QImageReader inputReader(inputDevice.get(), format);
|
||||||
<< " to " << formatToString(cmpFormat) << '\n';
|
QImageReader expReader(expfile, fmt.toLatin1());
|
||||||
inputImage = inputImage.convertToFormat(cmpFormat);
|
|
||||||
}
|
QImage inputImage;
|
||||||
if (expImage.format() != cmpFormat) {
|
QImage expImage;
|
||||||
QTextStream(stdout) << "INFO : " << fi.fileName() << ": converting " << expfilename << " from " << formatToString(expImage.format()) << " to "
|
|
||||||
<< formatToString(cmpFormat) << '\n';
|
// inputImage is auto-rotated to final orientation
|
||||||
expImage = expImage.convertToFormat(cmpFormat);
|
inputReader.setAutoTransform(true);
|
||||||
}
|
|
||||||
if (fuzzyeq(inputImage, expImage, fuzziness)) {
|
if (!expReader.read(&expImage)) {
|
||||||
QTextStream(stdout) << "PASS : " << fi.fileName() << "\n";
|
QTextStream(stdout) << "ERROR: " << fi.fileName() << ": could not load " << expfilename << ": " << expReader.errorString() << "\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;
|
++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) << "********* "
|
QTextStream(stdout) << "********* "
|
||||||
<< "Finished basic read tests for " << suffix << " images *********\n";
|
<< "Finished basic read tests for " << suffix << " images *********\n";
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.0 KiB |
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.0 KiB |
86
cmake/find-modules/FindLibRaw.cmake
Normal 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()
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
function(kimageformats_add_plugin plugin)
|
function(kimageformats_add_plugin plugin)
|
||||||
set(options)
|
set(options)
|
||||||
|
set(oneValueArgs)
|
||||||
set(multiValueArgs SOURCES)
|
set(multiValueArgs SOURCES)
|
||||||
cmake_parse_arguments(KIF_ADD_PLUGIN "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
|
cmake_parse_arguments(KIF_ADD_PLUGIN "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
|
||||||
if(NOT KIF_ADD_PLUGIN_SOURCES)
|
if(NOT KIF_ADD_PLUGIN_SOURCES)
|
||||||
@ -19,19 +20,26 @@ endfunction()
|
|||||||
##################################
|
##################################
|
||||||
|
|
||||||
kimageformats_add_plugin(kimg_ani SOURCES ani.cpp)
|
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)
|
if (TARGET avif)
|
||||||
kimageformats_add_plugin(kimg_avif SOURCES "avif.cpp")
|
kimageformats_add_plugin(kimg_avif SOURCES "avif.cpp")
|
||||||
target_link_libraries(kimg_avif "avif")
|
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()
|
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()
|
||||||
|
|
||||||
##################################
|
##################################
|
||||||
|
|
||||||
@ -39,14 +47,18 @@ if (BUILD_EPS_PLUGIN)
|
|||||||
if (TARGET Qt${QT_MAJOR_VERSION}::PrintSupport)
|
if (TARGET Qt${QT_MAJOR_VERSION}::PrintSupport)
|
||||||
kimageformats_add_plugin(kimg_eps SOURCES eps.cpp)
|
kimageformats_add_plugin(kimg_eps SOURCES eps.cpp)
|
||||||
target_link_libraries(kimg_eps Qt${QT_MAJOR_VERSION}::PrintSupport)
|
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()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
##################################
|
##################################
|
||||||
|
|
||||||
# need this for Qt's version of the plugin
|
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||||
install(FILES jp2.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
# need this for Qt's version of the plugin
|
||||||
|
install(FILES jp2.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||||
|
endif()
|
||||||
|
|
||||||
##################################
|
##################################
|
||||||
|
|
||||||
@ -64,21 +76,27 @@ if(OpenEXR_FOUND)
|
|||||||
endif()
|
endif()
|
||||||
kde_target_enable_exceptions(kimg_exr PRIVATE)
|
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()
|
endif()
|
||||||
|
|
||||||
##################################
|
##################################
|
||||||
|
|
||||||
kimageformats_add_plugin(kimg_hdr SOURCES hdr.cpp)
|
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()
|
||||||
|
|
||||||
##################################
|
##################################
|
||||||
|
|
||||||
if (LibHeif_FOUND)
|
if (LibHeif_FOUND)
|
||||||
kimageformats_add_plugin(kimg_heif SOURCES heif.cpp)
|
kimageformats_add_plugin(kimg_heif SOURCES heif.cpp)
|
||||||
target_link_libraries(kimg_heif PkgConfig::LibHeif)
|
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()
|
endif()
|
||||||
|
|
||||||
##################################
|
##################################
|
||||||
@ -86,43 +104,74 @@ endif()
|
|||||||
if (LibJXL_FOUND AND LibJXLThreads_FOUND)
|
if (LibJXL_FOUND AND LibJXLThreads_FOUND)
|
||||||
kimageformats_add_plugin(kimg_jxl SOURCES jxl.cpp)
|
kimageformats_add_plugin(kimg_jxl SOURCES jxl.cpp)
|
||||||
target_link_libraries(kimg_jxl PkgConfig::LibJXL PkgConfig::LibJXLThreads)
|
target_link_libraries(kimg_jxl PkgConfig::LibJXL PkgConfig::LibJXLThreads)
|
||||||
install(FILES jxl.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
if (LibJXL_VERSION VERSION_GREATER_EQUAL "0.7.0")
|
||||||
|
target_compile_definitions(kimg_jxl PRIVATE KIMG_JXL_API_VERSION=70)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||||
|
install(FILES jxl.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
##################################
|
##################################
|
||||||
|
|
||||||
kimageformats_add_plugin(kimg_pcx SOURCES pcx.cpp)
|
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)
|
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)
|
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)
|
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)
|
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)
|
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)
|
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()
|
||||||
|
|
||||||
##################################
|
##################################
|
||||||
|
|
||||||
@ -130,10 +179,14 @@ if (KF5Archive_FOUND)
|
|||||||
|
|
||||||
kimageformats_add_plugin(kimg_kra SOURCES kra.cpp)
|
kimageformats_add_plugin(kimg_kra SOURCES kra.cpp)
|
||||||
target_link_libraries(kimg_kra KF5::Archive)
|
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)
|
kimageformats_add_plugin(kimg_ora SOURCES ora.cpp)
|
||||||
target_link_libraries(kimg_ora KF5::Archive)
|
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()
|
endif()
|
||||||
|
@ -521,6 +521,9 @@ bool ANIHandler::canRead(QIODevice *device)
|
|||||||
qWarning("ANIHandler::canRead() called with no device");
|
qWarning("ANIHandler::canRead() called with no device");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (device->isSequential()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const QByteArray riffIntro = device->peek(12);
|
const QByteArray riffIntro = device->peek(12);
|
||||||
|
|
||||||
|
@ -12,6 +12,8 @@
|
|||||||
#include <QColorSpace>
|
#include <QColorSpace>
|
||||||
|
|
||||||
#include "avif_p.h"
|
#include "avif_p.h"
|
||||||
|
#include "util_p.h"
|
||||||
|
|
||||||
#include <cfloat>
|
#include <cfloat>
|
||||||
|
|
||||||
QAVIFHandler::QAVIFHandler()
|
QAVIFHandler::QAVIFHandler()
|
||||||
@ -40,6 +42,11 @@ bool QAVIFHandler::canRead() const
|
|||||||
|
|
||||||
if (m_parseState != ParseAvifError) {
|
if (m_parseState != ParseAvifError) {
|
||||||
setFormat("avif");
|
setFormat("avif");
|
||||||
|
|
||||||
|
if (m_parseState == ParseAvifFinished) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -56,7 +63,7 @@ bool QAVIFHandler::canRead(QIODevice *device)
|
|||||||
}
|
}
|
||||||
|
|
||||||
avifROData input;
|
avifROData input;
|
||||||
input.data = (const uint8_t *)header.constData();
|
input.data = reinterpret_cast<const uint8_t *>(header.constData());
|
||||||
input.size = header.size();
|
input.size = header.size();
|
||||||
|
|
||||||
if (avifPeekCompatibleFileType(&input)) {
|
if (avifPeekCompatibleFileType(&input)) {
|
||||||
@ -67,7 +74,7 @@ bool QAVIFHandler::canRead(QIODevice *device)
|
|||||||
|
|
||||||
bool QAVIFHandler::ensureParsed() const
|
bool QAVIFHandler::ensureParsed() const
|
||||||
{
|
{
|
||||||
if (m_parseState == ParseAvifSuccess) {
|
if (m_parseState == ParseAvifSuccess || m_parseState == ParseAvifMetadata || m_parseState == ParseAvifFinished) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (m_parseState == ParseAvifError) {
|
if (m_parseState == ParseAvifError) {
|
||||||
@ -79,6 +86,28 @@ bool QAVIFHandler::ensureParsed() const
|
|||||||
return that->ensureDecoder();
|
return that->ensureDecoder();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool QAVIFHandler::ensureOpened() const
|
||||||
|
{
|
||||||
|
if (m_parseState == ParseAvifSuccess || m_parseState == ParseAvifFinished) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (m_parseState == ParseAvifError) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QAVIFHandler *that = const_cast<QAVIFHandler *>(this);
|
||||||
|
if (ensureParsed()) {
|
||||||
|
if (m_parseState == ParseAvifMetadata) {
|
||||||
|
bool success = that->jumpToNextImage();
|
||||||
|
that->m_parseState = success ? ParseAvifSuccess : ParseAvifError;
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
that->m_parseState = ParseAvifError;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool QAVIFHandler::ensureDecoder()
|
bool QAVIFHandler::ensureDecoder()
|
||||||
{
|
{
|
||||||
if (m_decoder) {
|
if (m_decoder) {
|
||||||
@ -87,7 +116,7 @@ bool QAVIFHandler::ensureDecoder()
|
|||||||
|
|
||||||
m_rawData = device()->readAll();
|
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();
|
m_rawAvifData.size = m_rawData.size();
|
||||||
|
|
||||||
if (avifPeekCompatibleFileType(&m_rawAvifData) == AVIF_FALSE) {
|
if (avifPeekCompatibleFileType(&m_rawAvifData) == AVIF_FALSE) {
|
||||||
@ -97,6 +126,9 @@ bool QAVIFHandler::ensureDecoder()
|
|||||||
|
|
||||||
m_decoder = avifDecoderCreate();
|
m_decoder = avifDecoderCreate();
|
||||||
|
|
||||||
|
m_decoder->ignoreExif = AVIF_TRUE;
|
||||||
|
m_decoder->ignoreXMP = AVIF_TRUE;
|
||||||
|
|
||||||
#if AVIF_VERSION >= 80400
|
#if AVIF_VERSION >= 80400
|
||||||
m_decoder->maxThreads = qBound(1, QThread::idealThreadCount(), 64);
|
m_decoder->maxThreads = qBound(1, QThread::idealThreadCount(), 64);
|
||||||
#endif
|
#endif
|
||||||
@ -105,6 +137,10 @@ bool QAVIFHandler::ensureDecoder()
|
|||||||
m_decoder->strictFlags = AVIF_STRICT_DISABLED;
|
m_decoder->strictFlags = AVIF_STRICT_DISABLED;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if AVIF_VERSION >= 110000
|
||||||
|
m_decoder->imageDimensionLimit = 65535;
|
||||||
|
#endif
|
||||||
|
|
||||||
avifResult decodeResult;
|
avifResult decodeResult;
|
||||||
|
|
||||||
decodeResult = avifDecoderSetIOMemory(m_decoder, m_rawAvifData.data, m_rawAvifData.size);
|
decodeResult = avifDecoderSetIOMemory(m_decoder, m_rawAvifData.data, m_rawAvifData.size);
|
||||||
@ -127,45 +163,58 @@ bool QAVIFHandler::ensureDecoder()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
decodeResult = avifDecoderNextImage(m_decoder);
|
m_container_width = m_decoder->image->width;
|
||||||
|
m_container_height = m_decoder->image->height;
|
||||||
|
|
||||||
if (decodeResult == AVIF_RESULT_OK) {
|
if ((m_container_width > 65535) || (m_container_height > 65535)) {
|
||||||
m_container_width = m_decoder->image->width;
|
qWarning("AVIF image (%dx%d) is too large!", m_container_width, m_container_height);
|
||||||
m_container_height = m_decoder->image->height;
|
m_parseState = ParseAvifError;
|
||||||
|
return false;
|
||||||
if ((m_container_width > 65535) || (m_container_height > 65535)) {
|
|
||||||
qWarning("AVIF image (%dx%d) is too large!", m_container_width, m_container_height);
|
|
||||||
m_parseState = ParseAvifError;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((m_container_width == 0) || (m_container_height == 0)) {
|
|
||||||
qWarning("Empty image, nothing to decode");
|
|
||||||
m_parseState = ParseAvifError;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_container_width > ((16384 * 16384) / m_container_height)) {
|
|
||||||
qWarning("AVIF image (%dx%d) has more than 256 megapixels!", m_container_width, m_container_height);
|
|
||||||
m_parseState = ParseAvifError;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_parseState = ParseAvifSuccess;
|
|
||||||
if (decode_one_frame()) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
m_parseState = ParseAvifError;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
qWarning("ERROR: Failed to decode image: %s", avifResultToString(decodeResult));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
avifDecoderDestroy(m_decoder);
|
if ((m_container_width == 0) || (m_container_height == 0)) {
|
||||||
m_decoder = nullptr;
|
qWarning("Empty image, nothing to decode");
|
||||||
m_parseState = ParseAvifError;
|
m_parseState = ParseAvifError;
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_container_width > ((16384 * 16384) / m_container_height)) {
|
||||||
|
qWarning("AVIF image (%dx%d) has more than 256 megapixels!", m_container_width, m_container_height);
|
||||||
|
m_parseState = ParseAvifError;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate final dimensions with crop and rotate operations applied
|
||||||
|
int new_width = m_container_width;
|
||||||
|
int new_height = m_container_height;
|
||||||
|
|
||||||
|
if (m_decoder->image->transformFlags & AVIF_TRANSFORM_CLAP) {
|
||||||
|
if ((m_decoder->image->clap.widthD > 0) && (m_decoder->image->clap.heightD > 0) && (m_decoder->image->clap.horizOffD > 0)
|
||||||
|
&& (m_decoder->image->clap.vertOffD > 0)) {
|
||||||
|
int crop_width = (int)((double)(m_decoder->image->clap.widthN) / (m_decoder->image->clap.widthD) + 0.5);
|
||||||
|
if (crop_width < new_width && crop_width > 0) {
|
||||||
|
new_width = crop_width;
|
||||||
|
}
|
||||||
|
int crop_height = (int)((double)(m_decoder->image->clap.heightN) / (m_decoder->image->clap.heightD) + 0.5);
|
||||||
|
if (crop_height < new_height && crop_height > 0) {
|
||||||
|
new_height = crop_height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_decoder->image->transformFlags & AVIF_TRANSFORM_IROT) {
|
||||||
|
if (m_decoder->image->irot.angle == 1 || m_decoder->image->irot.angle == 3) {
|
||||||
|
int tmp = new_width;
|
||||||
|
new_width = new_height;
|
||||||
|
new_height = tmp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_estimated_dimensions.setWidth(new_width);
|
||||||
|
m_estimated_dimensions.setHeight(new_height);
|
||||||
|
|
||||||
|
m_parseState = ParseAvifMetadata;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool QAVIFHandler::decode_one_frame()
|
bool QAVIFHandler::decode_one_frame()
|
||||||
@ -192,13 +241,13 @@ bool QAVIFHandler::decode_one_frame()
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (loadalpha) {
|
if (loadalpha) {
|
||||||
resultformat = QImage::Format_RGBA8888;
|
resultformat = QImage::Format_ARGB32;
|
||||||
} else {
|
} else {
|
||||||
resultformat = QImage::Format_RGBX8888;
|
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()) {
|
if (result.isNull()) {
|
||||||
qWarning("Memory cannot be allocated");
|
qWarning("Memory cannot be allocated");
|
||||||
return false;
|
return false;
|
||||||
@ -206,7 +255,7 @@ bool QAVIFHandler::decode_one_frame()
|
|||||||
|
|
||||||
QColorSpace colorspace;
|
QColorSpace colorspace;
|
||||||
if (m_decoder->image->icc.data && (m_decoder->image->icc.size > 0)) {
|
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);
|
colorspace = QColorSpace::fromIccProfile(icc_data);
|
||||||
if (!colorspace.isValid()) {
|
if (!colorspace.isValid()) {
|
||||||
qWarning("AVIF image has Qt-unsupported or invalid ICC profile!");
|
qWarning("AVIF image has Qt-unsupported or invalid ICC profile!");
|
||||||
@ -285,14 +334,16 @@ bool QAVIFHandler::decode_one_frame()
|
|||||||
rgb.depth = 16;
|
rgb.depth = 16;
|
||||||
rgb.format = AVIF_RGB_FORMAT_RGBA;
|
rgb.format = AVIF_RGB_FORMAT_RGBA;
|
||||||
|
|
||||||
if (!loadalpha) {
|
if (!loadalpha && (m_decoder->image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400)) {
|
||||||
if (m_decoder->image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400) {
|
resultformat = QImage::Format_Grayscale16;
|
||||||
resultformat = QImage::Format_Grayscale16;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
rgb.depth = 8;
|
rgb.depth = 8;
|
||||||
rgb.format = AVIF_RGB_FORMAT_RGBA;
|
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
|
||||||
|
rgb.format = AVIF_RGB_FORMAT_BGRA;
|
||||||
|
#else
|
||||||
|
rgb.format = AVIF_RGB_FORMAT_ARGB;
|
||||||
|
#endif
|
||||||
|
|
||||||
#if AVIF_VERSION >= 80400
|
#if AVIF_VERSION >= 80400
|
||||||
if (m_decoder->imageCount > 1) {
|
if (m_decoder->imageCount > 1) {
|
||||||
@ -301,14 +352,8 @@ bool QAVIFHandler::decode_one_frame()
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (loadalpha) {
|
if (!loadalpha && (m_decoder->image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400)) {
|
||||||
resultformat = QImage::Format_ARGB32;
|
resultformat = QImage::Format_Grayscale8;
|
||||||
} else {
|
|
||||||
if (m_decoder->image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400) {
|
|
||||||
resultformat = QImage::Format_Grayscale8;
|
|
||||||
} else {
|
|
||||||
resultformat = QImage::Format_RGB32;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -399,13 +444,15 @@ bool QAVIFHandler::decode_one_frame()
|
|||||||
m_current_image = result.convertToFormat(resultformat);
|
m_current_image = result.convertToFormat(resultformat);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_estimated_dimensions = m_current_image.size();
|
||||||
|
|
||||||
m_must_jump_to_next_image = false;
|
m_must_jump_to_next_image = false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool QAVIFHandler::read(QImage *image)
|
bool QAVIFHandler::read(QImage *image)
|
||||||
{
|
{
|
||||||
if (!ensureParsed()) {
|
if (!ensureOpened()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -416,6 +463,13 @@ bool QAVIFHandler::read(QImage *image)
|
|||||||
*image = m_current_image;
|
*image = m_current_image;
|
||||||
if (imageCount() >= 2) {
|
if (imageCount() >= 2) {
|
||||||
m_must_jump_to_next_image = true;
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
@ -713,7 +767,7 @@ bool QAVIFHandler::write(const QImage &image)
|
|||||||
avif->transferCharacteristics = transfer_to_save;
|
avif->transferCharacteristics = transfer_to_save;
|
||||||
|
|
||||||
if (iccprofile.size() > 0) {
|
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;
|
avifRGBImage rgb;
|
||||||
@ -764,7 +818,7 @@ bool QAVIFHandler::write(const QImage &image)
|
|||||||
avifImageDestroy(avif);
|
avifImageDestroy(avif);
|
||||||
|
|
||||||
if (res == AVIF_RESULT_OK) {
|
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);
|
avifRWDataFree(&raw);
|
||||||
|
|
||||||
if (status > 0) {
|
if (status > 0) {
|
||||||
@ -792,7 +846,7 @@ QVariant QAVIFHandler::option(ImageOption option) const
|
|||||||
|
|
||||||
switch (option) {
|
switch (option) {
|
||||||
case Size:
|
case Size:
|
||||||
return m_current_image.size();
|
return m_estimated_dimensions;
|
||||||
case Animation:
|
case Animation:
|
||||||
if (imageCount() >= 2) {
|
if (imageCount() >= 2) {
|
||||||
return true;
|
return true;
|
||||||
@ -848,6 +902,14 @@ int QAVIFHandler::currentImageNumber() const
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_parseState == ParseAvifMetadata) {
|
||||||
|
if (m_decoder->imageCount >= 2) {
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return m_decoder->imageIndex;
|
return m_decoder->imageIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -857,12 +919,15 @@ bool QAVIFHandler::jumpToNextImage()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_decoder->imageCount < 2) {
|
if (m_decoder->imageIndex >= 0) {
|
||||||
return true;
|
if (m_decoder->imageCount < 2) {
|
||||||
}
|
m_parseState = ParseAvifSuccess;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (m_decoder->imageIndex >= m_decoder->imageCount - 1) { // start from beginning
|
if (m_decoder->imageIndex >= m_decoder->imageCount - 1) { // start from beginning
|
||||||
avifDecoderReset(m_decoder);
|
avifDecoderReset(m_decoder);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
avifResult decodeResult = avifDecoderNextImage(m_decoder);
|
avifResult decodeResult = avifDecoderNextImage(m_decoder);
|
||||||
@ -885,6 +950,7 @@ bool QAVIFHandler::jumpToNextImage()
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (decode_one_frame()) {
|
if (decode_one_frame()) {
|
||||||
|
m_parseState = ParseAvifSuccess;
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
m_parseState = ParseAvifError;
|
m_parseState = ParseAvifError;
|
||||||
@ -900,10 +966,12 @@ bool QAVIFHandler::jumpToImage(int imageNumber)
|
|||||||
|
|
||||||
if (m_decoder->imageCount < 2) { // not an animation
|
if (m_decoder->imageCount < 2) { // not an animation
|
||||||
if (imageNumber == 0) {
|
if (imageNumber == 0) {
|
||||||
return true;
|
if (ensureOpened()) {
|
||||||
} else {
|
m_parseState = ParseAvifSuccess;
|
||||||
return false;
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (imageNumber < 0 || imageNumber >= m_decoder->imageCount) { // wrong index
|
if (imageNumber < 0 || imageNumber >= m_decoder->imageCount) { // wrong index
|
||||||
@ -912,6 +980,7 @@ bool QAVIFHandler::jumpToImage(int imageNumber)
|
|||||||
|
|
||||||
if (imageNumber == m_decoder->imageIndex) { // we are here already
|
if (imageNumber == m_decoder->imageIndex) { // we are here already
|
||||||
m_must_jump_to_next_image = false;
|
m_must_jump_to_next_image = false;
|
||||||
|
m_parseState = ParseAvifSuccess;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -935,6 +1004,7 @@ bool QAVIFHandler::jumpToImage(int imageNumber)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (decode_one_frame()) {
|
if (decode_one_frame()) {
|
||||||
|
m_parseState = ParseAvifSuccess;
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
m_parseState = ParseAvifError;
|
m_parseState = ParseAvifError;
|
||||||
@ -944,7 +1014,7 @@ bool QAVIFHandler::jumpToImage(int imageNumber)
|
|||||||
|
|
||||||
int QAVIFHandler::nextImageDelay() const
|
int QAVIFHandler::nextImageDelay() const
|
||||||
{
|
{
|
||||||
if (!ensureParsed()) {
|
if (!ensureOpened()) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -969,7 +1039,8 @@ int QAVIFHandler::loopCount() const
|
|||||||
return 0;
|
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)
|
QPointF QAVIFHandler::CompatibleChromacity(qreal chrX, qreal chrY)
|
||||||
@ -986,12 +1057,26 @@ QPointF QAVIFHandler::CompatibleChromacity(qreal chrX, qreal chrY)
|
|||||||
|
|
||||||
QImageIOPlugin::Capabilities QAVIFPlugin::capabilities(QIODevice *device, const QByteArray &format) const
|
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") {
|
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") {
|
if (format == "avifs") {
|
||||||
return Capabilities(CanRead);
|
Capabilities format_cap;
|
||||||
|
if (isAvifDecoderAvailable) {
|
||||||
|
format_cap |= CanRead;
|
||||||
|
}
|
||||||
|
return format_cap;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!format.isEmpty()) {
|
if (!format.isEmpty()) {
|
||||||
@ -1002,10 +1087,10 @@ QImageIOPlugin::Capabilities QAVIFPlugin::capabilities(QIODevice *device, const
|
|||||||
}
|
}
|
||||||
|
|
||||||
Capabilities cap;
|
Capabilities cap;
|
||||||
if (device->isReadable() && QAVIFHandler::canRead(device)) {
|
if (device->isReadable() && QAVIFHandler::canRead(device) && isAvifDecoderAvailable) {
|
||||||
cap |= CanRead;
|
cap |= CanRead;
|
||||||
}
|
}
|
||||||
if (device->isWritable()) {
|
if (device->isWritable() && isAvifEncoderAvailable) {
|
||||||
cap |= CanWrite;
|
cap |= CanWrite;
|
||||||
}
|
}
|
||||||
return cap;
|
return cap;
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
#include <QImage>
|
#include <QImage>
|
||||||
#include <QImageIOPlugin>
|
#include <QImageIOPlugin>
|
||||||
#include <QPointF>
|
#include <QPointF>
|
||||||
|
#include <QSize>
|
||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
#include <avif/avif.h>
|
#include <avif/avif.h>
|
||||||
#include <qimageiohandler.h>
|
#include <qimageiohandler.h>
|
||||||
@ -45,6 +46,7 @@ public:
|
|||||||
private:
|
private:
|
||||||
static QPointF CompatibleChromacity(qreal chrX, qreal chrY);
|
static QPointF CompatibleChromacity(qreal chrX, qreal chrY);
|
||||||
bool ensureParsed() const;
|
bool ensureParsed() const;
|
||||||
|
bool ensureOpened() const;
|
||||||
bool ensureDecoder();
|
bool ensureDecoder();
|
||||||
bool decode_one_frame();
|
bool decode_one_frame();
|
||||||
|
|
||||||
@ -52,6 +54,8 @@ private:
|
|||||||
ParseAvifError = -1,
|
ParseAvifError = -1,
|
||||||
ParseAvifNotParsed = 0,
|
ParseAvifNotParsed = 0,
|
||||||
ParseAvifSuccess = 1,
|
ParseAvifSuccess = 1,
|
||||||
|
ParseAvifMetadata = 2,
|
||||||
|
ParseAvifFinished = 3,
|
||||||
};
|
};
|
||||||
|
|
||||||
ParseAvifState m_parseState;
|
ParseAvifState m_parseState;
|
||||||
@ -59,6 +63,7 @@ private:
|
|||||||
|
|
||||||
uint32_t m_container_width;
|
uint32_t m_container_width;
|
||||||
uint32_t m_container_height;
|
uint32_t m_container_height;
|
||||||
|
QSize m_estimated_dimensions;
|
||||||
|
|
||||||
QByteArray m_rawData;
|
QByteArray m_rawData;
|
||||||
avifROData m_rawAvifData;
|
avifROData m_rawAvifData;
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "exr_p.h"
|
#include "exr_p.h"
|
||||||
|
#include "util_p.h"
|
||||||
|
|
||||||
#include <IexThrowErrnoExc.h>
|
#include <IexThrowErrnoExc.h>
|
||||||
#include <ImathBox.h>
|
#include <ImathBox.h>
|
||||||
@ -191,7 +192,7 @@ bool EXRHandler::read(QImage *outImage)
|
|||||||
width = dw.max.x - dw.min.x + 1;
|
width = dw.max.x - dw.min.x + 1;
|
||||||
height = dw.max.y - dw.min.y + 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()) {
|
if (image.isNull()) {
|
||||||
qWarning() << "Failed to allocate image, invalid size?" << QSize(width, height);
|
qWarning() << "Failed to allocate image, invalid size?" << QSize(width, height);
|
||||||
return false;
|
return false;
|
||||||
|
35
src/imageformats/fastmath_p.h
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
Approximated math functions used into conversions.
|
||||||
|
|
||||||
|
SPDX-FileCopyrightText: Edward Kmett
|
||||||
|
SPDX-FileCopyrightText: 2023 Mirco Miranda <mircomir@outlook.com>
|
||||||
|
|
||||||
|
SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
*/
|
||||||
|
#ifndef FASTMATH_P_H
|
||||||
|
#define FASTMATH_P_H
|
||||||
|
|
||||||
|
#include <QtGlobal>
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief fastPow
|
||||||
|
* Based on Edward Kmett code released into the public domain.
|
||||||
|
* See also: https://github.com/ekmett/approximate
|
||||||
|
*/
|
||||||
|
inline double fastPow(double x, double y)
|
||||||
|
{
|
||||||
|
union {
|
||||||
|
double d;
|
||||||
|
qint32 i[2];
|
||||||
|
} u = {x};
|
||||||
|
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
|
||||||
|
u.i[1] = qint32(y * (u.i[1] - 1072632447) + 1072632447);
|
||||||
|
u.i[0] = 0;
|
||||||
|
#else // never tested
|
||||||
|
u.i[0] = qint32(y * (u.i[0] - 1072632447) + 1072632447);
|
||||||
|
u.i[1] = 0;
|
||||||
|
#endif
|
||||||
|
return u.d;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // FASTMATH_P_H
|
@ -7,6 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "hdr_p.h"
|
#include "hdr_p.h"
|
||||||
|
#include "util_p.h"
|
||||||
|
|
||||||
#include <QDataStream>
|
#include <QDataStream>
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
@ -93,7 +94,7 @@ static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &i
|
|||||||
uchar code;
|
uchar code;
|
||||||
|
|
||||||
// Create dst image.
|
// Create dst image.
|
||||||
img = QImage(width, height, QImage::Format_RGB32);
|
img = imageAlloc(width, height, QImage::Format_RGB32);
|
||||||
if (img.isNull()) {
|
if (img.isNull()) {
|
||||||
qCDebug(HDRPLUGIN) << "Couldn't create image with size" << width << height << "and format RGB32";
|
qCDebug(HDRPLUGIN) << "Couldn't create image with size" << width << height << "and format RGB32";
|
||||||
return false;
|
return false;
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
#include <QImageIOPlugin>
|
#include <QImageIOPlugin>
|
||||||
|
#include <QMutex>
|
||||||
|
|
||||||
class HEIFHandler : public QImageIOHandler
|
class HEIFHandler : public QImageIOHandler
|
||||||
{
|
{
|
||||||
@ -29,6 +30,9 @@ public:
|
|||||||
void setOption(ImageOption option, const QVariant &value) override;
|
void setOption(ImageOption option, const QVariant &value) override;
|
||||||
bool supportsOption(ImageOption option) const override;
|
bool supportsOption(ImageOption option) const override;
|
||||||
|
|
||||||
|
static bool isHeifDecoderAvailable();
|
||||||
|
static bool isHeifEncoderAvailable();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static bool isSupportedBMFFType(const QByteArray &header);
|
static bool isSupportedBMFFType(const QByteArray &header);
|
||||||
bool ensureParsed() const;
|
bool ensureParsed() const;
|
||||||
@ -43,6 +47,18 @@ private:
|
|||||||
ParseHeicState m_parseState;
|
ParseHeicState m_parseState;
|
||||||
int m_quality;
|
int m_quality;
|
||||||
QImage m_current_image;
|
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
|
class HEIFPlugin : public QImageIOPlugin
|
||||||
|
@ -10,6 +10,8 @@
|
|||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
|
|
||||||
#include "jxl_p.h"
|
#include "jxl_p.h"
|
||||||
|
#include "util_p.h"
|
||||||
|
|
||||||
#include <jxl/encode.h>
|
#include <jxl/encode.h>
|
||||||
#include <jxl/thread_parallel_runner.h>
|
#include <jxl/thread_parallel_runner.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
@ -46,6 +48,11 @@ bool QJpegXLHandler::canRead() const
|
|||||||
|
|
||||||
if (m_parseState != ParseJpegXLError) {
|
if (m_parseState != ParseJpegXLError) {
|
||||||
setFormat("jxl");
|
setFormat("jxl");
|
||||||
|
|
||||||
|
if (m_parseState == ParseJpegXLFinished) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -61,7 +68,7 @@ bool QJpegXLHandler::canRead(QIODevice *device)
|
|||||||
return false;
|
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) {
|
if (signature == JXL_SIG_CODESTREAM || signature == JXL_SIG_CONTAINER) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -70,7 +77,7 @@ bool QJpegXLHandler::canRead(QIODevice *device)
|
|||||||
|
|
||||||
bool QJpegXLHandler::ensureParsed() const
|
bool QJpegXLHandler::ensureParsed() const
|
||||||
{
|
{
|
||||||
if (m_parseState == ParseJpegXLSuccess || m_parseState == ParseJpegXLBasicInfoParsed) {
|
if (m_parseState == ParseJpegXLSuccess || m_parseState == ParseJpegXLBasicInfoParsed || m_parseState == ParseJpegXLFinished) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (m_parseState == ParseJpegXLError) {
|
if (m_parseState == ParseJpegXLError) {
|
||||||
@ -88,7 +95,7 @@ bool QJpegXLHandler::ensureALLCounted() const
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_parseState == ParseJpegXLSuccess) {
|
if (m_parseState == ParseJpegXLSuccess || m_parseState == ParseJpegXLFinished) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,7 +116,7 @@ bool QJpegXLHandler::ensureDecoder()
|
|||||||
return false;
|
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) {
|
if (signature != JXL_SIG_CODESTREAM && signature != JXL_SIG_CONTAINER) {
|
||||||
m_parseState = ParseJpegXLError;
|
m_parseState = ParseJpegXLError;
|
||||||
return false;
|
return false;
|
||||||
@ -137,12 +144,16 @@ 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");
|
qWarning("ERROR: JxlDecoderSetInput failed");
|
||||||
m_parseState = ParseJpegXLError;
|
m_parseState = ParseJpegXLError;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef KIMG_JXL_API_VERSION
|
||||||
|
JxlDecoderCloseInput(m_decoder);
|
||||||
|
#endif
|
||||||
|
|
||||||
JxlDecoderStatus status = JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING | JXL_DEC_FRAME);
|
JxlDecoderStatus status = JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING | JXL_DEC_FRAME);
|
||||||
if (status == JXL_DEC_ERROR) {
|
if (status == JXL_DEC_ERROR) {
|
||||||
qWarning("ERROR: JxlDecoderSubscribeEvents failed");
|
qWarning("ERROR: JxlDecoderSubscribeEvents failed");
|
||||||
@ -267,8 +278,12 @@ bool QJpegXLHandler::countALLFrames()
|
|||||||
size_t icc_size = 0;
|
size_t icc_size = 0;
|
||||||
if (JxlDecoderGetICCProfileSize(m_decoder, &m_input_pixel_format, JXL_COLOR_PROFILE_TARGET_DATA, &icc_size) == JXL_DEC_SUCCESS) {
|
if (JxlDecoderGetICCProfileSize(m_decoder, &m_input_pixel_format, JXL_COLOR_PROFILE_TARGET_DATA, &icc_size) == JXL_DEC_SUCCESS) {
|
||||||
if (icc_size > 0) {
|
if (icc_size > 0) {
|
||||||
QByteArray icc_data((int)icc_size, 0);
|
QByteArray icc_data(icc_size, 0);
|
||||||
if (JxlDecoderGetColorAsICCProfile(m_decoder, &m_input_pixel_format, JXL_COLOR_PROFILE_TARGET_DATA, (uint8_t *)icc_data.data(), icc_data.size())
|
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) {
|
== JXL_DEC_SUCCESS) {
|
||||||
m_colorspace = QColorSpace::fromIccProfile(icc_data);
|
m_colorspace = QColorSpace::fromIccProfile(icc_data);
|
||||||
|
|
||||||
@ -355,7 +370,7 @@ bool QJpegXLHandler::decode_one_frame()
|
|||||||
return false;
|
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()) {
|
if (m_current_image.isNull()) {
|
||||||
qWarning("Memory cannot be allocated");
|
qWarning("Memory cannot be allocated");
|
||||||
m_parseState = ParseJpegXLError;
|
m_parseState = ParseJpegXLError;
|
||||||
@ -391,7 +406,15 @@ bool QJpegXLHandler::decode_one_frame()
|
|||||||
if (!rewind()) {
|
if (!rewind()) {
|
||||||
return false;
|
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;
|
return true;
|
||||||
@ -482,37 +505,15 @@ bool QJpegXLHandler::write(const QImage &image)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void *runner = nullptr;
|
|
||||||
int num_worker_threads = qBound(1, QThread::idealThreadCount(), 64);
|
|
||||||
|
|
||||||
if (num_worker_threads > 1) {
|
|
||||||
runner = JxlThreadParallelRunnerCreate(nullptr, num_worker_threads);
|
|
||||||
if (JxlEncoderSetParallelRunner(encoder, JxlThreadParallelRunner, runner) != JXL_ENC_SUCCESS) {
|
|
||||||
qWarning("JxlEncoderSetParallelRunner failed");
|
|
||||||
JxlThreadParallelRunnerDestroy(runner);
|
|
||||||
JxlEncoderDestroy(encoder);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
JxlEncoderOptions *encoder_options = JxlEncoderOptionsCreate(encoder, nullptr);
|
|
||||||
|
|
||||||
if (m_quality > 100) {
|
if (m_quality > 100) {
|
||||||
m_quality = 100;
|
m_quality = 100;
|
||||||
} else if (m_quality < 0) {
|
} else if (m_quality < 0) {
|
||||||
m_quality = 90;
|
m_quality = 90;
|
||||||
}
|
}
|
||||||
|
|
||||||
JxlEncoderOptionsSetDistance(encoder_options, (100.0f - m_quality) / 10.0f);
|
|
||||||
|
|
||||||
JxlEncoderOptionsSetLossless(encoder_options, (m_quality == 100) ? JXL_TRUE : JXL_FALSE);
|
|
||||||
|
|
||||||
JxlBasicInfo output_info;
|
JxlBasicInfo output_info;
|
||||||
JxlEncoderInitBasicInfo(&output_info);
|
JxlEncoderInitBasicInfo(&output_info);
|
||||||
|
|
||||||
JxlColorEncoding color_profile;
|
|
||||||
JxlColorEncodingSetToSRGB(&color_profile, JXL_FALSE);
|
|
||||||
|
|
||||||
bool convert_color_profile;
|
bool convert_color_profile;
|
||||||
QByteArray iccprofile;
|
QByteArray iccprofile;
|
||||||
|
|
||||||
@ -526,7 +527,28 @@ bool QJpegXLHandler::write(const QImage &image)
|
|||||||
convert_color_profile = false;
|
convert_color_profile = false;
|
||||||
iccprofile = image.colorSpace().iccProfile();
|
iccprofile = image.colorSpace().iccProfile();
|
||||||
if (iccprofile.size() > 0 || m_quality == 100) {
|
if (iccprofile.size() > 0 || m_quality == 100) {
|
||||||
output_info.uses_original_profile = 1;
|
output_info.uses_original_profile = JXL_TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (save_depth == 16 && (image.hasAlphaChannel() || output_info.uses_original_profile)) {
|
||||||
|
output_info.have_container = JXL_TRUE;
|
||||||
|
JxlEncoderUseContainer(encoder, JXL_TRUE);
|
||||||
|
#ifdef KIMG_JXL_API_VERSION
|
||||||
|
JxlEncoderSetCodestreamLevel(encoder, 10);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void *runner = nullptr;
|
||||||
|
int num_worker_threads = qBound(1, QThread::idealThreadCount(), 64);
|
||||||
|
|
||||||
|
if (num_worker_threads > 1) {
|
||||||
|
runner = JxlThreadParallelRunnerCreate(nullptr, num_worker_threads);
|
||||||
|
if (JxlEncoderSetParallelRunner(encoder, JxlThreadParallelRunner, runner) != JXL_ENC_SUCCESS) {
|
||||||
|
qWarning("JxlEncoderSetParallelRunner failed");
|
||||||
|
JxlThreadParallelRunnerDestroy(runner);
|
||||||
|
JxlEncoderDestroy(encoder);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -537,7 +559,6 @@ bool QJpegXLHandler::write(const QImage &image)
|
|||||||
pixel_format.endianness = JXL_NATIVE_ENDIAN;
|
pixel_format.endianness = JXL_NATIVE_ENDIAN;
|
||||||
pixel_format.align = 0;
|
pixel_format.align = 0;
|
||||||
|
|
||||||
output_info.intensity_target = 255.0f;
|
|
||||||
output_info.orientation = JXL_ORIENT_IDENTITY;
|
output_info.orientation = JXL_ORIENT_IDENTITY;
|
||||||
output_info.num_color_channels = 3;
|
output_info.num_color_channels = 3;
|
||||||
output_info.animation.tps_numerator = 10;
|
output_info.animation.tps_numerator = 10;
|
||||||
@ -605,7 +626,7 @@ bool QJpegXLHandler::write(const QImage &image)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!convert_color_profile && iccprofile.size() > 0) {
|
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) {
|
if (status != JXL_ENC_SUCCESS) {
|
||||||
qWarning("JxlEncoderSetICCProfile failed!");
|
qWarning("JxlEncoderSetICCProfile failed!");
|
||||||
if (runner) {
|
if (runner) {
|
||||||
@ -615,6 +636,9 @@ bool QJpegXLHandler::write(const QImage &image)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
JxlColorEncoding color_profile;
|
||||||
|
JxlColorEncodingSetToSRGB(&color_profile, JXL_FALSE);
|
||||||
|
|
||||||
status = JxlEncoderSetColorEncoding(encoder, &color_profile);
|
status = JxlEncoderSetColorEncoding(encoder, &color_profile);
|
||||||
if (status != JXL_ENC_SUCCESS) {
|
if (status != JXL_ENC_SUCCESS) {
|
||||||
qWarning("JxlEncoderSetColorEncoding failed!");
|
qWarning("JxlEncoderSetColorEncoding failed!");
|
||||||
@ -626,8 +650,22 @@ bool QJpegXLHandler::write(const QImage &image)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef KIMG_JXL_API_VERSION
|
||||||
|
JxlEncoderFrameSettings *encoder_options = JxlEncoderFrameSettingsCreate(encoder, nullptr);
|
||||||
|
|
||||||
|
JxlEncoderSetFrameDistance(encoder_options, (100.0f - m_quality) / 10.0f);
|
||||||
|
|
||||||
|
JxlEncoderSetFrameLossless(encoder_options, (m_quality == 100) ? JXL_TRUE : JXL_FALSE);
|
||||||
|
#else
|
||||||
|
JxlEncoderOptions *encoder_options = JxlEncoderOptionsCreate(encoder, nullptr);
|
||||||
|
|
||||||
|
JxlEncoderOptionsSetDistance(encoder_options, (100.0f - m_quality) / 10.0f);
|
||||||
|
|
||||||
|
JxlEncoderOptionsSetLossless(encoder_options, (m_quality == 100) ? JXL_TRUE : JXL_FALSE);
|
||||||
|
#endif
|
||||||
|
|
||||||
if (image.hasAlphaChannel() || ((save_depth == 8) && (xsize % 4 == 0))) {
|
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 {
|
} else {
|
||||||
if (save_depth > 8) { // 16bit depth without alpha channel
|
if (save_depth > 8) { // 16bit depth without alpha channel
|
||||||
uint16_t *tmp_buffer = new (std::nothrow) uint16_t[3 * xsize * ysize];
|
uint16_t *tmp_buffer = new (std::nothrow) uint16_t[3 * xsize * ysize];
|
||||||
@ -658,7 +696,7 @@ bool QJpegXLHandler::write(const QImage &image)
|
|||||||
src_pixels += 2; // skipalpha
|
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;
|
delete[] tmp_buffer;
|
||||||
} else { // 8bit depth without alpha channel
|
} else { // 8bit depth without alpha channel
|
||||||
uchar *tmp_buffer8 = new (std::nothrow) uchar[3 * xsize * ysize];
|
uchar *tmp_buffer8 = new (std::nothrow) uchar[3 * xsize * ysize];
|
||||||
@ -677,7 +715,7 @@ bool QJpegXLHandler::write(const QImage &image)
|
|||||||
memcpy(dest_pixels8, tmpimage.constScanLine(y), rowbytes);
|
memcpy(dest_pixels8, tmpimage.constScanLine(y), rowbytes);
|
||||||
dest_pixels8 += 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;
|
delete[] tmp_buffer8;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -724,7 +762,7 @@ bool QJpegXLHandler::write(const QImage &image)
|
|||||||
compressed.resize(next_out - compressed.data());
|
compressed.resize(next_out - compressed.data());
|
||||||
|
|
||||||
if (compressed.size() > 0) {
|
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) {
|
if (write_status > 0) {
|
||||||
return true;
|
return true;
|
||||||
@ -835,6 +873,7 @@ bool QJpegXLHandler::jumpToNextImage()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_parseState = ParseJpegXLSuccess;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -849,12 +888,14 @@ bool QJpegXLHandler::jumpToImage(int imageNumber)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (imageNumber == m_currentimage_index) {
|
if (imageNumber == m_currentimage_index) {
|
||||||
|
m_parseState = ParseJpegXLSuccess;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (imageNumber > m_currentimage_index) {
|
if (imageNumber > m_currentimage_index) {
|
||||||
JxlDecoderSkipFrames(m_decoder, imageNumber - m_currentimage_index);
|
JxlDecoderSkipFrames(m_decoder, imageNumber - m_currentimage_index);
|
||||||
m_currentimage_index = imageNumber;
|
m_currentimage_index = imageNumber;
|
||||||
|
m_parseState = ParseJpegXLSuccess;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -866,6 +907,7 @@ bool QJpegXLHandler::jumpToImage(int imageNumber)
|
|||||||
JxlDecoderSkipFrames(m_decoder, imageNumber);
|
JxlDecoderSkipFrames(m_decoder, imageNumber);
|
||||||
}
|
}
|
||||||
m_currentimage_index = imageNumber;
|
m_currentimage_index = imageNumber;
|
||||||
|
m_parseState = ParseJpegXLSuccess;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -889,7 +931,7 @@ int QJpegXLHandler::loopCount() const
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (m_basicinfo.have_animation) {
|
if (m_basicinfo.have_animation) {
|
||||||
return 1;
|
return (m_basicinfo.animation.num_loops > 0) ? m_basicinfo.animation.num_loops - 1 : -1;
|
||||||
} else {
|
} else {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -909,12 +951,16 @@ 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");
|
qWarning("ERROR: JxlDecoderSetInput failed");
|
||||||
m_parseState = ParseJpegXLError;
|
m_parseState = ParseJpegXLError;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef KIMG_JXL_API_VERSION
|
||||||
|
JxlDecoderCloseInput(m_decoder);
|
||||||
|
#endif
|
||||||
|
|
||||||
if (m_basicinfo.uses_original_profile) {
|
if (m_basicinfo.uses_original_profile) {
|
||||||
if (JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_FULL_IMAGE) != JXL_DEC_SUCCESS) {
|
if (JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_FULL_IMAGE) != JXL_DEC_SUCCESS) {
|
||||||
qWarning("ERROR: JxlDecoderSubscribeEvents failed");
|
qWarning("ERROR: JxlDecoderSubscribeEvents failed");
|
||||||
|
@ -57,6 +57,7 @@ private:
|
|||||||
ParseJpegXLNotParsed = 0,
|
ParseJpegXLNotParsed = 0,
|
||||||
ParseJpegXLSuccess = 1,
|
ParseJpegXLSuccess = 1,
|
||||||
ParseJpegXLBasicInfoParsed = 2,
|
ParseJpegXLBasicInfoParsed = 2,
|
||||||
|
ParseJpegXLFinished = 3,
|
||||||
};
|
};
|
||||||
|
|
||||||
ParseJpegXLState m_parseState;
|
ParseJpegXLState m_parseState;
|
||||||
|
@ -57,6 +57,9 @@ bool KraHandler::canRead(QIODevice *device)
|
|||||||
qWarning("KraHandler::canRead() called with no device");
|
qWarning("KraHandler::canRead() called with no device");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (device->isSequential()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
char buff[57];
|
char buff[57];
|
||||||
if (device->peek(buff, sizeof(buff)) == sizeof(buff)) {
|
if (device->peek(buff, sizeof(buff)) == sizeof(buff)) {
|
||||||
|
@ -56,6 +56,9 @@ bool OraHandler::canRead(QIODevice *device)
|
|||||||
qWarning("OraHandler::canRead() called with no device");
|
qWarning("OraHandler::canRead() called with no device");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (device->isSequential()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
char buff[54];
|
char buff[54];
|
||||||
if (device->peek(buff, sizeof(buff)) == sizeof(buff)) {
|
if (device->peek(buff, sizeof(buff)) == sizeof(buff)) {
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "pcx_p.h"
|
#include "pcx_p.h"
|
||||||
|
#include "util_p.h"
|
||||||
|
|
||||||
#include <QColor>
|
#include <QColor>
|
||||||
#include <QDataStream>
|
#include <QDataStream>
|
||||||
@ -173,7 +174,7 @@ static QDataStream &operator>>(QDataStream &s, PCXHEADER &ph)
|
|||||||
|
|
||||||
// Skip the rest of the header
|
// Skip the rest of the header
|
||||||
quint8 byte;
|
quint8 byte;
|
||||||
while (s.device()->pos() < 128) {
|
for (auto i = 0; i < 54; ++i) {
|
||||||
s >> byte;
|
s >> byte;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -229,7 +230,7 @@ PCXHEADER::PCXHEADER()
|
|||||||
s >> *this;
|
s >> *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void readLine(QDataStream &s, QByteArray &buf, const PCXHEADER &header)
|
static bool readLine(QDataStream &s, QByteArray &buf, const PCXHEADER &header)
|
||||||
{
|
{
|
||||||
quint32 i = 0;
|
quint32 i = 0;
|
||||||
quint32 size = buf.size();
|
quint32 size = buf.size();
|
||||||
@ -256,27 +257,31 @@ static void readLine(QDataStream &s, QByteArray &buf, const PCXHEADER &header)
|
|||||||
buf[i++] = byte;
|
buf[i++] = byte;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return (s.status() == QDataStream::Ok);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void readImage1(QImage &img, QDataStream &s, const PCXHEADER &header)
|
static bool readImage1(QImage &img, QDataStream &s, const PCXHEADER &header)
|
||||||
{
|
{
|
||||||
QByteArray buf(header.BytesPerLine, 0);
|
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);
|
img.setColorCount(2);
|
||||||
|
|
||||||
if (img.isNull()) {
|
if (img.isNull()) {
|
||||||
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());
|
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int y = 0; y < header.height(); ++y) {
|
for (int y = 0; y < header.height(); ++y) {
|
||||||
if (s.atEnd()) {
|
if (s.atEnd()) {
|
||||||
img = QImage();
|
return false;
|
||||||
return;
|
}
|
||||||
|
|
||||||
|
if (!readLine(s, buf, header)) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
readLine(s, buf, header);
|
|
||||||
uchar *p = img.scanLine(y);
|
uchar *p = img.scanLine(y);
|
||||||
unsigned int bpl = qMin((quint16)((header.width() + 7) / 8), header.BytesPerLine);
|
unsigned int bpl = qMin((quint16)((header.width() + 7) / 8), header.BytesPerLine);
|
||||||
for (unsigned int x = 0; x < bpl; ++x) {
|
for (unsigned int x = 0; x < bpl; ++x) {
|
||||||
@ -287,28 +292,31 @@ static void readImage1(QImage &img, QDataStream &s, const PCXHEADER &header)
|
|||||||
// Set the color palette
|
// Set the color palette
|
||||||
img.setColor(0, qRgb(0, 0, 0));
|
img.setColor(0, qRgb(0, 0, 0));
|
||||||
img.setColor(1, qRgb(255, 255, 255));
|
img.setColor(1, qRgb(255, 255, 255));
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void readImage4(QImage &img, QDataStream &s, const PCXHEADER &header)
|
static bool readImage4(QImage &img, QDataStream &s, const PCXHEADER &header)
|
||||||
{
|
{
|
||||||
QByteArray buf(header.BytesPerLine * 4, 0);
|
QByteArray buf(header.BytesPerLine * 4, 0);
|
||||||
QByteArray pixbuf(header.width(), 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);
|
img.setColorCount(16);
|
||||||
if (img.isNull()) {
|
if (img.isNull()) {
|
||||||
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());
|
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int y = 0; y < header.height(); ++y) {
|
for (int y = 0; y < header.height(); ++y) {
|
||||||
if (s.atEnd()) {
|
if (s.atEnd()) {
|
||||||
img = QImage();
|
return false;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pixbuf.fill(0);
|
pixbuf.fill(0);
|
||||||
readLine(s, buf, header);
|
if (!readLine(s, buf, header)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < 4; i++) {
|
for (int i = 0; i < 4; i++) {
|
||||||
quint32 offset = i * header.BytesPerLine;
|
quint32 offset = i * header.BytesPerLine;
|
||||||
@ -332,32 +340,34 @@ static void readImage4(QImage &img, QDataStream &s, const PCXHEADER &header)
|
|||||||
for (int i = 0; i < 16; ++i) {
|
for (int i = 0; i < 16; ++i) {
|
||||||
img.setColor(i, header.ColorMap.color(i));
|
img.setColor(i, header.ColorMap.color(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void readImage8(QImage &img, QDataStream &s, const PCXHEADER &header)
|
static bool readImage8(QImage &img, QDataStream &s, const PCXHEADER &header)
|
||||||
{
|
{
|
||||||
QByteArray buf(header.BytesPerLine, 0);
|
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);
|
img.setColorCount(256);
|
||||||
|
|
||||||
if (img.isNull()) {
|
if (img.isNull()) {
|
||||||
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());
|
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int y = 0; y < header.height(); ++y) {
|
for (int y = 0; y < header.height(); ++y) {
|
||||||
if (s.atEnd()) {
|
if (s.atEnd()) {
|
||||||
img = QImage();
|
return false;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
readLine(s, buf, header);
|
if (!readLine(s, buf, header)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
uchar *p = img.scanLine(y);
|
uchar *p = img.scanLine(y);
|
||||||
|
|
||||||
if (!p) {
|
if (!p) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int bpl = qMin(header.BytesPerLine, (quint16)header.width());
|
unsigned int bpl = qMin(header.BytesPerLine, (quint16)header.width());
|
||||||
@ -366,10 +376,21 @@ static void readImage8(QImage &img, QDataStream &s, const PCXHEADER &header)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
quint8 flag;
|
// by specification, the extended palette starts at file.size() - 769
|
||||||
s >> flag;
|
quint8 flag = 0;
|
||||||
// qDebug() << "Palette Flag: " << flag;
|
if (auto device = s.device()) {
|
||||||
|
if (device->isSequential()) {
|
||||||
|
while (flag != 12 && s.status() == QDataStream::Ok) {
|
||||||
|
s >> flag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
device->seek(device->size() - 769);
|
||||||
|
s >> flag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// qDebug() << "Palette Flag: " << flag;
|
||||||
if (flag == 12 && (header.Version == 5 || header.Version == 2)) {
|
if (flag == 12 && (header.Version == 5 || header.Version == 2)) {
|
||||||
// Read the palette
|
// Read the palette
|
||||||
quint8 r;
|
quint8 r;
|
||||||
@ -380,39 +401,48 @@ static void readImage8(QImage &img, QDataStream &s, const PCXHEADER &header)
|
|||||||
img.setColor(i, qRgb(r, g, b));
|
img.setColor(i, qRgb(r, g, b));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return (s.status() == QDataStream::Ok);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void readImage24(QImage &img, QDataStream &s, const PCXHEADER &header)
|
static bool readImage24(QImage &img, QDataStream &s, const PCXHEADER &header)
|
||||||
{
|
{
|
||||||
QByteArray r_buf(header.BytesPerLine, 0);
|
QByteArray r_buf(header.BytesPerLine, 0);
|
||||||
QByteArray g_buf(header.BytesPerLine, 0);
|
QByteArray g_buf(header.BytesPerLine, 0);
|
||||||
QByteArray b_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()) {
|
if (img.isNull()) {
|
||||||
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());
|
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int y = 0; y < header.height(); ++y) {
|
for (int y = 0; y < header.height(); ++y) {
|
||||||
if (s.atEnd()) {
|
if (s.atEnd()) {
|
||||||
img = QImage();
|
return false;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
readLine(s, r_buf, header);
|
if (!readLine(s, r_buf, header)) {
|
||||||
readLine(s, g_buf, header);
|
return false;
|
||||||
readLine(s, b_buf, header);
|
}
|
||||||
|
if (!readLine(s, g_buf, header)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!readLine(s, b_buf, header)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
uint *p = (uint *)img.scanLine(y);
|
uint *p = (uint *)img.scanLine(y);
|
||||||
for (int x = 0; x < header.width(); ++x) {
|
for (int x = 0; x < header.width(); ++x) {
|
||||||
p[x] = qRgb(r_buf[x], g_buf[x], b_buf[x]);
|
p[x] = qRgb(r_buf[x], g_buf[x], b_buf[x]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void writeLine(QDataStream &s, QByteArray &buf)
|
static bool writeLine(QDataStream &s, QByteArray &buf)
|
||||||
{
|
{
|
||||||
quint32 i = 0;
|
quint32 i = 0;
|
||||||
quint32 size = buf.size();
|
quint32 size = buf.size();
|
||||||
@ -438,15 +468,26 @@ static void writeLine(QDataStream &s, QByteArray &buf)
|
|||||||
|
|
||||||
s << data;
|
s << data;
|
||||||
}
|
}
|
||||||
|
return (s.status() == QDataStream::Ok);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void writeImage1(QImage &img, QDataStream &s, PCXHEADER &header)
|
static bool writeImage1(QImage &img, QDataStream &s, PCXHEADER &header)
|
||||||
{
|
{
|
||||||
img = img.convertToFormat(QImage::Format_Mono);
|
if (img.format() != QImage::Format_Mono) {
|
||||||
|
img = img.convertToFormat(QImage::Format_Mono);
|
||||||
|
}
|
||||||
|
if (img.isNull() || img.colorCount() < 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto rgb = img.color(0);
|
||||||
|
auto minIsBlack = (qRed(rgb) + qGreen(rgb) + qBlue(rgb)) / 3 < 127;
|
||||||
|
|
||||||
header.Bpp = 1;
|
header.Bpp = 1;
|
||||||
header.NPlanes = 1;
|
header.NPlanes = 1;
|
||||||
header.BytesPerLine = img.bytesPerLine();
|
header.BytesPerLine = img.bytesPerLine();
|
||||||
|
if (header.BytesPerLine == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
s << header;
|
s << header;
|
||||||
|
|
||||||
@ -457,18 +498,24 @@ static void writeImage1(QImage &img, QDataStream &s, PCXHEADER &header)
|
|||||||
|
|
||||||
// Invert as QImage uses reverse palette for monochrome images?
|
// Invert as QImage uses reverse palette for monochrome images?
|
||||||
for (int i = 0; i < header.BytesPerLine; ++i) {
|
for (int i = 0; i < header.BytesPerLine; ++i) {
|
||||||
buf[i] = ~p[i];
|
buf[i] = minIsBlack ? p[i] : ~p[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
writeLine(s, buf);
|
if (!writeLine(s, buf)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void writeImage4(QImage &img, QDataStream &s, PCXHEADER &header)
|
static bool writeImage4(QImage &img, QDataStream &s, PCXHEADER &header)
|
||||||
{
|
{
|
||||||
header.Bpp = 1;
|
header.Bpp = 1;
|
||||||
header.NPlanes = 4;
|
header.NPlanes = 4;
|
||||||
header.BytesPerLine = header.width() / 8;
|
header.BytesPerLine = header.width() / 8;
|
||||||
|
if (header.BytesPerLine == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < 16; ++i) {
|
for (int i = 0; i < 16; ++i) {
|
||||||
header.ColorMap.setColor(i, img.color(i));
|
header.ColorMap.setColor(i, img.color(i));
|
||||||
@ -498,16 +545,22 @@ static void writeImage4(QImage &img, QDataStream &s, PCXHEADER &header)
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < 4; ++i) {
|
for (int i = 0; i < 4; ++i) {
|
||||||
writeLine(s, buf[i]);
|
if (!writeLine(s, buf[i])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void writeImage8(QImage &img, QDataStream &s, PCXHEADER &header)
|
static bool writeImage8(QImage &img, QDataStream &s, PCXHEADER &header)
|
||||||
{
|
{
|
||||||
header.Bpp = 8;
|
header.Bpp = 8;
|
||||||
header.NPlanes = 1;
|
header.NPlanes = 1;
|
||||||
header.BytesPerLine = img.bytesPerLine();
|
header.BytesPerLine = img.bytesPerLine();
|
||||||
|
if (header.BytesPerLine == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
s << header;
|
s << header;
|
||||||
|
|
||||||
@ -520,7 +573,9 @@ static void writeImage8(QImage &img, QDataStream &s, PCXHEADER &header)
|
|||||||
buf[i] = p[i];
|
buf[i] = p[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
writeLine(s, buf);
|
if (!writeLine(s, buf)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write palette flag
|
// Write palette flag
|
||||||
@ -531,13 +586,25 @@ static void writeImage8(QImage &img, QDataStream &s, PCXHEADER &header)
|
|||||||
for (int i = 0; i < 256; ++i) {
|
for (int i = 0; i < 256; ++i) {
|
||||||
s << RGB::from(img.color(i));
|
s << RGB::from(img.color(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return (s.status() == QDataStream::Ok);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void writeImage24(QImage &img, QDataStream &s, PCXHEADER &header)
|
static bool writeImage24(QImage &img, QDataStream &s, PCXHEADER &header)
|
||||||
{
|
{
|
||||||
header.Bpp = 8;
|
header.Bpp = 8;
|
||||||
header.NPlanes = 3;
|
header.NPlanes = 3;
|
||||||
header.BytesPerLine = header.width();
|
header.BytesPerLine = header.width();
|
||||||
|
if (header.BytesPerLine == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (img.format() != QImage::Format_ARGB32 && img.format() != QImage::Format_RGB32) {
|
||||||
|
img = img.convertToFormat(QImage::Format_RGB32);
|
||||||
|
}
|
||||||
|
if (img.isNull()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
s << header;
|
s << header;
|
||||||
|
|
||||||
@ -546,7 +613,7 @@ static void writeImage24(QImage &img, QDataStream &s, PCXHEADER &header)
|
|||||||
QByteArray b_buf(header.width(), 0);
|
QByteArray b_buf(header.width(), 0);
|
||||||
|
|
||||||
for (int y = 0; y < header.height(); ++y) {
|
for (int y = 0; y < header.height(); ++y) {
|
||||||
uint *p = (uint *)img.scanLine(y);
|
auto p = (QRgb*)img.scanLine(y);
|
||||||
|
|
||||||
for (int x = 0; x < header.width(); ++x) {
|
for (int x = 0; x < header.width(); ++x) {
|
||||||
QRgb rgb = *p++;
|
QRgb rgb = *p++;
|
||||||
@ -555,10 +622,18 @@ static void writeImage24(QImage &img, QDataStream &s, PCXHEADER &header)
|
|||||||
b_buf[x] = qBlue(rgb);
|
b_buf[x] = qBlue(rgb);
|
||||||
}
|
}
|
||||||
|
|
||||||
writeLine(s, r_buf);
|
if (!writeLine(s, r_buf)) {
|
||||||
writeLine(s, g_buf);
|
return false;
|
||||||
writeLine(s, b_buf);
|
}
|
||||||
|
if (!writeLine(s, g_buf)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!writeLine(s, b_buf)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
PCXHandler::PCXHandler()
|
PCXHandler::PCXHandler()
|
||||||
@ -587,46 +662,30 @@ bool PCXHandler::read(QImage *outImage)
|
|||||||
|
|
||||||
s >> header;
|
s >> header;
|
||||||
|
|
||||||
if (header.Manufacturer != 10 || s.atEnd()) {
|
if (header.Manufacturer != 10 || header.BytesPerLine == 0 || s.atEnd()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// int w = header.width();
|
auto ok = false;
|
||||||
// int h = header.height();
|
|
||||||
|
|
||||||
// qDebug() << "Manufacturer: " << header.Manufacturer;
|
|
||||||
// qDebug() << "Version: " << header.Version;
|
|
||||||
// qDebug() << "Encoding: " << header.Encoding;
|
|
||||||
// qDebug() << "Bpp: " << header.Bpp;
|
|
||||||
// qDebug() << "Width: " << w;
|
|
||||||
// qDebug() << "Height: " << h;
|
|
||||||
// qDebug() << "Window: " << header.XMin << "," << header.XMax << ","
|
|
||||||
// << header.YMin << "," << header.YMax << endl;
|
|
||||||
// qDebug() << "BytesPerLine: " << header.BytesPerLine;
|
|
||||||
// qDebug() << "NPlanes: " << header.NPlanes;
|
|
||||||
|
|
||||||
QImage img;
|
QImage img;
|
||||||
|
|
||||||
if (header.Bpp == 1 && header.NPlanes == 1) {
|
if (header.Bpp == 1 && header.NPlanes == 1) {
|
||||||
readImage1(img, s, header);
|
ok = readImage1(img, s, header);
|
||||||
} else if (header.Bpp == 1 && header.NPlanes == 4) {
|
} else if (header.Bpp == 1 && header.NPlanes == 4) {
|
||||||
readImage4(img, s, header);
|
ok = readImage4(img, s, header);
|
||||||
} else if (header.Bpp == 8 && header.NPlanes == 1) {
|
} else if (header.Bpp == 8 && header.NPlanes == 1) {
|
||||||
readImage8(img, s, header);
|
ok = readImage8(img, s, header);
|
||||||
} else if (header.Bpp == 8 && header.NPlanes == 3) {
|
} else if (header.Bpp == 8 && header.NPlanes == 3) {
|
||||||
readImage24(img, s, header);
|
ok = readImage24(img, s, header);
|
||||||
}
|
}
|
||||||
|
|
||||||
// qDebug() << "Image Bytes: " << img.numBytes();
|
if (img.isNull() || !ok) {
|
||||||
// qDebug() << "Image Bytes Per Line: " << img.bytesPerLine();
|
|
||||||
// qDebug() << "Image Depth: " << img.depth();
|
|
||||||
|
|
||||||
if (!img.isNull()) {
|
|
||||||
*outImage = img;
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
img.setDotsPerMeterX(qRound(header.HDpi / 25.4 * 1000));
|
||||||
|
img.setDotsPerMeterY(qRound(header.YDpi / 25.4 * 1000));
|
||||||
|
*outImage = img;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PCXHandler::write(const QImage &image)
|
bool PCXHandler::write(const QImage &image)
|
||||||
@ -643,12 +702,6 @@ bool PCXHandler::write(const QImage &image)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// qDebug() << "Width: " << w;
|
|
||||||
// qDebug() << "Height: " << h;
|
|
||||||
// qDebug() << "Depth: " << img.depth();
|
|
||||||
// qDebug() << "BytesPerLine: " << img.bytesPerLine();
|
|
||||||
// qDebug() << "Color Count: " << img.colorCount();
|
|
||||||
|
|
||||||
PCXHEADER header;
|
PCXHEADER header;
|
||||||
|
|
||||||
header.Manufacturer = 10;
|
header.Manufacturer = 10;
|
||||||
@ -658,22 +711,23 @@ bool PCXHandler::write(const QImage &image)
|
|||||||
header.YMin = 0;
|
header.YMin = 0;
|
||||||
header.XMax = w - 1;
|
header.XMax = w - 1;
|
||||||
header.YMax = h - 1;
|
header.YMax = h - 1;
|
||||||
header.HDpi = 300;
|
header.HDpi = qRound(image.dotsPerMeterX() * 25.4 / 1000);
|
||||||
header.YDpi = 300;
|
header.YDpi = qRound(image.dotsPerMeterY() * 25.4 / 1000);
|
||||||
header.Reserved = 0;
|
header.Reserved = 0;
|
||||||
header.PaletteInfo = 1;
|
header.PaletteInfo = 1;
|
||||||
|
|
||||||
|
auto ok = false;
|
||||||
if (img.depth() == 1) {
|
if (img.depth() == 1) {
|
||||||
writeImage1(img, s, header);
|
ok = writeImage1(img, s, header);
|
||||||
} else if (img.depth() == 8 && img.colorCount() <= 16) {
|
} else if (img.depth() == 8 && img.colorCount() <= 16) {
|
||||||
writeImage4(img, s, header);
|
ok = writeImage4(img, s, header);
|
||||||
} else if (img.depth() == 8) {
|
} else if (img.depth() == 8) {
|
||||||
writeImage8(img, s, header);
|
ok = writeImage8(img, s, header);
|
||||||
} else if (img.depth() == 32) {
|
} else if (img.depth() >= 24) {
|
||||||
writeImage24(img, s, header);
|
ok = writeImage24(img, s, header);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PCXHandler::canRead(QIODevice *device)
|
bool PCXHandler::canRead(QIODevice *device)
|
||||||
|
@ -14,8 +14,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "pic_p.h"
|
#include "pic_p.h"
|
||||||
|
|
||||||
#include "rle_p.h"
|
#include "rle_p.h"
|
||||||
|
#include "util_p.h"
|
||||||
|
|
||||||
#include <QDataStream>
|
#include <QDataStream>
|
||||||
#include <QDebug>
|
#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()) {
|
if (img.isNull()) {
|
||||||
qDebug() << "Failed to allocate image, invalid dimensions?" << QSize(m_header.width, m_header.height) << fmt;
|
qDebug() << "Failed to allocate image, invalid dimensions?" << QSize(m_header.width, m_header.height) << fmt;
|
||||||
return false;
|
return false;
|
||||||
|
@ -3,14 +3,14 @@
|
|||||||
|
|
||||||
SPDX-FileCopyrightText: 2003 Ignacio Castaño <castano@ludicon.com>
|
SPDX-FileCopyrightText: 2003 Ignacio Castaño <castano@ludicon.com>
|
||||||
SPDX-FileCopyrightText: 2015 Alex Merry <alex.merry@kde.org>
|
SPDX-FileCopyrightText: 2015 Alex Merry <alex.merry@kde.org>
|
||||||
SPDX-FileCopyrightText: 2022 Mirco Miranda <mirco.miranda@systemceramics.com>
|
SPDX-FileCopyrightText: 2022-2023 Mirco Miranda <mircomir@outlook.com>
|
||||||
|
|
||||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This code is based on Thacher Ulrich PSD loading code released
|
* The early version of this code was based on Thacher Ulrich PSD loading code
|
||||||
* into the public domain. See: http://tulrich.com/geekstuff/
|
* released into the public domain. See: http://tulrich.com/geekstuff/
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -21,18 +21,19 @@
|
|||||||
/*
|
/*
|
||||||
* Limitations of the current code:
|
* Limitations of the current code:
|
||||||
* - 32-bit float image are converted to 16-bit integer image.
|
* - 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 directly be read due to lack of QImage support for
|
||||||
* - Other color spaces cannot be read due to lack of QImage support for
|
* color spaces other than RGB (and Grayscale). Where possible, a conversion
|
||||||
* color spaces other than RGB (and Grayscale): a conversion to
|
* to RGB is done:
|
||||||
* RGB must be done.
|
* - CMYK images are converted using an approximated way that ignores the color
|
||||||
* - The best way to convert between different color spaces is to use a
|
* information (ICC profile).
|
||||||
* color management engine (e.g. LittleCMS).
|
* - LAB images are converted to sRGB using literature formulas.
|
||||||
* - An approximate way is to ignore the color information and use
|
*
|
||||||
* literature formulas (possible but not recommended).
|
* NOTE: The best way to convert between different color spaces is to use a
|
||||||
|
* color management engine (e.g. LittleCMS).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "fastmath_p.h"
|
||||||
#include "psd_p.h"
|
#include "psd_p.h"
|
||||||
|
|
||||||
#include "util_p.h"
|
#include "util_p.h"
|
||||||
|
|
||||||
#include <QDataStream>
|
#include <QDataStream>
|
||||||
@ -40,13 +41,35 @@
|
|||||||
#include <QImage>
|
#include <QImage>
|
||||||
#include <QColorSpace>
|
#include <QColorSpace>
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
typedef quint32 uint;
|
typedef quint32 uint;
|
||||||
typedef quint16 ushort;
|
typedef quint16 ushort;
|
||||||
typedef quint8 uchar;
|
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 slightly improved. 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.
|
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_BITMAP = 0,
|
||||||
CM_GRAYSCALE = 1,
|
CM_GRAYSCALE = 1,
|
||||||
CM_INDEXED = 2,
|
CM_INDEXED = 2,
|
||||||
@ -65,6 +88,12 @@ enum ImageResourceId : quint16 {
|
|||||||
IRI_XMPMETADATA = 0x0424
|
IRI_XMPMETADATA = 0x0424
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum LayerId : quint32 {
|
||||||
|
LI_MT16 = 0x4D743136, // 'Mt16',
|
||||||
|
LI_MT32 = 0x4D743332, // 'Mt32',
|
||||||
|
LI_MTRN = 0x4D74726E // 'Mtrn'
|
||||||
|
};
|
||||||
|
|
||||||
struct PSDHeader {
|
struct PSDHeader {
|
||||||
uint signature;
|
uint signature;
|
||||||
ushort version;
|
ushort version;
|
||||||
@ -101,6 +130,58 @@ struct PSDColorModeDataSection {
|
|||||||
|
|
||||||
using PSDImageResourceSection = QHash<quint16, PSDImageResourceBlock>;
|
using PSDImageResourceSection = QHash<quint16, PSDImageResourceBlock>;
|
||||||
|
|
||||||
|
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
|
* \brief fixedPointToDouble
|
||||||
* Converts a fixed point number to floating point one.
|
* Converts a fixed point number to floating point one.
|
||||||
@ -112,6 +193,43 @@ static double fixedPointToDouble(qint32 fixedPoint)
|
|||||||
return (i+d);
|
return (i+d);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static qint64 readSize(QDataStream &s, bool psb = false)
|
||||||
|
{
|
||||||
|
qint64 size = 0;
|
||||||
|
if (!psb) {
|
||||||
|
quint32 tmp;
|
||||||
|
s >> tmp;
|
||||||
|
size = tmp;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
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; size; size -= i32) {
|
||||||
|
i32 = std::min(size, qint64(std::numeric_limits<qint32>::max()));
|
||||||
|
i32 = s.skipRawData(i32);
|
||||||
|
if (i32 < 1)
|
||||||
|
return 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
|
* \brief readPascalString
|
||||||
* Reads the Pascal string as defined in the PSD specification.
|
* Reads the Pascal string as defined in the PSD specification.
|
||||||
@ -169,12 +287,6 @@ static PSDImageResourceSection readImageResourceSection(QDataStream &s, bool *ok
|
|||||||
qint32 sectioSize;
|
qint32 sectioSize;
|
||||||
s >> sectioSize;
|
s >> sectioSize;
|
||||||
|
|
||||||
#ifdef QT_DEBUG
|
|
||||||
auto pos = qint64();
|
|
||||||
if (auto dev = s.device())
|
|
||||||
pos = dev->pos();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Reading Image resource block
|
// Reading Image resource block
|
||||||
for (auto size = sectioSize; size > 0;) {
|
for (auto size = sectioSize; size > 0;) {
|
||||||
// Length Description
|
// Length Description
|
||||||
@ -193,7 +305,7 @@ static PSDImageResourceSection readImageResourceSection(QDataStream &s, bool *ok
|
|||||||
s >> signature;
|
s >> signature;
|
||||||
size -= sizeof(signature);
|
size -= sizeof(signature);
|
||||||
// NOTE: MeSa signature is not documented but found in some old PSD take from Photoshop 7.0 CD.
|
// 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!";
|
qDebug() << "Invalid Image Resource Block Signature!";
|
||||||
*ok = false;
|
*ok = false;
|
||||||
break;
|
break;
|
||||||
@ -218,12 +330,12 @@ static PSDImageResourceSection readImageResourceSection(QDataStream &s, bool *ok
|
|||||||
size -= sizeof(dataSize);
|
size -= sizeof(dataSize);
|
||||||
// NOTE: Qt device::read() and QDataStream::readRawData() could read less data than specified.
|
// NOTE: Qt device::read() and QDataStream::readRawData() could read less data than specified.
|
||||||
// The read code should be improved.
|
// The read code should be improved.
|
||||||
if(auto dev = s.device())
|
if (auto dev = s.device())
|
||||||
irb.data = dev->read(dataSize);
|
irb.data = dev->read(dataSize);
|
||||||
auto read = irb.data.size();
|
auto read = irb.data.size();
|
||||||
if (read > 0)
|
if (read > 0)
|
||||||
size -= read;
|
size -= read;
|
||||||
if (read != dataSize) {
|
if (quint32(read) != dataSize) {
|
||||||
qDebug() << "Image Resource Block Read Error!";
|
qDebug() << "Image Resource Block Read Error!";
|
||||||
*ok = false;
|
*ok = false;
|
||||||
break;
|
break;
|
||||||
@ -239,15 +351,80 @@ static PSDImageResourceSection readImageResourceSection(QDataStream &s, bool *ok
|
|||||||
irs.insert(id, irb);
|
irs.insert(id, irb);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef QT_DEBUG
|
return irs;
|
||||||
if (auto dev = s.device()) {
|
}
|
||||||
if ((dev->pos() - pos) != sectioSize) {
|
|
||||||
*ok = false;
|
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)
|
||||||
|
{
|
||||||
|
PSDLayerAndMaskSection lms;
|
||||||
|
|
||||||
|
bool tmp = true;
|
||||||
|
if (ok == nullptr)
|
||||||
|
ok = &tmp;
|
||||||
|
*ok = true;
|
||||||
|
|
||||||
|
auto device = s.device();
|
||||||
|
device->startTransaction();
|
||||||
|
|
||||||
|
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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
return irs;
|
// 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@ -424,18 +601,54 @@ static QDataStream &operator>>(QDataStream &s, PSDHeader &header)
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that the header is a valid PSD.
|
// Check that the header is a valid PSD (as written in the PSD specification).
|
||||||
static bool IsValid(const PSDHeader &header)
|
static bool IsValid(const PSDHeader &header)
|
||||||
{
|
{
|
||||||
if (header.signature != 0x38425053) { // '8BPS'
|
if (header.signature != 0x38425053) { // '8BPS'
|
||||||
|
//qDebug() << "PSD header: invalid signature" << header.signature;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (header.version != 1 && header.version != 2) {
|
||||||
|
qDebug() << "PSD header: invalid version" << header.version;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (header.depth != 8 &&
|
||||||
|
header.depth != 16 &&
|
||||||
|
header.depth != 32 &&
|
||||||
|
header.depth != 1) {
|
||||||
|
qDebug() << "PSD header: invalid depth" << header.depth;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (header.color_mode != CM_RGB &&
|
||||||
|
header.color_mode != CM_GRAYSCALE &&
|
||||||
|
header.color_mode != CM_INDEXED &&
|
||||||
|
header.color_mode != CM_DUOTONE &&
|
||||||
|
header.color_mode != CM_CMYK &&
|
||||||
|
header.color_mode != CM_LABCOLOR &&
|
||||||
|
header.color_mode != CM_MULTICHANNEL &&
|
||||||
|
header.color_mode != CM_BITMAP) {
|
||||||
|
qDebug() << "PSD header: invalid color mode" << header.color_mode;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
if (header.width > 300000 || header.height > 300000) {
|
||||||
|
qDebug() << "PSD header: invalid image size" << header.width << "x" << header.height;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that the header is supported.
|
// Check that the header is supported by this plugin.
|
||||||
static bool IsSupported(const PSDHeader &header)
|
static bool IsSupported(const PSDHeader &header)
|
||||||
{
|
{
|
||||||
|
if (!IsValid(header)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (header.version != 1 && header.version != 2) {
|
if (header.version != 1 && header.version != 2) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -449,30 +662,15 @@ static bool IsSupported(const PSDHeader &header)
|
|||||||
header.color_mode != CM_GRAYSCALE &&
|
header.color_mode != CM_GRAYSCALE &&
|
||||||
header.color_mode != CM_INDEXED &&
|
header.color_mode != CM_INDEXED &&
|
||||||
header.color_mode != CM_DUOTONE &&
|
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) {
|
header.color_mode != CM_BITMAP) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
if (header.color_mode == CM_MULTICHANNEL &&
|
||||||
}
|
header.channel_count < 4) {
|
||||||
|
return false;
|
||||||
static bool skip_section(QDataStream &s, bool psb = false)
|
|
||||||
{
|
|
||||||
qint64 section_length;
|
|
||||||
if (!psb) {
|
|
||||||
quint32 tmp;
|
|
||||||
s >> tmp;
|
|
||||||
section_length = tmp;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
s >> section_length;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip mode data.
|
|
||||||
for (qint32 i32 = 0; section_length; section_length -= i32) {
|
|
||||||
i32 = std::min(section_length, qint64(std::numeric_limits<qint32>::max()));
|
|
||||||
i32 = s.skipRawData(i32);
|
|
||||||
if (i32 < 1)
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -497,7 +695,7 @@ qint64 decompress(const char *input, qint64 ilen, char *output, qint64 olen)
|
|||||||
if (n >= 0) {
|
if (n >= 0) {
|
||||||
rr = qint64(n) + 1;
|
rr = qint64(n) + 1;
|
||||||
if (available < rr) {
|
if (available < rr) {
|
||||||
ip--;
|
--ip;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -509,7 +707,7 @@ qint64 decompress(const char *input, qint64 ilen, char *output, qint64 olen)
|
|||||||
else if (ip < ilen) {
|
else if (ip < ilen) {
|
||||||
rr = qint64(1-n);
|
rr = qint64(1-n);
|
||||||
if (available < rr) {
|
if (available < rr) {
|
||||||
ip--;
|
--ip;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
memset(output + j, input[ip++], size_t(rr));
|
memset(output + j, input[ip++], size_t(rr));
|
||||||
@ -525,7 +723,7 @@ qint64 decompress(const char *input, qint64 ilen, char *output, qint64 olen)
|
|||||||
* \param header The PSD header.
|
* \param header The PSD header.
|
||||||
* \return The Qt image format.
|
* \return The Qt image format.
|
||||||
*/
|
*/
|
||||||
static QImage::Format imageFormat(const PSDHeader &header)
|
static QImage::Format imageFormat(const PSDHeader &header, bool alpha)
|
||||||
{
|
{
|
||||||
if (header.channel_count == 0) {
|
if (header.channel_count == 0) {
|
||||||
return QImage::Format_Invalid;
|
return QImage::Format_Invalid;
|
||||||
@ -535,9 +733,22 @@ static QImage::Format imageFormat(const PSDHeader &header)
|
|||||||
switch(header.color_mode) {
|
switch(header.color_mode) {
|
||||||
case CM_RGB:
|
case CM_RGB:
|
||||||
if (header.depth == 16 || header.depth == 32)
|
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_Premultiplied;
|
||||||
else
|
else
|
||||||
format = header.channel_count < 4 ? QImage::Format_RGB888 : QImage::Format_RGBA8888;
|
format = header.channel_count < 4 || !alpha ? QImage::Format_RGB888 : QImage::Format_RGBA8888_Premultiplied;
|
||||||
|
break;
|
||||||
|
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 ? QImage::Format_RGBX64 : QImage::Format_RGBA64;
|
||||||
|
else if (header.depth == 8)
|
||||||
|
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;
|
break;
|
||||||
case CM_GRAYSCALE:
|
case CM_GRAYSCALE:
|
||||||
case CM_DUOTONE:
|
case CM_DUOTONE:
|
||||||
@ -598,23 +809,61 @@ inline quint32 xchg(quint32 v) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
template<class T>
|
template<class T>
|
||||||
inline void planarToChunchy(uchar *target, const char* source, qint32 width, qint32 c, qint32 cn)
|
inline void planarToChunchy(uchar *target, const char *source, qint32 width, qint32 c, qint32 cn)
|
||||||
{
|
{
|
||||||
auto s = reinterpret_cast<const T*>(source);
|
auto s = reinterpret_cast<const T*>(source);
|
||||||
auto t = reinterpret_cast<T*>(target);
|
auto t = reinterpret_cast<T*>(target);
|
||||||
for (qint32 x = 0; x < width; ++x)
|
for (qint32 x = 0; x < width; ++x) {
|
||||||
t[x*cn+c] = xchg(s[x]);
|
t[x * cn + c] = xchg(s[x]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class T>
|
template<class T, T min = 0, T max = 1>
|
||||||
inline void planarToChunchyFloat(uchar *target, const char* source, qint32 width, qint32 c, qint32 cn)
|
inline void planarToChunchyFloat(uchar *target, const char *source, qint32 width, qint32 c, qint32 cn)
|
||||||
{
|
{
|
||||||
auto s = reinterpret_cast<const T*>(source);
|
auto s = reinterpret_cast<const T*>(source);
|
||||||
auto t = reinterpret_cast<quint16*>(target);
|
auto t = reinterpret_cast<quint16*>(target);
|
||||||
for (qint32 x = 0; x < width; ++x) {
|
for (qint32 x = 0; x < width; ++x) {
|
||||||
auto tmp = xchg(s[x]);
|
auto tmp = xchg(s[x]);
|
||||||
t[x*cn+c] = std::min(quint16(*reinterpret_cast<float*>(&tmp) * std::numeric_limits<quint16>::max() + 0.5),
|
auto ftmp = (*reinterpret_cast<float*>(&tmp) - double(min)) / (double(max) - double(min));
|
||||||
std::numeric_limits<quint16>::max());
|
t[x * cn + c] = quint16(std::min(ftmp * std::numeric_limits<quint16>::max() + 0.5, double(std::numeric_limits<quint16>::max())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class PremulConversion {
|
||||||
|
PS2P, // Photoshop premul to qimage premul (required by RGB)
|
||||||
|
PS2A, // Photoshop premul to unassociated alpha (required by RGB, CMYK and L* components of LAB)
|
||||||
|
PSLab2A // Photoshop premul to unassociated alpha (required by a* and b* components of LAB)
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
inline void premulConversion(char *stride, qint32 width, qint32 ac, qint32 cn, const PremulConversion &conv)
|
||||||
|
{
|
||||||
|
auto s = reinterpret_cast<T *>(stride);
|
||||||
|
auto max = qint64(std::numeric_limits<T>::max());
|
||||||
|
|
||||||
|
for (qint32 c = 0; c < ac; ++c) {
|
||||||
|
if (conv == PremulConversion::PS2P) {
|
||||||
|
for (qint32 x = 0; x < width; ++x) {
|
||||||
|
auto xcn = x * cn;
|
||||||
|
auto alpha = *(s + xcn + ac);
|
||||||
|
*(s + xcn + c) = *(s + xcn + c) + alpha - max;
|
||||||
|
}
|
||||||
|
} else if (conv == PremulConversion::PS2A || (conv == PremulConversion::PSLab2A && c == 0)) {
|
||||||
|
for (qint32 x = 0; x < width; ++x) {
|
||||||
|
auto xcn = x * cn;
|
||||||
|
auto alpha = *(s + xcn + ac);
|
||||||
|
if (alpha > 0)
|
||||||
|
*(s + xcn + c) = ((*(s + xcn + c) + alpha - max) * max + alpha / 2) / alpha;
|
||||||
|
}
|
||||||
|
} else if (conv == PremulConversion::PSLab2A) {
|
||||||
|
for (qint32 x = 0; x < width; ++x) {
|
||||||
|
auto xcn = x * cn;
|
||||||
|
auto alpha = *(s + xcn + ac);
|
||||||
|
if (alpha > 0)
|
||||||
|
*(s + xcn + c) = ((*(s + xcn + c) + (alpha - max + 1) / 2) * max + alpha / 2) / alpha;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -622,8 +871,139 @@ inline void monoInvert(uchar *target, const char* source, qint32 bytes)
|
|||||||
{
|
{
|
||||||
auto s = reinterpret_cast<const quint8*>(source);
|
auto s = reinterpret_cast<const quint8*>(source);
|
||||||
auto t = reinterpret_cast<quint8*>(target);
|
auto t = reinterpret_cast<quint8*>(target);
|
||||||
for (qint32 x = 0; x < bytes; ++x)
|
for (qint32 x = 0; x < bytes; ++x) {
|
||||||
t[x] = ~s[x];
|
t[x] = ~s[x];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
inline void rawChannelsCopy(uchar *target, qint32 targetChannels, const char *source, qint32 sourceChannels, qint32 width)
|
||||||
|
{
|
||||||
|
auto s = reinterpret_cast<const T *>(source);
|
||||||
|
auto t = reinterpret_cast<T *>(target);
|
||||||
|
for (qint32 c = 0, cs = std::min(targetChannels, sourceChannels); c < cs; ++c) {
|
||||||
|
for (qint32 x = 0; x < width; ++x) {
|
||||||
|
t[x * targetChannels + c] = s[x * sourceChannels + c];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
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);
|
||||||
|
auto max = double(std::numeric_limits<T>::max());
|
||||||
|
auto invmax = 1.0 / max; // speed improvements by ~10%
|
||||||
|
|
||||||
|
if (sourceChannels < 4) {
|
||||||
|
qDebug() << "cmykToRgb: image is not a valid CMYK!";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (qint32 w = 0; w < width; ++w) {
|
||||||
|
auto ps = s + sourceChannels * w;
|
||||||
|
auto C = 1 - *(ps + 0) * invmax;
|
||||||
|
auto M = 1 - *(ps + 1) * invmax;
|
||||||
|
auto Y = 1 - *(ps + 2) * invmax;
|
||||||
|
auto K = 1 - *(ps + 3) * invmax;
|
||||||
|
|
||||||
|
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 && alpha)
|
||||||
|
*(pt + 3) = *(ps + 4);
|
||||||
|
else
|
||||||
|
*(pt + 3) = std::numeric_limits<T>::max();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
// Replacing fastPow with std::pow the conversion time is 2/3 times longer: using fastPow
|
||||||
|
// there are minimal differences in the conversion that are not visually noticeable.
|
||||||
|
return (linear > 0.0031308 ? 1.055 * fastPow(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());
|
||||||
|
auto invmax = 1.0 / 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) * invmax) * 100.0;
|
||||||
|
auto A = (*(ps + 1) * invmax) * 255.0 - 128.0;
|
||||||
|
auto B = (*(ps + 2) * invmax) * 255.0 - 128.0;
|
||||||
|
|
||||||
|
// converting LAB to XYZ (D65 illuminant)
|
||||||
|
auto Y = (L + 16.0) * (1.0 / 116.0);
|
||||||
|
auto X = A * (1.0 / 500.0) + Y;
|
||||||
|
auto Z = Y - B * (1.0 / 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()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (decompress(tmp.data(), tmp.size(), target.data(), target.size()) < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (stream.readRawData(target.data(), target.size()) != target.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return stream.status() == QDataStream::Ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the PSD image.
|
// Load the PSD image.
|
||||||
@ -653,7 +1033,8 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Layer and Mask section
|
// Layer and Mask section
|
||||||
if (!skip_section(stream, isPsb)) {
|
auto lms = readLayerAndMaskSection(stream, isPsb, &ok);
|
||||||
|
if (!ok) {
|
||||||
qDebug() << "Error while skipping Layer and Mask section";
|
qDebug() << "Error while skipping Layer and Mask section";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -669,13 +1050,19 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QImage::Format format = imageFormat(header);
|
// 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 = header.color_mode == CM_RGB;
|
||||||
|
if (!lms.isNull())
|
||||||
|
alpha = lms.hasAlpha();
|
||||||
|
|
||||||
|
const QImage::Format format = imageFormat(header, alpha);
|
||||||
if (format == QImage::Format_Invalid) {
|
if (format == QImage::Format_Invalid) {
|
||||||
qWarning() << "Unsupported image format. color_mode:" << header.color_mode << "depth:" << header.depth << "channel_count:" << header.channel_count;
|
qWarning() << "Unsupported image format. color_mode:" << header.color_mode << "depth:" << header.depth << "channel_count:" << header.channel_count;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
img = QImage(header.width, header.height, format);
|
img = imageAlloc(header.width, header.height, format);
|
||||||
if (img.isNull()) {
|
if (img.isNull()) {
|
||||||
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width, header.height);
|
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width, header.height);
|
||||||
return false;
|
return false;
|
||||||
@ -697,7 +1084,7 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
|
|||||||
|
|
||||||
QVector<quint32> strides(header.height * header.channel_count, raw_count);
|
QVector<quint32> strides(header.height * header.channel_count, raw_count);
|
||||||
// Read the compressed stride sizes
|
// Read the compressed stride sizes
|
||||||
if (compression)
|
if (compression) {
|
||||||
for (auto&& v : strides) {
|
for (auto&& v : strides) {
|
||||||
if (isPsb) {
|
if (isPsb) {
|
||||||
stream >> v;
|
stream >> v;
|
||||||
@ -707,47 +1094,129 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
|
|||||||
stream >> tmp;
|
stream >> tmp;
|
||||||
v = tmp;
|
v = tmp;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
// calculate the absolute file positions of each stride (required when a colorspace conversion should be done)
|
||||||
|
auto device = stream.device();
|
||||||
|
QVector<quint64> stridePositions(strides.size());
|
||||||
|
if (!stridePositions.isEmpty()) {
|
||||||
|
stridePositions[0] = device->pos();
|
||||||
|
}
|
||||||
|
for (qsizetype i = 1, n = stridePositions.size(); i < n; ++i) {
|
||||||
|
stridePositions[i] = stridePositions[i-1] + strides.at(i-1);
|
||||||
|
}
|
||||||
|
|
||||||
// Read the image
|
// Read the image
|
||||||
QByteArray rawStride;
|
QByteArray rawStride;
|
||||||
rawStride.resize(raw_count);
|
rawStride.resize(raw_count);
|
||||||
for (qint32 c = 0; c < channel_num; ++c) {
|
|
||||||
for(qint32 y = 0, h = header.height; y < h; ++y) {
|
// clang-format off
|
||||||
auto&& strideSize = strides.at(c*qsizetype(h)+y);
|
// checks the need of color conversion (that requires random access to the image)
|
||||||
if (compression) {
|
auto randomAccess = (header.color_mode == CM_CMYK) ||
|
||||||
QByteArray tmp;
|
(header.color_mode == CM_LABCOLOR) ||
|
||||||
tmp.resize(strideSize);
|
(header.color_mode == CM_MULTICHANNEL) ||
|
||||||
if (stream.readRawData(tmp.data(), tmp.size()) != tmp.size()) {
|
(header.color_mode != CM_INDEXED && img.hasAlphaChannel());
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
if (randomAccess) {
|
||||||
|
// In order to make a colorspace transformation, we need all channels of a scanline
|
||||||
|
QByteArray psdScanline;
|
||||||
|
psdScanline.resize(qsizetype(header.width * std::min(header.depth, quint16(16)) * header.channel_count + 7) / 8);
|
||||||
|
for (qint32 y = 0, h = header.height; y < h; ++y) {
|
||||||
|
for (qint32 c = 0; c < header.channel_count; ++c) {
|
||||||
|
auto strideNumber = c * qsizetype(h) + y;
|
||||||
|
if (!device->seek(stridePositions.at(strideNumber))) {
|
||||||
|
qDebug() << "Error while seeking the stream of channel" << c << "line" << y;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto&& strideSize = strides.at(strideNumber);
|
||||||
|
if (!readChannel(rawStride, stream, strideSize, compression)) {
|
||||||
qDebug() << "Error while reading the stream of channel" << c << "line" << y;
|
qDebug() << "Error while reading the stream of channel" << c << "line" << y;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (decompress(tmp.data(), tmp.size(), rawStride.data(), rawStride.size()) < 0) {
|
|
||||||
qDebug() << "Error while decompressing the channel" << c << "line" << y;
|
auto scanLine = reinterpret_cast<unsigned char*>(psdScanline.data());
|
||||||
return false;
|
if (header.depth == 8) {
|
||||||
}
|
planarToChunchy<quint8>(scanLine, rawStride.data(), header.width, c, header.channel_count);
|
||||||
}
|
} else if (header.depth == 16) {
|
||||||
else {
|
planarToChunchy<quint16>(scanLine, rawStride.data(), header.width, c, header.channel_count);
|
||||||
if (stream.readRawData(rawStride.data(), rawStride.size()) != rawStride.size()) {
|
} else if (header.depth == 32) {
|
||||||
qDebug() << "Error while reading the stream of channel" << c << "line" << y;
|
planarToChunchyFloat<quint32>(scanLine, rawStride.data(), header.width, c, header.channel_count);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stream.status() != QDataStream::Ok) {
|
// Convert premultiplied data to unassociated data
|
||||||
qDebug() << "Stream read error" << stream.status();
|
if (img.hasAlphaChannel()) {
|
||||||
return false;
|
if (header.color_mode == CM_CMYK) {
|
||||||
|
if (header.depth == 8)
|
||||||
|
premulConversion<quint8>(psdScanline.data(), header.width, 4, header.channel_count, PremulConversion::PS2A);
|
||||||
|
else if (header.depth == 16)
|
||||||
|
premulConversion<quint16>(psdScanline.data(), header.width, 4, header.channel_count, PremulConversion::PS2A);
|
||||||
|
}
|
||||||
|
if (header.color_mode == CM_LABCOLOR) {
|
||||||
|
if (header.depth == 8)
|
||||||
|
premulConversion<quint8>(psdScanline.data(), header.width, 3, header.channel_count, PremulConversion::PSLab2A);
|
||||||
|
else if (header.depth == 16)
|
||||||
|
premulConversion<quint16>(psdScanline.data(), header.width, 3, header.channel_count, PremulConversion::PSLab2A);
|
||||||
|
}
|
||||||
|
if (header.color_mode == CM_RGB) {
|
||||||
|
if (header.depth == 8)
|
||||||
|
premulConversion<quint8>(psdScanline.data(), header.width, 3, header.channel_count, PremulConversion::PS2P);
|
||||||
|
else if (header.depth == 16 || header.depth == 32)
|
||||||
|
premulConversion<quint16>(psdScanline.data(), header.width, 3, header.channel_count, PremulConversion::PS2P);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto scanLine = img.scanLine(y);
|
// Conversion to RGB
|
||||||
if (header.depth == 1) // Bitmap
|
if (header.color_mode == CM_CMYK || header.color_mode == CM_MULTICHANNEL) {
|
||||||
monoInvert(scanLine, rawStride.data(), std::min(rawStride.size(), img.bytesPerLine()));
|
if (header.depth == 8)
|
||||||
else if (header.depth == 8) // 8-bits images: Indexed, Grayscale, RGB/RGBA
|
cmykToRgb<quint8>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha);
|
||||||
planarToChunchy<quint8>(scanLine, rawStride.data(), header.width, c, imgChannels);
|
else if (header.depth == 16)
|
||||||
else if (header.depth == 16) // 16-bits integer images: Grayscale, RGB/RGBA
|
cmykToRgb<quint16>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha);
|
||||||
planarToChunchy<quint16>(scanLine, rawStride.data(), header.width, c, imgChannels);
|
}
|
||||||
else if (header.depth == 32) // 32-bits float images: Grayscale, RGB/RGBA (coverted to equivalent integer 16-bits)
|
if (header.color_mode == CM_LABCOLOR) {
|
||||||
planarToChunchyFloat<quint32>(scanLine, rawStride.data(), header.width, c, imgChannels);
|
if (header.depth == 8)
|
||||||
|
labToRgb<quint8>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha);
|
||||||
|
else if (header.depth == 16)
|
||||||
|
labToRgb<quint16>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha);
|
||||||
|
}
|
||||||
|
if (header.color_mode == CM_RGB) {
|
||||||
|
if (header.depth == 8)
|
||||||
|
rawChannelsCopy<quint8>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width);
|
||||||
|
else if (header.depth == 16 || header.depth == 32)
|
||||||
|
rawChannelsCopy<quint16>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Linear read (no position jumps): optimized code usable only for the colorspaces supported by QImage
|
||||||
|
for (qint32 c = 0; c < channel_num; ++c) {
|
||||||
|
for (qint32 y = 0, h = header.height; y < h; ++y) {
|
||||||
|
auto&& strideSize = strides.at(c * qsizetype(h) + y);
|
||||||
|
if (!readChannel(rawStride, stream, strideSize, compression)) {
|
||||||
|
qDebug() << "Error while reading the stream of channel" << c << "line" << y;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto scanLine = img.scanLine(y);
|
||||||
|
if (header.depth == 1) { // Bitmap
|
||||||
|
monoInvert(scanLine, rawStride.data(), std::min(rawStride.size(), img.bytesPerLine()));
|
||||||
|
} else if (header.depth == 8) { // 8-bits images: Indexed, Grayscale, RGB/RGBA
|
||||||
|
planarToChunchy<quint8>(scanLine, rawStride.data(), header.width, c, imgChannels);
|
||||||
|
} else if (header.depth == 16) { // 16-bits integer images: Grayscale, RGB/RGBA
|
||||||
|
planarToChunchy<quint16>(scanLine, rawStride.data(), header.width, c, imgChannels);
|
||||||
|
} else if (header.depth == 32) { // 32-bits float images: Grayscale, RGB/RGBA (coverted to equivalent integer 16-bits)
|
||||||
|
planarToChunchyFloat<quint32>(scanLine, rawStride.data(), header.width, c, imgChannels);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
// Resolution info
|
||||||
@ -859,35 +1328,30 @@ bool PSDHandler::canRead(QIODevice *device)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
qint64 oldPos = device->pos();
|
device->startTransaction();
|
||||||
|
|
||||||
char head[4];
|
QDataStream s(device);
|
||||||
qint64 readBytes = device->read(head, sizeof(head));
|
s.setByteOrder(QDataStream::BigEndian);
|
||||||
if (readBytes < 0) {
|
|
||||||
qWarning() << "Read failed" << device->errorString();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (readBytes != sizeof(head)) {
|
PSDHeader header;
|
||||||
if (device->isSequential()) {
|
s >> header;
|
||||||
while (readBytes > 0) {
|
|
||||||
device->ungetChar(head[readBytes-- - 1]);
|
device->rollbackTransaction();
|
||||||
}
|
|
||||||
} else {
|
if (s.status() != QDataStream::Ok) {
|
||||||
device->seek(oldPos);
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (device->isSequential()) {
|
if (device->isSequential()) {
|
||||||
while (readBytes > 0) {
|
if (header.color_mode == CM_CMYK || header.color_mode == CM_LABCOLOR || header.color_mode == CM_MULTICHANNEL) {
|
||||||
device->ungetChar(head[readBytes-- - 1]);
|
return false;
|
||||||
|
}
|
||||||
|
if (header.color_mode == CM_RGB && header.channel_count > 3) {
|
||||||
|
return false; // supposing extra channel as alpha
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
device->seek(oldPos);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return qstrncmp(head, "8BPS", 4) == 0;
|
return IsSupported(header);
|
||||||
}
|
}
|
||||||
|
|
||||||
QImageIOPlugin::Capabilities PSDPlugin::capabilities(QIODevice *device, const QByteArray &format) const
|
QImageIOPlugin::Capabilities PSDPlugin::capabilities(QIODevice *device, const QByteArray &format) const
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
[Desktop Entry]
|
[Desktop Entry]
|
||||||
Type=Service
|
Type=Service
|
||||||
X-KDE-ServiceTypes=QImageIOPlugins
|
X-KDE-ServiceTypes=QImageIOPlugins
|
||||||
X-KDE-ImageFormat=psd
|
X-KDE-ImageFormat=psd,psb,pdd,psdt
|
||||||
X-KDE-MimeType=image/vnd.adobe.photoshop
|
X-KDE-MimeType=image/vnd.adobe.photoshop
|
||||||
X-KDE-Read=true
|
X-KDE-Read=true
|
||||||
X-KDE-Write=false
|
X-KDE-Write=false
|
||||||
|
@ -8,7 +8,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "ras_p.h"
|
#include "ras_p.h"
|
||||||
|
|
||||||
#include "util_p.h"
|
#include "util_p.h"
|
||||||
|
|
||||||
#include <QDataStream>
|
#include <QDataStream>
|
||||||
@ -152,8 +151,7 @@ static bool LoadRAS(QDataStream &s, const RasHeader &ras, QImage &img)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Allocate image
|
// Allocate image
|
||||||
img = QImage(ras.Width, ras.Height, QImage::Format_ARGB32);
|
img = imageAlloc(ras.Width, ras.Height, QImage::Format_ARGB32);
|
||||||
|
|
||||||
if (img.isNull()) {
|
if (img.isNull()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
912
src/imageformats/raw.cpp
Normal file
@ -0,0 +1,912 @@
|
|||||||
|
/*
|
||||||
|
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",
|
||||||
|
"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 || 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 &¶ms = 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 &¶ms = 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;
|
||||||
|
}
|