Compare commits
53 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 |
@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.16)
|
||||
project(KImageFormats)
|
||||
|
||||
include(FeatureSummary)
|
||||
find_package(ECM 5.99.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")
|
||||
feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES)
|
||||
|
||||
|
@ -136,7 +136,9 @@ kimageformats_write_tests(
|
||||
# kimageformats_write_tests(eps)
|
||||
#endif()
|
||||
if (OpenEXR_FOUND)
|
||||
# FIXME: OpenEXR tests
|
||||
kimageformats_read_tests(
|
||||
exr
|
||||
)
|
||||
endif()
|
||||
|
||||
if (LibRaw_FOUND)
|
||||
|
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 |
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 4.9 KiB |
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
Before Width: | Height: | Size: 189 KiB After Width: | Height: | Size: 189 KiB |
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 114 KiB |
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
@ -9,6 +9,7 @@
|
||||
#include <QCommandLineParser>
|
||||
#include <QCoreApplication>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QImage>
|
||||
#include <QImageReader>
|
||||
@ -18,6 +19,44 @@
|
||||
|
||||
#include "fuzzyeq.cpp"
|
||||
|
||||
/**
|
||||
* @brief The SequentialFile class
|
||||
* Class to make a file a sequential access device. This class is used to check if the plugins could works
|
||||
* on a sequential device such as a socket.
|
||||
*/
|
||||
class SequentialFile : public QFile
|
||||
{
|
||||
public:
|
||||
SequentialFile()
|
||||
: QFile()
|
||||
{
|
||||
}
|
||||
explicit SequentialFile(const QString &name)
|
||||
: QFile(name)
|
||||
{
|
||||
}
|
||||
#ifndef QT_NO_QOBJECT
|
||||
explicit SequentialFile(QObject *parent)
|
||||
: QFile(parent)
|
||||
{
|
||||
}
|
||||
SequentialFile(const QString &name, QObject *parent)
|
||||
: QFile(name, parent)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
bool isSequential() const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
qint64 size() const override
|
||||
{
|
||||
return bytesAvailable();
|
||||
}
|
||||
};
|
||||
|
||||
static void writeImageData(const char *name, const QString &filename, const QImage &image)
|
||||
{
|
||||
QFile file(filename);
|
||||
@ -56,7 +95,7 @@ int main(int argc, char **argv)
|
||||
QCoreApplication::removeLibraryPath(QStringLiteral(PLUGIN_DIR));
|
||||
QCoreApplication::addLibraryPath(QStringLiteral(PLUGIN_DIR));
|
||||
QCoreApplication::setApplicationName(QStringLiteral("readtest"));
|
||||
QCoreApplication::setApplicationVersion(QStringLiteral("1.0.0"));
|
||||
QCoreApplication::setApplicationVersion(QStringLiteral("1.1.0"));
|
||||
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription(QStringLiteral("Performs basic image conversion checking."));
|
||||
@ -98,6 +137,7 @@ int main(int argc, char **argv)
|
||||
|
||||
int passed = 0;
|
||||
int failed = 0;
|
||||
int skipped = 0;
|
||||
|
||||
QTextStream(stdout) << "********* "
|
||||
<< "Starting basic read tests for " << suffix << " images *********\n";
|
||||
@ -111,29 +151,52 @@ int main(int argc, char **argv)
|
||||
QTextStream(stdout) << "QImageReader::supportedImageFormats: " << formatStrings.join(", ") << "\n";
|
||||
|
||||
const QFileInfoList lstImgDir = imgdir.entryInfoList();
|
||||
// Launch 2 runs for each test: first run on a random access device, second run on a sequential access device
|
||||
for (int seq = 0; seq < 2; ++seq) {
|
||||
if (seq) {
|
||||
QTextStream(stdout) << "* Run on SEQUENTIAL ACCESS device\n";
|
||||
} else {
|
||||
QTextStream(stdout) << "* Run on RANDOM ACCESS device\n";
|
||||
}
|
||||
for (const QFileInfo &fi : lstImgDir) {
|
||||
if (!fi.suffix().compare("png", Qt::CaseInsensitive)) {
|
||||
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 expfile = fi.filePath().replace(suffixPos, suffix.count(), QStringLiteral("png"));
|
||||
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();
|
||||
|
||||
QImageReader inputReader(inputfile, format);
|
||||
QImageReader expReader(expfile, "png");
|
||||
std::unique_ptr<QIODevice> inputDevice(seq ? new SequentialFile(inputfile) : new QFile(inputfile));
|
||||
QImageReader inputReader(inputDevice.get(), format);
|
||||
QImageReader expReader(expfile, fmt.toLatin1());
|
||||
|
||||
QImage inputImage;
|
||||
QImage expImage;
|
||||
|
||||
// inputImage is auto-rotated to final orientation
|
||||
inputReader.setAutoTransform(true);
|
||||
|
||||
if (!expReader.read(&expImage)) {
|
||||
QTextStream(stdout) << "ERROR: " << fi.fileName() << ": could not load " << expfilename << ": " << expReader.errorString() << "\n";
|
||||
++failed;
|
||||
continue;
|
||||
}
|
||||
if (!inputReader.canRead()) {
|
||||
// All plugins must pass the test on a random device.
|
||||
// canRead() must also return false if the plugin is unable to run on a sequential device.
|
||||
if (inputDevice->isSequential()) {
|
||||
QTextStream(stdout) << "SKIP : " << fi.fileName() << ": cannot read on a sequential device (don't worry, it's ok)\n";
|
||||
++skipped;
|
||||
} else {
|
||||
QTextStream(stdout) << "FAIL : " << fi.fileName() << ": failed can read: " << inputReader.errorString() << "\n";
|
||||
++failed;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (!inputReader.read(&inputImage)) {
|
||||
@ -160,8 +223,8 @@ int main(int argc, char **argv)
|
||||
inputImage = inputImage.convertToFormat(cmpFormat);
|
||||
}
|
||||
if (expImage.format() != cmpFormat) {
|
||||
QTextStream(stdout) << "INFO : " << fi.fileName() << ": converting " << expfilename << " from " << formatToString(expImage.format()) << " to "
|
||||
<< formatToString(cmpFormat) << '\n';
|
||||
QTextStream(stdout) << "INFO : " << fi.fileName() << ": converting " << expfilename << " from " << formatToString(expImage.format())
|
||||
<< " to " << formatToString(cmpFormat) << '\n';
|
||||
expImage = expImage.convertToFormat(cmpFormat);
|
||||
}
|
||||
if (fuzzyeq(inputImage, expImage, fuzziness)) {
|
||||
@ -175,8 +238,9 @@ int main(int argc, char **argv)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QTextStream(stdout) << "Totals: " << passed << " passed, " << failed << " failed\n";
|
||||
QTextStream(stdout) << "Totals: " << passed << " passed, " << skipped << " skipped, " << failed << " failed\n";
|
||||
QTextStream(stdout) << "********* "
|
||||
<< "Finished basic read tests for " << suffix << " images *********\n";
|
||||
|
||||
|
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 |
@ -2,7 +2,7 @@
|
||||
# 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.h
|
||||
# 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)
|
||||
@ -23,11 +23,10 @@ IF(PKG_CONFIG_FOUND)
|
||||
SET(LibRaw_r_DEFINITIONS ${PC_LIBRAW_R_CFLAGS_OTHER})
|
||||
ENDIF()
|
||||
|
||||
FIND_PATH(LibRaw_INCLUDE_DIR libraw.h
|
||||
FIND_PATH(LibRaw_INCLUDE_DIR libraw/libraw.h
|
||||
HINTS
|
||||
${PC_LIBRAW_INCLUDEDIR}
|
||||
${PC_LibRaw_INCLUDE_DIRS}
|
||||
PATH_SUFFIXES libraw
|
||||
)
|
||||
|
||||
FIND_LIBRARY(LibRaw_LIBRARIES NAMES raw
|
||||
@ -43,7 +42,7 @@ FIND_LIBRARY(LibRaw_r_LIBRARIES NAMES raw_r
|
||||
)
|
||||
|
||||
IF(LibRaw_INCLUDE_DIR)
|
||||
FILE(READ ${LibRaw_INCLUDE_DIR}/libraw_version.h _libraw_version_content)
|
||||
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}")
|
||||
@ -58,7 +57,7 @@ IF(LibRaw_INCLUDE_DIR)
|
||||
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_version.h")
|
||||
MESSAGE(STATUS "Failed to get version information from ${LibRaw_INCLUDE_DIR}/libraw/libraw_version.h")
|
||||
ENDIF()
|
||||
ENDIF()
|
||||
ENDIF()
|
||||
|
@ -20,19 +20,26 @@ endfunction()
|
||||
##################################
|
||||
|
||||
kimageformats_add_plugin(kimg_ani SOURCES ani.cpp)
|
||||
install(FILES ani.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||
|
||||
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||
install(FILES ani.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||
endif()
|
||||
|
||||
##################################
|
||||
|
||||
if (TARGET avif)
|
||||
kimageformats_add_plugin(kimg_avif SOURCES "avif.cpp")
|
||||
target_link_libraries(kimg_avif "avif")
|
||||
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||
install(FILES avif.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
##################################
|
||||
|
||||
install(FILES dds-qt.desktop RENAME dds.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||
install(FILES dds-qt.desktop RENAME dds.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||
endif()
|
||||
|
||||
##################################
|
||||
|
||||
@ -40,14 +47,18 @@ if (BUILD_EPS_PLUGIN)
|
||||
if (TARGET Qt${QT_MAJOR_VERSION}::PrintSupport)
|
||||
kimageformats_add_plugin(kimg_eps SOURCES eps.cpp)
|
||||
target_link_libraries(kimg_eps Qt${QT_MAJOR_VERSION}::PrintSupport)
|
||||
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||
install(FILES eps.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
##################################
|
||||
|
||||
# need this for Qt's version of the plugin
|
||||
install(FILES jp2.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||
# need this for Qt's version of the plugin
|
||||
install(FILES jp2.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||
endif()
|
||||
|
||||
##################################
|
||||
|
||||
@ -65,21 +76,27 @@ if(OpenEXR_FOUND)
|
||||
endif()
|
||||
kde_target_enable_exceptions(kimg_exr PRIVATE)
|
||||
|
||||
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||
install(FILES exr.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
##################################
|
||||
|
||||
kimageformats_add_plugin(kimg_hdr SOURCES hdr.cpp)
|
||||
install(FILES hdr.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||
install(FILES hdr.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||
endif()
|
||||
|
||||
##################################
|
||||
|
||||
if (LibHeif_FOUND)
|
||||
kimageformats_add_plugin(kimg_heif SOURCES heif.cpp)
|
||||
target_link_libraries(kimg_heif PkgConfig::LibHeif)
|
||||
kde_target_enable_exceptions(kimg_heif PRIVATE)
|
||||
|
||||
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||
install(FILES heif.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
##################################
|
||||
@ -90,43 +107,60 @@ if (LibJXL_FOUND AND LibJXLThreads_FOUND)
|
||||
if (LibJXL_VERSION VERSION_GREATER_EQUAL "0.7.0")
|
||||
target_compile_definitions(kimg_jxl PRIVATE KIMG_JXL_API_VERSION=70)
|
||||
endif()
|
||||
|
||||
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||
install(FILES jxl.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
##################################
|
||||
|
||||
kimageformats_add_plugin(kimg_pcx SOURCES pcx.cpp)
|
||||
install(FILES pcx.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||
install(FILES pcx.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||
endif()
|
||||
|
||||
##################################
|
||||
|
||||
kimageformats_add_plugin(kimg_pic SOURCES pic.cpp)
|
||||
install(FILES pic.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||
install(FILES pic.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||
endif()
|
||||
|
||||
##################################
|
||||
|
||||
kimageformats_add_plugin(kimg_psd SOURCES psd.cpp)
|
||||
install(FILES psd.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||
install(FILES psd.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||
endif()
|
||||
|
||||
##################################
|
||||
|
||||
kimageformats_add_plugin(kimg_ras SOURCES ras.cpp)
|
||||
install(FILES ras.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||
install(FILES ras.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||
endif()
|
||||
|
||||
##################################
|
||||
|
||||
kimageformats_add_plugin(kimg_rgb SOURCES rgb.cpp)
|
||||
install(FILES rgb.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||
install(FILES rgb.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||
endif()
|
||||
|
||||
##################################
|
||||
|
||||
kimageformats_add_plugin(kimg_tga SOURCES tga.cpp)
|
||||
install(FILES tga.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||
install(FILES tga.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||
endif()
|
||||
|
||||
##################################
|
||||
|
||||
kimageformats_add_plugin(kimg_xcf SOURCES xcf.cpp)
|
||||
install(FILES xcf.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||
install(FILES xcf.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||
endif()
|
||||
|
||||
##################################
|
||||
|
||||
@ -134,7 +168,9 @@ 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()
|
||||
|
||||
##################################
|
||||
@ -143,10 +179,14 @@ if (KF5Archive_FOUND)
|
||||
|
||||
kimageformats_add_plugin(kimg_kra SOURCES kra.cpp)
|
||||
target_link_libraries(kimg_kra KF5::Archive)
|
||||
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||
install(FILES kra.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||
endif()
|
||||
|
||||
kimageformats_add_plugin(kimg_ora SOURCES ora.cpp)
|
||||
target_link_libraries(kimg_ora KF5::Archive)
|
||||
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||
install(FILES ora.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||
endif()
|
||||
|
||||
endif()
|
||||
|
@ -521,6 +521,9 @@ bool ANIHandler::canRead(QIODevice *device)
|
||||
qWarning("ANIHandler::canRead() called with no device");
|
||||
return false;
|
||||
}
|
||||
if (device->isSequential()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const QByteArray riffIntro = device->peek(12);
|
||||
|
||||
|
@ -42,6 +42,11 @@ bool QAVIFHandler::canRead() const
|
||||
|
||||
if (m_parseState != ParseAvifError) {
|
||||
setFormat("avif");
|
||||
|
||||
if (m_parseState == ParseAvifFinished) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@ -58,7 +63,7 @@ bool QAVIFHandler::canRead(QIODevice *device)
|
||||
}
|
||||
|
||||
avifROData input;
|
||||
input.data = (const uint8_t *)header.constData();
|
||||
input.data = reinterpret_cast<const uint8_t *>(header.constData());
|
||||
input.size = header.size();
|
||||
|
||||
if (avifPeekCompatibleFileType(&input)) {
|
||||
@ -69,7 +74,7 @@ bool QAVIFHandler::canRead(QIODevice *device)
|
||||
|
||||
bool QAVIFHandler::ensureParsed() const
|
||||
{
|
||||
if (m_parseState == ParseAvifSuccess || m_parseState == ParseAvifMetadata) {
|
||||
if (m_parseState == ParseAvifSuccess || m_parseState == ParseAvifMetadata || m_parseState == ParseAvifFinished) {
|
||||
return true;
|
||||
}
|
||||
if (m_parseState == ParseAvifError) {
|
||||
@ -83,7 +88,7 @@ bool QAVIFHandler::ensureParsed() const
|
||||
|
||||
bool QAVIFHandler::ensureOpened() const
|
||||
{
|
||||
if (m_parseState == ParseAvifSuccess) {
|
||||
if (m_parseState == ParseAvifSuccess || m_parseState == ParseAvifFinished) {
|
||||
return true;
|
||||
}
|
||||
if (m_parseState == ParseAvifError) {
|
||||
@ -111,7 +116,7 @@ bool QAVIFHandler::ensureDecoder()
|
||||
|
||||
m_rawData = device()->readAll();
|
||||
|
||||
m_rawAvifData.data = (const uint8_t *)m_rawData.constData();
|
||||
m_rawAvifData.data = reinterpret_cast<const uint8_t *>(m_rawData.constData());
|
||||
m_rawAvifData.size = m_rawData.size();
|
||||
|
||||
if (avifPeekCompatibleFileType(&m_rawAvifData) == AVIF_FALSE) {
|
||||
@ -132,6 +137,10 @@ bool QAVIFHandler::ensureDecoder()
|
||||
m_decoder->strictFlags = AVIF_STRICT_DISABLED;
|
||||
#endif
|
||||
|
||||
#if AVIF_VERSION >= 110000
|
||||
m_decoder->imageDimensionLimit = 65535;
|
||||
#endif
|
||||
|
||||
avifResult decodeResult;
|
||||
|
||||
decodeResult = avifDecoderSetIOMemory(m_decoder, m_rawAvifData.data, m_rawAvifData.size);
|
||||
@ -246,7 +255,7 @@ bool QAVIFHandler::decode_one_frame()
|
||||
|
||||
QColorSpace colorspace;
|
||||
if (m_decoder->image->icc.data && (m_decoder->image->icc.size > 0)) {
|
||||
const QByteArray icc_data((const char *)m_decoder->image->icc.data, (int)m_decoder->image->icc.size);
|
||||
const QByteArray icc_data(reinterpret_cast<const char *>(m_decoder->image->icc.data), m_decoder->image->icc.size);
|
||||
colorspace = QColorSpace::fromIccProfile(icc_data);
|
||||
if (!colorspace.isValid()) {
|
||||
qWarning("AVIF image has Qt-unsupported or invalid ICC profile!");
|
||||
@ -336,7 +345,7 @@ bool QAVIFHandler::decode_one_frame()
|
||||
rgb.format = AVIF_RGB_FORMAT_ARGB;
|
||||
#endif
|
||||
|
||||
#if (AVIF_VERSION >= 80400) && (AVIF_VERSION <= 100100)
|
||||
#if AVIF_VERSION >= 80400
|
||||
if (m_decoder->imageCount > 1) {
|
||||
/* accelerate animated AVIF */
|
||||
rgb.chromaUpsampling = AVIF_CHROMA_UPSAMPLING_FASTEST;
|
||||
@ -351,12 +360,7 @@ bool QAVIFHandler::decode_one_frame()
|
||||
rgb.rowBytes = result.bytesPerLine();
|
||||
rgb.pixels = result.bits();
|
||||
|
||||
#if AVIF_VERSION >= 100101
|
||||
// use faster decoding for animations
|
||||
avifResult res = avifImageYUVToRGB(m_decoder->image, &rgb, (m_decoder->imageCount > 1) ? AVIF_CHROMA_UPSAMPLING_NEAREST : AVIF_YUV_TO_RGB_DEFAULT);
|
||||
#else
|
||||
avifResult res = avifImageYUVToRGB(m_decoder->image, &rgb);
|
||||
#endif
|
||||
if (res != AVIF_RESULT_OK) {
|
||||
qWarning("ERROR in avifImageYUVToRGB: %s", avifResultToString(res));
|
||||
return false;
|
||||
@ -459,6 +463,13 @@ bool QAVIFHandler::read(QImage *image)
|
||||
*image = m_current_image;
|
||||
if (imageCount() >= 2) {
|
||||
m_must_jump_to_next_image = true;
|
||||
if (m_decoder->imageIndex >= m_decoder->imageCount - 1) {
|
||||
// all frames in animation have been read
|
||||
m_parseState = ParseAvifFinished;
|
||||
}
|
||||
} else {
|
||||
// the static image has been read
|
||||
m_parseState = ParseAvifFinished;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -756,7 +767,7 @@ bool QAVIFHandler::write(const QImage &image)
|
||||
avif->transferCharacteristics = transfer_to_save;
|
||||
|
||||
if (iccprofile.size() > 0) {
|
||||
avifImageSetProfileICC(avif, (const uint8_t *)iccprofile.constData(), iccprofile.size());
|
||||
avifImageSetProfileICC(avif, reinterpret_cast<const uint8_t *>(iccprofile.constData()), iccprofile.size());
|
||||
}
|
||||
|
||||
avifRGBImage rgb;
|
||||
@ -782,11 +793,7 @@ bool QAVIFHandler::write(const QImage &image)
|
||||
}
|
||||
}
|
||||
|
||||
#if AVIF_VERSION >= 100101
|
||||
res = avifImageRGBToYUV(avif, &rgb, AVIF_RGB_TO_YUV_DEFAULT);
|
||||
#else
|
||||
res = avifImageRGBToYUV(avif, &rgb);
|
||||
#endif
|
||||
if (res != AVIF_RESULT_OK) {
|
||||
qWarning("ERROR in avifImageRGBToYUV: %s", avifResultToString(res));
|
||||
return false;
|
||||
@ -914,6 +921,7 @@ bool QAVIFHandler::jumpToNextImage()
|
||||
|
||||
if (m_decoder->imageIndex >= 0) {
|
||||
if (m_decoder->imageCount < 2) {
|
||||
m_parseState = ParseAvifSuccess;
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -958,11 +966,13 @@ bool QAVIFHandler::jumpToImage(int imageNumber)
|
||||
|
||||
if (m_decoder->imageCount < 2) { // not an animation
|
||||
if (imageNumber == 0) {
|
||||
return ensureOpened();
|
||||
} else {
|
||||
return false;
|
||||
if (ensureOpened()) {
|
||||
m_parseState = ParseAvifSuccess;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (imageNumber < 0 || imageNumber >= m_decoder->imageCount) { // wrong index
|
||||
return false;
|
||||
@ -970,6 +980,7 @@ bool QAVIFHandler::jumpToImage(int imageNumber)
|
||||
|
||||
if (imageNumber == m_decoder->imageIndex) { // we are here already
|
||||
m_must_jump_to_next_image = false;
|
||||
m_parseState = ParseAvifSuccess;
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1028,7 +1039,8 @@ int QAVIFHandler::loopCount() const
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
// Endless loop to work around https://github.com/AOMediaCodec/libavif/issues/347
|
||||
return -1;
|
||||
}
|
||||
|
||||
QPointF QAVIFHandler::CompatibleChromacity(qreal chrX, qreal chrY)
|
||||
@ -1045,12 +1057,26 @@ QPointF QAVIFHandler::CompatibleChromacity(qreal chrX, qreal chrY)
|
||||
|
||||
QImageIOPlugin::Capabilities QAVIFPlugin::capabilities(QIODevice *device, const QByteArray &format) const
|
||||
{
|
||||
static const bool isAvifDecoderAvailable(avifCodecName(AVIF_CODEC_CHOICE_AUTO, AVIF_CODEC_FLAG_CAN_DECODE) != nullptr);
|
||||
static const bool isAvifEncoderAvailable(avifCodecName(AVIF_CODEC_CHOICE_AUTO, AVIF_CODEC_FLAG_CAN_ENCODE) != nullptr);
|
||||
|
||||
if (format == "avif") {
|
||||
return Capabilities(CanRead | CanWrite);
|
||||
Capabilities format_cap;
|
||||
if (isAvifDecoderAvailable) {
|
||||
format_cap |= CanRead;
|
||||
}
|
||||
if (isAvifEncoderAvailable) {
|
||||
format_cap |= CanWrite;
|
||||
}
|
||||
return format_cap;
|
||||
}
|
||||
|
||||
if (format == "avifs") {
|
||||
return Capabilities(CanRead);
|
||||
Capabilities format_cap;
|
||||
if (isAvifDecoderAvailable) {
|
||||
format_cap |= CanRead;
|
||||
}
|
||||
return format_cap;
|
||||
}
|
||||
|
||||
if (!format.isEmpty()) {
|
||||
@ -1061,10 +1087,10 @@ QImageIOPlugin::Capabilities QAVIFPlugin::capabilities(QIODevice *device, const
|
||||
}
|
||||
|
||||
Capabilities cap;
|
||||
if (device->isReadable() && QAVIFHandler::canRead(device)) {
|
||||
if (device->isReadable() && QAVIFHandler::canRead(device) && isAvifDecoderAvailable) {
|
||||
cap |= CanRead;
|
||||
}
|
||||
if (device->isWritable()) {
|
||||
if (device->isWritable() && isAvifEncoderAvailable) {
|
||||
cap |= CanWrite;
|
||||
}
|
||||
return cap;
|
||||
|
@ -55,6 +55,7 @@ private:
|
||||
ParseAvifNotParsed = 0,
|
||||
ParseAvifSuccess = 1,
|
||||
ParseAvifMetadata = 2,
|
||||
ParseAvifFinished = 3,
|
||||
};
|
||||
|
||||
ParseAvifState m_parseState;
|
||||
|
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
|
@ -8,49 +8,48 @@
|
||||
*/
|
||||
|
||||
#include "heif_p.h"
|
||||
#include "libheif/heif_cxx.h"
|
||||
#include "util_p.h"
|
||||
#include <libheif/heif.h>
|
||||
|
||||
#include <QColorSpace>
|
||||
#include <QDebug>
|
||||
#include <QPointF>
|
||||
#include <QSysInfo>
|
||||
#include <limits>
|
||||
#include <string.h>
|
||||
|
||||
namespace // Private.
|
||||
{
|
||||
struct HeifQIODeviceWriter : public heif::Context::Writer {
|
||||
HeifQIODeviceWriter(QIODevice *device)
|
||||
: m_ioDevice(device)
|
||||
{
|
||||
}
|
||||
size_t HEIFHandler::m_initialized_count = 0;
|
||||
bool HEIFHandler::m_plugins_queried = false;
|
||||
bool HEIFHandler::m_heif_decoder_available = false;
|
||||
bool HEIFHandler::m_heif_encoder_available = false;
|
||||
|
||||
heif_error write(const void *data, size_t size) override
|
||||
{
|
||||
extern "C" {
|
||||
static struct heif_error heifhandler_write_callback(struct heif_context * /* ctx */, const void *data, size_t size, void *userdata)
|
||||
{
|
||||
heif_error error;
|
||||
error.code = heif_error_Ok;
|
||||
error.subcode = heif_suberror_Unspecified;
|
||||
error.message = errorOkMessage;
|
||||
error.message = "Success";
|
||||
|
||||
qint64 bytesWritten = m_ioDevice->write(static_cast<const char *>(data), size);
|
||||
if (!userdata || !data || size == 0) {
|
||||
error.code = heif_error_Usage_error;
|
||||
error.subcode = heif_suberror_Null_pointer_argument;
|
||||
error.message = "Wrong parameters!";
|
||||
return error;
|
||||
}
|
||||
|
||||
QIODevice *ioDevice = static_cast<QIODevice *>(userdata);
|
||||
qint64 bytesWritten = ioDevice->write(static_cast<const char *>(data), size);
|
||||
|
||||
if (bytesWritten < static_cast<qint64>(size)) {
|
||||
error.code = heif_error_Encoding_error;
|
||||
error.message = QIODeviceWriteErrorMessage;
|
||||
error.message = "Bytes written to QIODevice are smaller than input data size";
|
||||
error.subcode = heif_suberror_Cannot_write_output_data;
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static constexpr const char *errorOkMessage = "Success";
|
||||
static constexpr const char *QIODeviceWriteErrorMessage = "Bytes written to QIODevice are smaller than input data size";
|
||||
|
||||
private:
|
||||
QIODevice *m_ioDevice;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
}
|
||||
}
|
||||
|
||||
HEIFHandler::HEIFHandler()
|
||||
: m_parseState(ParseHeicNotParsed)
|
||||
@ -88,6 +87,21 @@ bool HEIFHandler::write(const QImage &image)
|
||||
return false;
|
||||
}
|
||||
|
||||
#if LIBHEIF_HAVE_VERSION(1, 13, 0)
|
||||
startHeifLib();
|
||||
#endif
|
||||
|
||||
bool success = write_helper(image);
|
||||
|
||||
#if LIBHEIF_HAVE_VERSION(1, 13, 0)
|
||||
finishHeifLib();
|
||||
#endif
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool HEIFHandler::write_helper(const QImage &image)
|
||||
{
|
||||
int save_depth; // 8 or 10bit per channel
|
||||
QImage::Format tmpformat; // format for temporary image
|
||||
const bool save_alpha = image.hasAlphaChannel();
|
||||
@ -133,20 +147,25 @@ bool HEIFHandler::write(const QImage &image)
|
||||
|
||||
const QImage tmpimage = image.convertToFormat(tmpformat);
|
||||
|
||||
try {
|
||||
heif::Context ctx;
|
||||
heif::Image heifImage;
|
||||
heifImage.create(tmpimage.width(), tmpimage.height(), heif_colorspace_RGB, chroma);
|
||||
struct heif_context *context = heif_context_alloc();
|
||||
struct heif_error err;
|
||||
struct heif_image *h_image = nullptr;
|
||||
|
||||
err = heif_image_create(tmpimage.width(), tmpimage.height(), heif_colorspace_RGB, chroma, &h_image);
|
||||
if (err.code) {
|
||||
qWarning() << "heif_image_create error:" << err.message;
|
||||
heif_context_free(context);
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArray iccprofile = tmpimage.colorSpace().iccProfile();
|
||||
if (iccprofile.size() > 0) {
|
||||
std::vector<uint8_t> rawProfile(iccprofile.begin(), iccprofile.end());
|
||||
heifImage.set_raw_color_profile(heif_color_profile_type_prof, rawProfile);
|
||||
heif_image_set_raw_color_profile(h_image, "prof", iccprofile.constData(), iccprofile.size());
|
||||
}
|
||||
|
||||
heifImage.add_plane(heif_channel_interleaved, image.width(), image.height(), save_depth);
|
||||
heif_image_add_plane(h_image, heif_channel_interleaved, image.width(), image.height(), save_depth);
|
||||
int stride = 0;
|
||||
uint8_t *const dst = heifImage.get_plane(heif_channel_interleaved, &stride);
|
||||
uint8_t *const dst = heif_image_get_plane(h_image, heif_channel_interleaved, &stride);
|
||||
size_t rowbytes;
|
||||
|
||||
switch (save_depth) {
|
||||
@ -214,42 +233,70 @@ bool HEIFHandler::write(const QImage &image)
|
||||
break;
|
||||
default:
|
||||
qWarning() << "Unsupported depth:" << save_depth;
|
||||
heif_image_release(h_image);
|
||||
heif_context_free(context);
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
|
||||
heif::Encoder encoder(heif_compression_HEVC);
|
||||
struct heif_encoder *encoder = nullptr;
|
||||
err = heif_context_get_encoder_for_format(context, heif_compression_HEVC, &encoder);
|
||||
if (err.code) {
|
||||
qWarning() << "Unable to get an encoder instance:" << err.message;
|
||||
heif_image_release(h_image);
|
||||
heif_context_free(context);
|
||||
return false;
|
||||
}
|
||||
|
||||
encoder.set_lossy_quality(m_quality);
|
||||
heif_encoder_set_lossy_quality(encoder, m_quality);
|
||||
if (m_quality > 90) {
|
||||
if (m_quality == 100) {
|
||||
encoder.set_lossless(true);
|
||||
heif_encoder_set_lossless(encoder, true);
|
||||
}
|
||||
encoder.set_string_parameter("chroma", "444");
|
||||
heif_encoder_set_parameter_string(encoder, "chroma", "444");
|
||||
}
|
||||
|
||||
heif::Context::EncodingOptions encodingOptions;
|
||||
encodingOptions.save_alpha_channel = save_alpha;
|
||||
struct heif_encoding_options *encoder_options = heif_encoding_options_alloc();
|
||||
encoder_options->save_alpha_channel = save_alpha;
|
||||
|
||||
if ((tmpimage.width() % 2 == 1) || (tmpimage.height() % 2 == 1)) {
|
||||
qWarning() << "Image has odd dimension!\nUse even-numbered dimension(s) for better compatibility with other HEIF implementations.";
|
||||
if (save_alpha) {
|
||||
// This helps to save alpha channel when image has odd dimension
|
||||
encodingOptions.macOS_compatibility_workaround = 0;
|
||||
encoder_options->macOS_compatibility_workaround = 0;
|
||||
}
|
||||
}
|
||||
|
||||
ctx.encode_image(heifImage, encoder, encodingOptions);
|
||||
err = heif_context_encode_image(context, h_image, encoder, encoder_options, nullptr);
|
||||
|
||||
HeifQIODeviceWriter writer(device());
|
||||
if (encoder_options) {
|
||||
heif_encoding_options_free(encoder_options);
|
||||
}
|
||||
|
||||
ctx.write(writer);
|
||||
|
||||
} catch (const heif::Error &err) {
|
||||
qWarning() << "libheif error:" << err.get_message().c_str();
|
||||
if (err.code) {
|
||||
qWarning() << "heif_context_encode_image failed:" << err.message;
|
||||
heif_encoder_release(encoder);
|
||||
heif_image_release(h_image);
|
||||
heif_context_free(context);
|
||||
return false;
|
||||
}
|
||||
|
||||
struct heif_writer writer;
|
||||
writer.writer_api_version = 1;
|
||||
writer.write = heifhandler_write_callback;
|
||||
|
||||
err = heif_context_write(context, &writer, device());
|
||||
|
||||
heif_encoder_release(encoder);
|
||||
heif_image_release(h_image);
|
||||
|
||||
if (err.code) {
|
||||
qWarning() << "Writing HEIF image failed:" << err.message;
|
||||
heif_context_free(context);
|
||||
return false;
|
||||
}
|
||||
|
||||
heif_context_free(context);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -356,8 +403,18 @@ bool HEIFHandler::ensureParsed() const
|
||||
|
||||
HEIFHandler *that = const_cast<HEIFHandler *>(this);
|
||||
|
||||
return that->ensureDecoder();
|
||||
#if LIBHEIF_HAVE_VERSION(1, 13, 0)
|
||||
startHeifLib();
|
||||
#endif
|
||||
|
||||
bool success = that->ensureDecoder();
|
||||
|
||||
#if LIBHEIF_HAVE_VERSION(1, 13, 0)
|
||||
finishHeifLib();
|
||||
#endif
|
||||
return success;
|
||||
}
|
||||
|
||||
bool HEIFHandler::ensureDecoder()
|
||||
{
|
||||
if (m_parseState != ParseHeicNotParsed) {
|
||||
@ -373,14 +430,35 @@ bool HEIFHandler::ensureDecoder()
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
heif::Context ctx;
|
||||
ctx.read_from_memory_without_copy((const void *)(buffer.constData()), buffer.size());
|
||||
struct heif_context *ctx = heif_context_alloc();
|
||||
struct heif_error err = heif_context_read_from_memory(ctx, static_cast<const void *>(buffer.constData()), buffer.size(), nullptr);
|
||||
|
||||
heif::ImageHandle handle = ctx.get_primary_image_handle();
|
||||
if (err.code) {
|
||||
qWarning() << "heif_context_read_from_memory error:" << err.message;
|
||||
heif_context_free(ctx);
|
||||
m_parseState = ParseHeicError;
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool hasAlphaChannel = handle.has_alpha_channel();
|
||||
const int bit_depth = handle.get_luma_bits_per_pixel();
|
||||
struct heif_image_handle *handle = nullptr;
|
||||
err = heif_context_get_primary_image_handle(ctx, &handle);
|
||||
if (err.code) {
|
||||
qWarning() << "heif_context_get_primary_image_handle error:" << err.message;
|
||||
heif_context_free(ctx);
|
||||
m_parseState = ParseHeicError;
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((heif_image_handle_get_width(handle) == 0) || (heif_image_handle_get_height(handle) == 0)) {
|
||||
m_parseState = ParseHeicError;
|
||||
heif_image_handle_release(handle);
|
||||
heif_context_free(ctx);
|
||||
qWarning() << "HEIC image has zero dimension";
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool hasAlphaChannel = heif_image_handle_has_alpha_channel(handle);
|
||||
const int bit_depth = heif_image_handle_get_luma_bits_per_pixel(handle);
|
||||
heif_chroma chroma;
|
||||
|
||||
QImage::Format target_image_format;
|
||||
@ -403,6 +481,8 @@ bool HEIFHandler::ensureDecoder()
|
||||
}
|
||||
} else {
|
||||
m_parseState = ParseHeicError;
|
||||
heif_image_handle_release(handle);
|
||||
heif_context_free(ctx);
|
||||
if (bit_depth > 0) {
|
||||
qWarning() << "Unsupported bit depth:" << bit_depth;
|
||||
} else {
|
||||
@ -411,23 +491,48 @@ bool HEIFHandler::ensureDecoder()
|
||||
return false;
|
||||
}
|
||||
|
||||
heif::Image img = handle.decode_image(heif_colorspace_RGB, chroma);
|
||||
struct heif_decoding_options *decoder_option = heif_decoding_options_alloc();
|
||||
|
||||
const int imageWidth = img.get_width(heif_channel_interleaved);
|
||||
const int imageHeight = img.get_height(heif_channel_interleaved);
|
||||
#if LIBHEIF_HAVE_VERSION(1, 13, 0)
|
||||
decoder_option->strict_decoding = 1;
|
||||
#endif
|
||||
|
||||
struct heif_image *img = nullptr;
|
||||
err = heif_decode_image(handle, &img, heif_colorspace_RGB, chroma, decoder_option);
|
||||
|
||||
if (decoder_option) {
|
||||
heif_decoding_options_free(decoder_option);
|
||||
}
|
||||
|
||||
if (err.code) {
|
||||
qWarning() << "heif_decode_image error:" << err.message;
|
||||
heif_image_handle_release(handle);
|
||||
heif_context_free(ctx);
|
||||
m_parseState = ParseHeicError;
|
||||
return false;
|
||||
}
|
||||
|
||||
const int imageWidth = heif_image_get_width(img, heif_channel_interleaved);
|
||||
const int imageHeight = heif_image_get_height(img, heif_channel_interleaved);
|
||||
|
||||
QSize imageSize(imageWidth, imageHeight);
|
||||
|
||||
if (!imageSize.isValid()) {
|
||||
heif_image_release(img);
|
||||
heif_image_handle_release(handle);
|
||||
heif_context_free(ctx);
|
||||
m_parseState = ParseHeicError;
|
||||
qWarning() << "HEIC image size invalid:" << imageSize;
|
||||
return false;
|
||||
}
|
||||
|
||||
int stride = 0;
|
||||
const uint8_t *const src = img.get_plane(heif_channel_interleaved, &stride);
|
||||
const uint8_t *const src = heif_image_get_plane_readonly(img, heif_channel_interleaved, &stride);
|
||||
|
||||
if (!src || stride <= 0) {
|
||||
heif_image_release(img);
|
||||
heif_image_handle_release(handle);
|
||||
heif_context_free(ctx);
|
||||
m_parseState = ParseHeicError;
|
||||
qWarning() << "HEIC data pixels information not valid!";
|
||||
return false;
|
||||
@ -435,6 +540,9 @@ bool HEIFHandler::ensureDecoder()
|
||||
|
||||
m_current_image = imageAlloc(imageSize, target_image_format);
|
||||
if (m_current_image.isNull()) {
|
||||
heif_image_release(img);
|
||||
heif_image_handle_release(handle);
|
||||
heif_context_free(ctx);
|
||||
m_parseState = ParseHeicError;
|
||||
qWarning() << "Unable to allocate memory!";
|
||||
return false;
|
||||
@ -598,19 +706,21 @@ bool HEIFHandler::ensureDecoder()
|
||||
}
|
||||
break;
|
||||
default:
|
||||
heif_image_release(img);
|
||||
heif_image_handle_release(handle);
|
||||
heif_context_free(ctx);
|
||||
m_parseState = ParseHeicError;
|
||||
qWarning() << "Unsupported bit depth:" << bit_depth;
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
|
||||
heif_color_profile_type profileType = heif_image_handle_get_color_profile_type(handle.get_raw_image_handle());
|
||||
struct heif_error err;
|
||||
heif_color_profile_type profileType = heif_image_handle_get_color_profile_type(handle);
|
||||
if (profileType == heif_color_profile_type_prof || profileType == heif_color_profile_type_rICC) {
|
||||
int rawProfileSize = (int)heif_image_handle_get_raw_color_profile_size(handle.get_raw_image_handle());
|
||||
if (rawProfileSize > 0) {
|
||||
size_t rawProfileSize = heif_image_handle_get_raw_color_profile_size(handle);
|
||||
if (rawProfileSize > 0 && rawProfileSize < std::numeric_limits<int>::max()) {
|
||||
QByteArray ba(rawProfileSize, 0);
|
||||
err = heif_image_handle_get_raw_color_profile(handle.get_raw_image_handle(), ba.data());
|
||||
err = heif_image_handle_get_raw_color_profile(handle, ba.data());
|
||||
if (err.code) {
|
||||
qWarning() << "icc profile loading failed";
|
||||
} else {
|
||||
@ -620,12 +730,12 @@ bool HEIFHandler::ensureDecoder()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
qWarning() << "icc profile is empty";
|
||||
qWarning() << "icc profile is empty or above limits";
|
||||
}
|
||||
|
||||
} else if (profileType == heif_color_profile_type_nclx) {
|
||||
struct heif_color_profile_nclx *nclx = nullptr;
|
||||
err = heif_image_handle_get_nclx_color_profile(handle.get_raw_image_handle(), &nclx);
|
||||
err = heif_image_handle_get_nclx_color_profile(handle, &nclx);
|
||||
if (err.code || !nclx) {
|
||||
qWarning() << "nclx profile loading failed";
|
||||
} else {
|
||||
@ -686,24 +796,107 @@ bool HEIFHandler::ensureDecoder()
|
||||
m_current_image.setColorSpace(QColorSpace(QColorSpace::SRgb));
|
||||
}
|
||||
|
||||
} catch (const heif::Error &err) {
|
||||
m_parseState = ParseHeicError;
|
||||
qWarning() << "libheif error:" << err.get_message().c_str();
|
||||
return false;
|
||||
}
|
||||
|
||||
heif_image_release(img);
|
||||
heif_image_handle_release(handle);
|
||||
heif_context_free(ctx);
|
||||
m_parseState = ParseHeicSuccess;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HEIFHandler::isHeifDecoderAvailable()
|
||||
{
|
||||
QMutexLocker locker(&getHEIFHandlerMutex());
|
||||
|
||||
if (!m_plugins_queried) {
|
||||
#if LIBHEIF_HAVE_VERSION(1, 13, 0)
|
||||
if (m_initialized_count == 0) {
|
||||
heif_init(nullptr);
|
||||
}
|
||||
#endif
|
||||
|
||||
m_heif_encoder_available = heif_have_encoder_for_format(heif_compression_HEVC);
|
||||
m_heif_decoder_available = heif_have_decoder_for_format(heif_compression_HEVC);
|
||||
m_plugins_queried = true;
|
||||
|
||||
#if LIBHEIF_HAVE_VERSION(1, 13, 0)
|
||||
if (m_initialized_count == 0) {
|
||||
heif_deinit();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
return m_heif_decoder_available;
|
||||
}
|
||||
|
||||
bool HEIFHandler::isHeifEncoderAvailable()
|
||||
{
|
||||
QMutexLocker locker(&getHEIFHandlerMutex());
|
||||
|
||||
if (!m_plugins_queried) {
|
||||
#if LIBHEIF_HAVE_VERSION(1, 13, 0)
|
||||
if (m_initialized_count == 0) {
|
||||
heif_init(nullptr);
|
||||
}
|
||||
#endif
|
||||
|
||||
m_heif_decoder_available = heif_have_decoder_for_format(heif_compression_HEVC);
|
||||
m_heif_encoder_available = heif_have_encoder_for_format(heif_compression_HEVC);
|
||||
m_plugins_queried = true;
|
||||
|
||||
#if LIBHEIF_HAVE_VERSION(1, 13, 0)
|
||||
if (m_initialized_count == 0) {
|
||||
heif_deinit();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
return m_heif_encoder_available;
|
||||
}
|
||||
|
||||
void HEIFHandler::startHeifLib()
|
||||
{
|
||||
#if LIBHEIF_HAVE_VERSION(1, 13, 0)
|
||||
QMutexLocker locker(&getHEIFHandlerMutex());
|
||||
|
||||
if (m_initialized_count == 0) {
|
||||
heif_init(nullptr);
|
||||
}
|
||||
|
||||
m_initialized_count++;
|
||||
#endif
|
||||
}
|
||||
|
||||
void HEIFHandler::finishHeifLib()
|
||||
{
|
||||
#if LIBHEIF_HAVE_VERSION(1, 13, 0)
|
||||
QMutexLocker locker(&getHEIFHandlerMutex());
|
||||
|
||||
if (m_initialized_count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_initialized_count--;
|
||||
if (m_initialized_count == 0) {
|
||||
heif_deinit();
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
QMutex &HEIFHandler::getHEIFHandlerMutex()
|
||||
{
|
||||
static QMutex heif_handler_mutex;
|
||||
return heif_handler_mutex;
|
||||
}
|
||||
|
||||
QImageIOPlugin::Capabilities HEIFPlugin::capabilities(QIODevice *device, const QByteArray &format) const
|
||||
{
|
||||
if (format == "heif" || format == "heic") {
|
||||
Capabilities format_cap;
|
||||
if (heif_have_decoder_for_format(heif_compression_HEVC)) {
|
||||
if (HEIFHandler::isHeifDecoderAvailable()) {
|
||||
format_cap |= CanRead;
|
||||
}
|
||||
if (heif_have_encoder_for_format(heif_compression_HEVC)) {
|
||||
if (HEIFHandler::isHeifEncoderAvailable()) {
|
||||
format_cap |= CanWrite;
|
||||
}
|
||||
return format_cap;
|
||||
@ -716,11 +909,11 @@ QImageIOPlugin::Capabilities HEIFPlugin::capabilities(QIODevice *device, const Q
|
||||
}
|
||||
|
||||
Capabilities cap;
|
||||
if (device->isReadable() && HEIFHandler::canRead(device) && heif_have_decoder_for_format(heif_compression_HEVC)) {
|
||||
if (device->isReadable() && HEIFHandler::canRead(device) && HEIFHandler::isHeifDecoderAvailable()) {
|
||||
cap |= CanRead;
|
||||
}
|
||||
|
||||
if (device->isWritable() && heif_have_encoder_for_format(heif_compression_HEVC)) {
|
||||
if (device->isWritable() && HEIFHandler::isHeifEncoderAvailable()) {
|
||||
cap |= CanWrite;
|
||||
}
|
||||
return cap;
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include <QByteArray>
|
||||
#include <QImage>
|
||||
#include <QImageIOPlugin>
|
||||
#include <QMutex>
|
||||
|
||||
class HEIFHandler : public QImageIOHandler
|
||||
{
|
||||
@ -29,6 +30,9 @@ public:
|
||||
void setOption(ImageOption option, const QVariant &value) override;
|
||||
bool supportsOption(ImageOption option) const override;
|
||||
|
||||
static bool isHeifDecoderAvailable();
|
||||
static bool isHeifEncoderAvailable();
|
||||
|
||||
private:
|
||||
static bool isSupportedBMFFType(const QByteArray &header);
|
||||
bool ensureParsed() const;
|
||||
@ -43,6 +47,18 @@ private:
|
||||
ParseHeicState m_parseState;
|
||||
int m_quality;
|
||||
QImage m_current_image;
|
||||
|
||||
bool write_helper(const QImage &image);
|
||||
|
||||
static void startHeifLib();
|
||||
static void finishHeifLib();
|
||||
static size_t m_initialized_count;
|
||||
|
||||
static bool m_plugins_queried;
|
||||
static bool m_heif_decoder_available;
|
||||
static bool m_heif_encoder_available;
|
||||
|
||||
static QMutex &getHEIFHandlerMutex();
|
||||
};
|
||||
|
||||
class HEIFPlugin : public QImageIOPlugin
|
||||
|
@ -48,6 +48,11 @@ bool QJpegXLHandler::canRead() const
|
||||
|
||||
if (m_parseState != ParseJpegXLError) {
|
||||
setFormat("jxl");
|
||||
|
||||
if (m_parseState == ParseJpegXLFinished) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@ -63,7 +68,7 @@ bool QJpegXLHandler::canRead(QIODevice *device)
|
||||
return false;
|
||||
}
|
||||
|
||||
JxlSignature signature = JxlSignatureCheck((const uint8_t *)header.constData(), header.size());
|
||||
JxlSignature signature = JxlSignatureCheck(reinterpret_cast<const uint8_t *>(header.constData()), header.size());
|
||||
if (signature == JXL_SIG_CODESTREAM || signature == JXL_SIG_CONTAINER) {
|
||||
return true;
|
||||
}
|
||||
@ -72,7 +77,7 @@ bool QJpegXLHandler::canRead(QIODevice *device)
|
||||
|
||||
bool QJpegXLHandler::ensureParsed() const
|
||||
{
|
||||
if (m_parseState == ParseJpegXLSuccess || m_parseState == ParseJpegXLBasicInfoParsed) {
|
||||
if (m_parseState == ParseJpegXLSuccess || m_parseState == ParseJpegXLBasicInfoParsed || m_parseState == ParseJpegXLFinished) {
|
||||
return true;
|
||||
}
|
||||
if (m_parseState == ParseJpegXLError) {
|
||||
@ -90,7 +95,7 @@ bool QJpegXLHandler::ensureALLCounted() const
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_parseState == ParseJpegXLSuccess) {
|
||||
if (m_parseState == ParseJpegXLSuccess || m_parseState == ParseJpegXLFinished) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -111,7 +116,7 @@ bool QJpegXLHandler::ensureDecoder()
|
||||
return false;
|
||||
}
|
||||
|
||||
JxlSignature signature = JxlSignatureCheck((const uint8_t *)m_rawData.constData(), m_rawData.size());
|
||||
JxlSignature signature = JxlSignatureCheck(reinterpret_cast<const uint8_t *>(m_rawData.constData()), m_rawData.size());
|
||||
if (signature != JXL_SIG_CODESTREAM && signature != JXL_SIG_CONTAINER) {
|
||||
m_parseState = ParseJpegXLError;
|
||||
return false;
|
||||
@ -139,7 +144,7 @@ bool QJpegXLHandler::ensureDecoder()
|
||||
}
|
||||
}
|
||||
|
||||
if (JxlDecoderSetInput(m_decoder, (const uint8_t *)m_rawData.constData(), m_rawData.size()) != JXL_DEC_SUCCESS) {
|
||||
if (JxlDecoderSetInput(m_decoder, reinterpret_cast<const uint8_t *>(m_rawData.constData()), m_rawData.size()) != JXL_DEC_SUCCESS) {
|
||||
qWarning("ERROR: JxlDecoderSetInput failed");
|
||||
m_parseState = ParseJpegXLError;
|
||||
return false;
|
||||
@ -273,8 +278,12 @@ bool QJpegXLHandler::countALLFrames()
|
||||
size_t icc_size = 0;
|
||||
if (JxlDecoderGetICCProfileSize(m_decoder, &m_input_pixel_format, JXL_COLOR_PROFILE_TARGET_DATA, &icc_size) == JXL_DEC_SUCCESS) {
|
||||
if (icc_size > 0) {
|
||||
QByteArray icc_data((int)icc_size, 0);
|
||||
if (JxlDecoderGetColorAsICCProfile(m_decoder, &m_input_pixel_format, JXL_COLOR_PROFILE_TARGET_DATA, (uint8_t *)icc_data.data(), icc_data.size())
|
||||
QByteArray icc_data(icc_size, 0);
|
||||
if (JxlDecoderGetColorAsICCProfile(m_decoder,
|
||||
&m_input_pixel_format,
|
||||
JXL_COLOR_PROFILE_TARGET_DATA,
|
||||
reinterpret_cast<uint8_t *>(icc_data.data()),
|
||||
icc_data.size())
|
||||
== JXL_DEC_SUCCESS) {
|
||||
m_colorspace = QColorSpace::fromIccProfile(icc_data);
|
||||
|
||||
@ -397,7 +406,15 @@ bool QJpegXLHandler::decode_one_frame()
|
||||
if (!rewind()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// all frames in animation have been read
|
||||
m_parseState = ParseJpegXLFinished;
|
||||
} else {
|
||||
m_parseState = ParseJpegXLSuccess;
|
||||
}
|
||||
} else {
|
||||
// the static image has been read
|
||||
m_parseState = ParseJpegXLFinished;
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -609,7 +626,7 @@ bool QJpegXLHandler::write(const QImage &image)
|
||||
}
|
||||
|
||||
if (!convert_color_profile && iccprofile.size() > 0) {
|
||||
status = JxlEncoderSetICCProfile(encoder, (const uint8_t *)iccprofile.constData(), iccprofile.size());
|
||||
status = JxlEncoderSetICCProfile(encoder, reinterpret_cast<const uint8_t *>(iccprofile.constData()), iccprofile.size());
|
||||
if (status != JXL_ENC_SUCCESS) {
|
||||
qWarning("JxlEncoderSetICCProfile failed!");
|
||||
if (runner) {
|
||||
@ -648,7 +665,7 @@ bool QJpegXLHandler::write(const QImage &image)
|
||||
#endif
|
||||
|
||||
if (image.hasAlphaChannel() || ((save_depth == 8) && (xsize % 4 == 0))) {
|
||||
status = JxlEncoderAddImageFrame(encoder_options, &pixel_format, (void *)tmpimage.constBits(), buffer_size);
|
||||
status = JxlEncoderAddImageFrame(encoder_options, &pixel_format, static_cast<const void *>(tmpimage.constBits()), buffer_size);
|
||||
} else {
|
||||
if (save_depth > 8) { // 16bit depth without alpha channel
|
||||
uint16_t *tmp_buffer = new (std::nothrow) uint16_t[3 * xsize * ysize];
|
||||
@ -679,7 +696,7 @@ bool QJpegXLHandler::write(const QImage &image)
|
||||
src_pixels += 2; // skipalpha
|
||||
}
|
||||
}
|
||||
status = JxlEncoderAddImageFrame(encoder_options, &pixel_format, (void *)tmp_buffer, buffer_size);
|
||||
status = JxlEncoderAddImageFrame(encoder_options, &pixel_format, static_cast<const void *>(tmp_buffer), buffer_size);
|
||||
delete[] tmp_buffer;
|
||||
} else { // 8bit depth without alpha channel
|
||||
uchar *tmp_buffer8 = new (std::nothrow) uchar[3 * xsize * ysize];
|
||||
@ -698,7 +715,7 @@ bool QJpegXLHandler::write(const QImage &image)
|
||||
memcpy(dest_pixels8, tmpimage.constScanLine(y), rowbytes);
|
||||
dest_pixels8 += rowbytes;
|
||||
}
|
||||
status = JxlEncoderAddImageFrame(encoder_options, &pixel_format, (void *)tmp_buffer8, buffer_size);
|
||||
status = JxlEncoderAddImageFrame(encoder_options, &pixel_format, static_cast<const void *>(tmp_buffer8), buffer_size);
|
||||
delete[] tmp_buffer8;
|
||||
}
|
||||
}
|
||||
@ -856,6 +873,7 @@ bool QJpegXLHandler::jumpToNextImage()
|
||||
}
|
||||
}
|
||||
|
||||
m_parseState = ParseJpegXLSuccess;
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -870,12 +888,14 @@ bool QJpegXLHandler::jumpToImage(int imageNumber)
|
||||
}
|
||||
|
||||
if (imageNumber == m_currentimage_index) {
|
||||
m_parseState = ParseJpegXLSuccess;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (imageNumber > m_currentimage_index) {
|
||||
JxlDecoderSkipFrames(m_decoder, imageNumber - m_currentimage_index);
|
||||
m_currentimage_index = imageNumber;
|
||||
m_parseState = ParseJpegXLSuccess;
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -887,6 +907,7 @@ bool QJpegXLHandler::jumpToImage(int imageNumber)
|
||||
JxlDecoderSkipFrames(m_decoder, imageNumber);
|
||||
}
|
||||
m_currentimage_index = imageNumber;
|
||||
m_parseState = ParseJpegXLSuccess;
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -910,7 +931,7 @@ int QJpegXLHandler::loopCount() const
|
||||
}
|
||||
|
||||
if (m_basicinfo.have_animation) {
|
||||
return 1;
|
||||
return (m_basicinfo.animation.num_loops > 0) ? m_basicinfo.animation.num_loops - 1 : -1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
@ -930,7 +951,7 @@ bool QJpegXLHandler::rewind()
|
||||
}
|
||||
}
|
||||
|
||||
if (JxlDecoderSetInput(m_decoder, (const uint8_t *)m_rawData.constData(), m_rawData.size()) != JXL_DEC_SUCCESS) {
|
||||
if (JxlDecoderSetInput(m_decoder, reinterpret_cast<const uint8_t *>(m_rawData.constData()), m_rawData.size()) != JXL_DEC_SUCCESS) {
|
||||
qWarning("ERROR: JxlDecoderSetInput failed");
|
||||
m_parseState = ParseJpegXLError;
|
||||
return false;
|
||||
|
@ -57,6 +57,7 @@ private:
|
||||
ParseJpegXLNotParsed = 0,
|
||||
ParseJpegXLSuccess = 1,
|
||||
ParseJpegXLBasicInfoParsed = 2,
|
||||
ParseJpegXLFinished = 3,
|
||||
};
|
||||
|
||||
ParseJpegXLState m_parseState;
|
||||
|
@ -57,6 +57,9 @@ bool KraHandler::canRead(QIODevice *device)
|
||||
qWarning("KraHandler::canRead() called with no device");
|
||||
return false;
|
||||
}
|
||||
if (device->isSequential()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char buff[57];
|
||||
if (device->peek(buff, sizeof(buff)) == sizeof(buff)) {
|
||||
|
@ -56,6 +56,9 @@ bool OraHandler::canRead(QIODevice *device)
|
||||
qWarning("OraHandler::canRead() called with no device");
|
||||
return false;
|
||||
}
|
||||
if (device->isSequential()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char buff[54];
|
||||
if (device->peek(buff, sizeof(buff)) == sizeof(buff)) {
|
||||
|
@ -174,7 +174,7 @@ static QDataStream &operator>>(QDataStream &s, PCXHEADER &ph)
|
||||
|
||||
// Skip the rest of the header
|
||||
quint8 byte;
|
||||
while (s.device()->pos() < 128) {
|
||||
for (auto i = 0; i < 54; ++i) {
|
||||
s >> byte;
|
||||
}
|
||||
|
||||
@ -230,7 +230,7 @@ PCXHEADER::PCXHEADER()
|
||||
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 size = buf.size();
|
||||
@ -257,9 +257,11 @@ static void readLine(QDataStream &s, QByteArray &buf, const PCXHEADER &header)
|
||||
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);
|
||||
|
||||
@ -268,16 +270,18 @@ static void readImage1(QImage &img, QDataStream &s, const PCXHEADER &header)
|
||||
|
||||
if (img.isNull()) {
|
||||
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int y = 0; y < header.height(); ++y) {
|
||||
if (s.atEnd()) {
|
||||
img = QImage();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!readLine(s, buf, header)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
readLine(s, buf, header);
|
||||
uchar *p = img.scanLine(y);
|
||||
unsigned int bpl = qMin((quint16)((header.width() + 7) / 8), header.BytesPerLine);
|
||||
for (unsigned int x = 0; x < bpl; ++x) {
|
||||
@ -288,9 +292,11 @@ static void readImage1(QImage &img, QDataStream &s, const PCXHEADER &header)
|
||||
// Set the color palette
|
||||
img.setColor(0, qRgb(0, 0, 0));
|
||||
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 pixbuf(header.width(), 0);
|
||||
@ -299,17 +305,18 @@ static void readImage4(QImage &img, QDataStream &s, const PCXHEADER &header)
|
||||
img.setColorCount(16);
|
||||
if (img.isNull()) {
|
||||
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int y = 0; y < header.height(); ++y) {
|
||||
if (s.atEnd()) {
|
||||
img = QImage();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
pixbuf.fill(0);
|
||||
readLine(s, buf, header);
|
||||
if (!readLine(s, buf, header)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
quint32 offset = i * header.BytesPerLine;
|
||||
@ -333,9 +340,11 @@ static void readImage4(QImage &img, QDataStream &s, const PCXHEADER &header)
|
||||
for (int i = 0; i < 16; ++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);
|
||||
|
||||
@ -344,21 +353,21 @@ static void readImage8(QImage &img, QDataStream &s, const PCXHEADER &header)
|
||||
|
||||
if (img.isNull()) {
|
||||
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int y = 0; y < header.height(); ++y) {
|
||||
if (s.atEnd()) {
|
||||
img = QImage();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
readLine(s, buf, header);
|
||||
if (!readLine(s, buf, header)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uchar *p = img.scanLine(y);
|
||||
|
||||
if (!p) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned int bpl = qMin(header.BytesPerLine, (quint16)header.width());
|
||||
@ -367,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
|
||||
quint8 flag = 0;
|
||||
if (auto device = s.device()) {
|
||||
if (device->isSequential()) {
|
||||
while (flag != 12 && s.status() == QDataStream::Ok) {
|
||||
s >> flag;
|
||||
// qDebug() << "Palette Flag: " << flag;
|
||||
}
|
||||
}
|
||||
else {
|
||||
device->seek(device->size() - 769);
|
||||
s >> flag;
|
||||
}
|
||||
}
|
||||
|
||||
// qDebug() << "Palette Flag: " << flag;
|
||||
if (flag == 12 && (header.Version == 5 || header.Version == 2)) {
|
||||
// Read the palette
|
||||
quint8 r;
|
||||
@ -381,9 +401,11 @@ static void readImage8(QImage &img, QDataStream &s, const PCXHEADER &header)
|
||||
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 g_buf(header.BytesPerLine, 0);
|
||||
@ -393,27 +415,34 @@ static void readImage24(QImage &img, QDataStream &s, const PCXHEADER &header)
|
||||
|
||||
if (img.isNull()) {
|
||||
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int y = 0; y < header.height(); ++y) {
|
||||
if (s.atEnd()) {
|
||||
img = QImage();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
readLine(s, r_buf, header);
|
||||
readLine(s, g_buf, header);
|
||||
readLine(s, b_buf, header);
|
||||
if (!readLine(s, r_buf, header)) {
|
||||
return false;
|
||||
}
|
||||
if (!readLine(s, g_buf, header)) {
|
||||
return false;
|
||||
}
|
||||
if (!readLine(s, b_buf, header)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint *p = (uint *)img.scanLine(y);
|
||||
for (int x = 0; x < header.width(); ++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 size = buf.size();
|
||||
@ -439,15 +468,26 @@ static void writeLine(QDataStream &s, QByteArray &buf)
|
||||
|
||||
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)
|
||||
{
|
||||
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.NPlanes = 1;
|
||||
header.BytesPerLine = img.bytesPerLine();
|
||||
if (header.BytesPerLine == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
s << header;
|
||||
|
||||
@ -458,18 +498,24 @@ static void writeImage1(QImage &img, QDataStream &s, PCXHEADER &header)
|
||||
|
||||
// Invert as QImage uses reverse palette for monochrome images?
|
||||
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.NPlanes = 4;
|
||||
header.BytesPerLine = header.width() / 8;
|
||||
if (header.BytesPerLine == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
header.ColorMap.setColor(i, img.color(i));
|
||||
@ -499,16 +545,22 @@ static void writeImage4(QImage &img, QDataStream &s, PCXHEADER &header)
|
||||
}
|
||||
|
||||
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.NPlanes = 1;
|
||||
header.BytesPerLine = img.bytesPerLine();
|
||||
if (header.BytesPerLine == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
s << header;
|
||||
|
||||
@ -521,7 +573,9 @@ static void writeImage8(QImage &img, QDataStream &s, PCXHEADER &header)
|
||||
buf[i] = p[i];
|
||||
}
|
||||
|
||||
writeLine(s, buf);
|
||||
if (!writeLine(s, buf)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Write palette flag
|
||||
@ -532,13 +586,25 @@ static void writeImage8(QImage &img, QDataStream &s, PCXHEADER &header)
|
||||
for (int i = 0; i < 256; ++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.NPlanes = 3;
|
||||
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;
|
||||
|
||||
@ -547,7 +613,7 @@ static void writeImage24(QImage &img, QDataStream &s, PCXHEADER &header)
|
||||
QByteArray b_buf(header.width(), 0);
|
||||
|
||||
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) {
|
||||
QRgb rgb = *p++;
|
||||
@ -556,10 +622,18 @@ static void writeImage24(QImage &img, QDataStream &s, PCXHEADER &header)
|
||||
b_buf[x] = qBlue(rgb);
|
||||
}
|
||||
|
||||
writeLine(s, r_buf);
|
||||
writeLine(s, g_buf);
|
||||
writeLine(s, b_buf);
|
||||
if (!writeLine(s, r_buf)) {
|
||||
return false;
|
||||
}
|
||||
if (!writeLine(s, g_buf)) {
|
||||
return false;
|
||||
}
|
||||
if (!writeLine(s, b_buf)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
PCXHandler::PCXHandler()
|
||||
@ -588,46 +662,30 @@ bool PCXHandler::read(QImage *outImage)
|
||||
|
||||
s >> header;
|
||||
|
||||
if (header.Manufacturer != 10 || s.atEnd()) {
|
||||
if (header.Manufacturer != 10 || header.BytesPerLine == 0 || s.atEnd()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// int w = header.width();
|
||||
// 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;
|
||||
|
||||
auto ok = false;
|
||||
QImage img;
|
||||
|
||||
if (header.Bpp == 1 && header.NPlanes == 1) {
|
||||
readImage1(img, s, header);
|
||||
ok = readImage1(img, s, header);
|
||||
} 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) {
|
||||
readImage8(img, s, header);
|
||||
ok = readImage8(img, s, header);
|
||||
} else if (header.Bpp == 8 && header.NPlanes == 3) {
|
||||
readImage24(img, s, header);
|
||||
ok = readImage24(img, s, header);
|
||||
}
|
||||
|
||||
// qDebug() << "Image Bytes: " << img.numBytes();
|
||||
// qDebug() << "Image Bytes Per Line: " << img.bytesPerLine();
|
||||
// qDebug() << "Image Depth: " << img.depth();
|
||||
if (img.isNull() || !ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!img.isNull()) {
|
||||
img.setDotsPerMeterX(qRound(header.HDpi / 25.4 * 1000));
|
||||
img.setDotsPerMeterY(qRound(header.YDpi / 25.4 * 1000));
|
||||
*outImage = img;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool PCXHandler::write(const QImage &image)
|
||||
@ -644,12 +702,6 @@ bool PCXHandler::write(const QImage &image)
|
||||
return false;
|
||||
}
|
||||
|
||||
// qDebug() << "Width: " << w;
|
||||
// qDebug() << "Height: " << h;
|
||||
// qDebug() << "Depth: " << img.depth();
|
||||
// qDebug() << "BytesPerLine: " << img.bytesPerLine();
|
||||
// qDebug() << "Color Count: " << img.colorCount();
|
||||
|
||||
PCXHEADER header;
|
||||
|
||||
header.Manufacturer = 10;
|
||||
@ -659,22 +711,23 @@ bool PCXHandler::write(const QImage &image)
|
||||
header.YMin = 0;
|
||||
header.XMax = w - 1;
|
||||
header.YMax = h - 1;
|
||||
header.HDpi = 300;
|
||||
header.YDpi = 300;
|
||||
header.HDpi = qRound(image.dotsPerMeterX() * 25.4 / 1000);
|
||||
header.YDpi = qRound(image.dotsPerMeterY() * 25.4 / 1000);
|
||||
header.Reserved = 0;
|
||||
header.PaletteInfo = 1;
|
||||
|
||||
auto ok = false;
|
||||
if (img.depth() == 1) {
|
||||
writeImage1(img, s, header);
|
||||
ok = writeImage1(img, s, header);
|
||||
} else if (img.depth() == 8 && img.colorCount() <= 16) {
|
||||
writeImage4(img, s, header);
|
||||
ok = writeImage4(img, s, header);
|
||||
} else if (img.depth() == 8) {
|
||||
writeImage8(img, s, header);
|
||||
} else if (img.depth() == 32) {
|
||||
writeImage24(img, s, header);
|
||||
ok = writeImage8(img, s, header);
|
||||
} else if (img.depth() >= 24) {
|
||||
ok = writeImage24(img, s, header);
|
||||
}
|
||||
|
||||
return true;
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool PCXHandler::canRead(QIODevice *device)
|
||||
@ -684,12 +737,6 @@ bool PCXHandler::canRead(QIODevice *device)
|
||||
return false;
|
||||
}
|
||||
|
||||
// We do not support sequential images
|
||||
// We need to know the current position to properly read the header
|
||||
if (device->isSequential()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
qint64 oldPos = device->pos();
|
||||
|
||||
char head[1];
|
||||
|
@ -3,14 +3,14 @@
|
||||
|
||||
SPDX-FileCopyrightText: 2003 Ignacio Castaño <castano@ludicon.com>
|
||||
SPDX-FileCopyrightText: 2015 Alex Merry <alex.merry@kde.org>
|
||||
SPDX-FileCopyrightText: 2022 Mirco Miranda <mircomir@outlook.com>
|
||||
SPDX-FileCopyrightText: 2022-2023 Mirco Miranda <mircomir@outlook.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
/*
|
||||
* This code is based on Thacher Ulrich PSD loading code released
|
||||
* into the public domain. See: http://tulrich.com/geekstuff/
|
||||
* The early version of this code was based on Thacher Ulrich PSD loading code
|
||||
* released into the public domain. See: http://tulrich.com/geekstuff/
|
||||
*/
|
||||
|
||||
/*
|
||||
@ -21,7 +21,6 @@
|
||||
/*
|
||||
* Limitations of the current code:
|
||||
* - 32-bit float image are converted to 16-bit integer image.
|
||||
* NOTE: Qt 6.2 allow 32-bit float images (RGB only)
|
||||
* - Other color spaces cannot directly be read due to lack of QImage support for
|
||||
* color spaces other than RGB (and Grayscale). Where possible, a conversion
|
||||
* to RGB is done:
|
||||
@ -33,6 +32,7 @@
|
||||
* color management engine (e.g. LittleCMS).
|
||||
*/
|
||||
|
||||
#include "fastmath_p.h"
|
||||
#include "psd_p.h"
|
||||
#include "util_p.h"
|
||||
|
||||
@ -51,7 +51,7 @@ typedef quint8 uchar;
|
||||
* This should not be a problem because the Qt's QColorSpace supports the linear
|
||||
* sRgb colorspace.
|
||||
*
|
||||
* Using linear conversion, the loading speed is improved by 4x. Anyway, if you are using
|
||||
* 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
|
||||
@ -172,7 +172,8 @@ struct PSDLayerAndMaskSection {
|
||||
if (globalLayerMaskInfo.size > -1) {
|
||||
currentSize += globalLayerMaskInfo.size + 4;
|
||||
}
|
||||
for (auto && v : additionalLayerInfo.values()) {
|
||||
auto aliv = additionalLayerInfo.values();
|
||||
for (auto &&v : aliv) {
|
||||
currentSize += (12 + v.size);
|
||||
if (v.signature == S_8B64)
|
||||
currentSize += 4;
|
||||
@ -286,12 +287,6 @@ static PSDImageResourceSection readImageResourceSection(QDataStream &s, bool *ok
|
||||
qint32 sectioSize;
|
||||
s >> sectioSize;
|
||||
|
||||
#ifdef QT_DEBUG
|
||||
auto pos = qint64();
|
||||
if (auto dev = s.device())
|
||||
pos = dev->pos();
|
||||
#endif
|
||||
|
||||
// Reading Image resource block
|
||||
for (auto size = sectioSize; size > 0;) {
|
||||
// Length Description
|
||||
@ -356,14 +351,6 @@ static PSDImageResourceSection readImageResourceSection(QDataStream &s, bool *ok
|
||||
irs.insert(id, irb);
|
||||
}
|
||||
|
||||
#ifdef QT_DEBUG
|
||||
if (auto dev = s.device()) {
|
||||
if ((dev->pos() - pos) != sectioSize) {
|
||||
*ok = false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return irs;
|
||||
}
|
||||
|
||||
@ -659,6 +646,9 @@ static bool IsValid(const PSDHeader &header)
|
||||
// Check that the header is supported by this plugin.
|
||||
static bool IsSupported(const PSDHeader &header)
|
||||
{
|
||||
if (!IsValid(header)) {
|
||||
return false;
|
||||
}
|
||||
if (header.version != 1 && header.version != 2) {
|
||||
return false;
|
||||
}
|
||||
@ -673,10 +663,15 @@ static bool IsSupported(const PSDHeader &header)
|
||||
header.color_mode != CM_INDEXED &&
|
||||
header.color_mode != CM_DUOTONE &&
|
||||
header.color_mode != CM_CMYK &&
|
||||
header.color_mode != CM_MULTICHANNEL &&
|
||||
header.color_mode != CM_LABCOLOR &&
|
||||
header.color_mode != CM_BITMAP) {
|
||||
return false;
|
||||
}
|
||||
if (header.color_mode == CM_MULTICHANNEL &&
|
||||
header.channel_count < 4) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -738,11 +733,12 @@ static QImage::Format imageFormat(const PSDHeader &header, bool alpha)
|
||||
switch(header.color_mode) {
|
||||
case CM_RGB:
|
||||
if (header.depth == 16 || header.depth == 32)
|
||||
format = header.channel_count < 4 || !alpha ? QImage::Format_RGBX64 : QImage::Format_RGBA64;
|
||||
format = header.channel_count < 4 || !alpha ? QImage::Format_RGBX64 : QImage::Format_RGBA64_Premultiplied;
|
||||
else
|
||||
format = header.channel_count < 4 || !alpha ? QImage::Format_RGB888 : QImage::Format_RGBA8888;
|
||||
format = header.channel_count < 4 || !alpha ? QImage::Format_RGB888 : QImage::Format_RGBA8888_Premultiplied;
|
||||
break;
|
||||
case CM_CMYK: // Photoshop supports CMYK 8-bits and 16-bits only
|
||||
case CM_MULTICHANNEL: // Treat MCH as CMYK (number of channel check is done in IsSupported())
|
||||
case CM_CMYK: // Photoshop supports CMYK/MCH 8-bits and 16-bits only
|
||||
if (header.depth == 16)
|
||||
format = header.channel_count < 5 || !alpha ? QImage::Format_RGBX64 : QImage::Format_RGBA64;
|
||||
else if (header.depth == 8)
|
||||
@ -818,7 +814,7 @@ inline void planarToChunchy(uchar *target, const char *source, qint32 width, qin
|
||||
auto s = reinterpret_cast<const T*>(source);
|
||||
auto t = reinterpret_cast<T*>(target);
|
||||
for (qint32 x = 0; x < width; ++x) {
|
||||
t[x*cn+c] = xchg(s[x]);
|
||||
t[x * cn + c] = xchg(s[x]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -830,7 +826,44 @@ inline void planarToChunchyFloat(uchar *target, const char *source, qint32 width
|
||||
for (qint32 x = 0; x < width; ++x) {
|
||||
auto tmp = xchg(s[x]);
|
||||
auto ftmp = (*reinterpret_cast<float*>(&tmp) - double(min)) / (double(max) - double(min));
|
||||
t[x*cn+c] = quint16(std::min(ftmp * std::numeric_limits<quint16>::max() + 0.5, double(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -843,12 +876,25 @@ inline void monoInvert(uchar *target, const char* source, qint32 bytes)
|
||||
}
|
||||
}
|
||||
|
||||
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!";
|
||||
@ -857,10 +903,10 @@ inline void cmykToRgb(uchar *target, qint32 targetChannels, const char *source,
|
||||
|
||||
for (qint32 w = 0; w < width; ++w) {
|
||||
auto ps = s + sourceChannels * w;
|
||||
auto C = 1 - *(ps + 0) / max;
|
||||
auto M = 1 - *(ps + 1) / max;
|
||||
auto Y = 1 - *(ps + 2) / max;
|
||||
auto K = 1 - *(ps + 3) / max;
|
||||
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));
|
||||
@ -885,8 +931,9 @@ inline double gammaCorrection(double linear)
|
||||
#ifdef PSD_FAST_LAB_CONVERSION
|
||||
return linear;
|
||||
#else
|
||||
// NOTE: pow() slow down the performance by a 4 factor :(
|
||||
return (linear > 0.0031308 ? 1.055 * std::pow(linear, 1.0 / 2.4) - 0.055 : 12.92 * linear);
|
||||
// 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
|
||||
}
|
||||
|
||||
@ -896,6 +943,7 @@ inline void labToRgb(uchar *target, qint32 targetChannels, const char *source, q
|
||||
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!";
|
||||
@ -904,14 +952,14 @@ inline void labToRgb(uchar *target, qint32 targetChannels, const char *source, q
|
||||
|
||||
for (qint32 w = 0; w < width; ++w) {
|
||||
auto ps = s + sourceChannels * w;
|
||||
auto L = (*(ps + 0) / max) * 100.0;
|
||||
auto A = (*(ps + 1) / max) * 255.0 - 128.0;
|
||||
auto B = (*(ps + 2) / max) * 255.0 - 128.0;
|
||||
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) / 116.0;
|
||||
auto X = A / 500.0 + Y;
|
||||
auto Z = Y - B / 200.0;
|
||||
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
|
||||
@ -1061,7 +1109,15 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
|
||||
QByteArray rawStride;
|
||||
rawStride.resize(raw_count);
|
||||
|
||||
if (header.color_mode == CM_CMYK || header.color_mode == CM_LABCOLOR || header.color_mode == CM_MULTICHANNEL) {
|
||||
// clang-format off
|
||||
// checks the need of color conversion (that requires random access to the image)
|
||||
auto randomAccess = (header.color_mode == CM_CMYK) ||
|
||||
(header.color_mode == CM_LABCOLOR) ||
|
||||
(header.color_mode == CM_MULTICHANNEL) ||
|
||||
(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);
|
||||
@ -1081,31 +1137,56 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
|
||||
auto scanLine = reinterpret_cast<unsigned char*>(psdScanline.data());
|
||||
if (header.depth == 8) {
|
||||
planarToChunchy<quint8>(scanLine, rawStride.data(), header.width, c, header.channel_count);
|
||||
}
|
||||
else if (header.depth == 16) {
|
||||
} else if (header.depth == 16) {
|
||||
planarToChunchy<quint16>(scanLine, rawStride.data(), header.width, c, header.channel_count);
|
||||
}
|
||||
else if (header.depth == 32) { // Not currently used
|
||||
} else if (header.depth == 32) {
|
||||
planarToChunchyFloat<quint32>(scanLine, rawStride.data(), header.width, c, header.channel_count);
|
||||
}
|
||||
}
|
||||
|
||||
// Conversion to RGB
|
||||
// Convert premultiplied data to unassociated data
|
||||
if (img.hasAlphaChannel()) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// Conversion to RGB
|
||||
if (header.color_mode == CM_CMYK || header.color_mode == CM_MULTICHANNEL) {
|
||||
if (header.depth == 8)
|
||||
cmykToRgb<quint8>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha);
|
||||
else
|
||||
else if (header.depth == 16)
|
||||
cmykToRgb<quint16>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha);
|
||||
}
|
||||
if (header.color_mode == CM_LABCOLOR) {
|
||||
if (header.depth == 8)
|
||||
labToRgb<quint8>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha);
|
||||
else
|
||||
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 {
|
||||
} 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) {
|
||||
@ -1118,14 +1199,11 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
|
||||
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
|
||||
} 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
|
||||
} 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)
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
@ -1250,35 +1328,30 @@ bool PSDHandler::canRead(QIODevice *device)
|
||||
return false;
|
||||
}
|
||||
|
||||
qint64 oldPos = device->pos();
|
||||
device->startTransaction();
|
||||
|
||||
char head[4];
|
||||
qint64 readBytes = device->read(head, sizeof(head));
|
||||
if (readBytes < 0) {
|
||||
qWarning() << "Read failed" << device->errorString();
|
||||
return false;
|
||||
}
|
||||
QDataStream s(device);
|
||||
s.setByteOrder(QDataStream::BigEndian);
|
||||
|
||||
if (readBytes != sizeof(head)) {
|
||||
if (device->isSequential()) {
|
||||
while (readBytes > 0) {
|
||||
device->ungetChar(head[readBytes-- - 1]);
|
||||
}
|
||||
} else {
|
||||
device->seek(oldPos);
|
||||
}
|
||||
PSDHeader header;
|
||||
s >> header;
|
||||
|
||||
device->rollbackTransaction();
|
||||
|
||||
if (s.status() != QDataStream::Ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (device->isSequential()) {
|
||||
while (readBytes > 0) {
|
||||
device->ungetChar(head[readBytes-- - 1]);
|
||||
if (header.color_mode == CM_CMYK || header.color_mode == CM_LABCOLOR || header.color_mode == CM_MULTICHANNEL) {
|
||||
return false;
|
||||
}
|
||||
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
|
||||
|
@ -19,11 +19,7 @@
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_WINDOWS)
|
||||
#include <libraw.h>
|
||||
#else
|
||||
#include <libraw/libraw.h>
|
||||
#endif
|
||||
|
||||
#ifdef QT_DEBUG
|
||||
// This should be used to exclude the local QIODevice wrapper of the LibRaw_abstract_datastream interface.
|
||||
@ -49,7 +45,6 @@ const auto supported_formats = QSet<QByteArray>{
|
||||
"dcs", "dc2", "dcr", "dng", "drf", "dxo",
|
||||
"eip", "erf",
|
||||
"fff",
|
||||
"hdr",
|
||||
"iiq",
|
||||
"k25", "kc2", "kdc",
|
||||
"mdc", "mef", "mfw", "mos", "mrw",
|
||||
@ -115,12 +110,19 @@ public:
|
||||
}
|
||||
virtual int read(void *ptr, size_t sz, size_t nmemb) override
|
||||
{
|
||||
auto read = m_device->read(reinterpret_cast<char *>(ptr), sz * nmemb);
|
||||
if (read < 1) {
|
||||
qint64 read = 0;
|
||||
if (sz == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (auto o = read % sz) {
|
||||
seek(-(sz - o), SEEK_CUR);
|
||||
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;
|
||||
}
|
||||
@ -138,7 +140,7 @@ public:
|
||||
if (whence == SEEK_END) {
|
||||
pos = size + o;
|
||||
}
|
||||
if (pos < 0 || pos > size || m_device->isSequential()) {
|
||||
if (pos < 0 || m_device->isSequential()) {
|
||||
return -1;
|
||||
}
|
||||
return m_device->seek(pos) ? 0 : -1;
|
||||
@ -673,6 +675,9 @@ bool LoadRAW(QImageIOHandler *handler, QImage &img)
|
||||
if (params.output_color == 4) {
|
||||
img.setColorSpace(QColorSpace(QColorSpace::ProPhotoRgb));
|
||||
}
|
||||
if (params.output_color == 7) {
|
||||
img.setColorSpace(QColorSpace(QColorSpace::DisplayP3));
|
||||
}
|
||||
}
|
||||
|
||||
// *** Set the metadata
|
||||
@ -855,6 +860,14 @@ int RAWHandler::currentImageNumber() const
|
||||
|
||||
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);
|
||||
|
@ -7,7 +7,6 @@
|
||||
"dcs", "dc2", "dcr", "dng", "drf", "dxo",
|
||||
"eip", "erf",
|
||||
"fff",
|
||||
"hdr",
|
||||
"iiq",
|
||||
"k25", "kdc", "kc2",
|
||||
"mdc", "mef", "mfw", "mos", "mrw",
|
||||
@ -27,7 +26,6 @@
|
||||
"image/x-kodak-dcs", "image/x-dc2", "image/x-kodak-dcr", "image/x-adobe-dng", "image/x-drf", "image/x-dxo",
|
||||
"image/x-epson-eip", "image/x-epson-erf",
|
||||
"image/x-fff",
|
||||
"image/x-hdr",
|
||||
"image/x-iiq",
|
||||
"image/x-kodak-k25", "image/x-kodak-kdc", "image/x-kodak-kc2",
|
||||
"image/x-minolta-mdc", "image/x-mamiya-mef", "image/x-mfw", "image/x-aptus-mos", "image/x-minolta-mrw",
|
||||
|
@ -672,11 +672,16 @@ bool SGIImage::writeImage(const QImage &image)
|
||||
_dim = 3, _zsize = 3;
|
||||
}
|
||||
|
||||
if (img.format() == QImage::Format_ARGB32) {
|
||||
auto hasAlpha = img.hasAlphaChannel();
|
||||
if (hasAlpha) {
|
||||
_dim = 3, _zsize++;
|
||||
}
|
||||
|
||||
if (hasAlpha && img.format() != QImage::Format_ARGB32) {
|
||||
img = img.convertToFormat(QImage::Format_ARGB32);
|
||||
} else if (!hasAlpha && img.format() != QImage::Format_RGB32) {
|
||||
img = img.convertToFormat(QImage::Format_RGB32);
|
||||
}
|
||||
if (img.isNull()) {
|
||||
// qDebug() << "can't convert image to depth 32";
|
||||
return false;
|
||||
@ -685,7 +690,7 @@ bool SGIImage::writeImage(const QImage &image)
|
||||
const int w = img.width();
|
||||
const int h = img.height();
|
||||
|
||||
if (w > 65536 || h > 65536) {
|
||||
if (w > 65535 || h > 65535) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -712,12 +717,6 @@ bool SGIImage::writeImage(const QImage &image)
|
||||
rle_size += _rlevector[i]->size();
|
||||
}
|
||||
|
||||
// qDebug() << "minimum intensity: " << _pixmin;
|
||||
// qDebug() << "maximum intensity: " << _pixmax;
|
||||
// qDebug() << "saved scanlines: " << _numrows - _rlemap.size();
|
||||
// qDebug() << "total savings: " << (verbatim_size - rle_size) << " bytes";
|
||||
// qDebug() << "compression: " << (rle_size * 100.0 / verbatim_size) << '%';
|
||||
|
||||
if (verbatim_size <= rle_size) {
|
||||
writeVerbatim(img);
|
||||
} else {
|
||||
|
@ -428,8 +428,20 @@ bool TGAHandler::write(const QImage &image)
|
||||
QDataStream s(device());
|
||||
s.setByteOrder(QDataStream::LittleEndian);
|
||||
|
||||
const QImage &img = image;
|
||||
const bool hasAlpha = (img.format() == QImage::Format_ARGB32);
|
||||
QImage img(image);
|
||||
const bool hasAlpha = img.hasAlphaChannel();
|
||||
if (hasAlpha && img.format() != QImage::Format_ARGB32) {
|
||||
img = img.convertToFormat(QImage::Format_ARGB32);
|
||||
} else if (!hasAlpha && img.format() != QImage::Format_RGB32) {
|
||||
img = img.convertToFormat(QImage::Format_RGB32);
|
||||
}
|
||||
if (img.isNull()) {
|
||||
qDebug() << "TGAHandler::write: image conversion to 32 bits failed!";
|
||||
return false;
|
||||
}
|
||||
static constexpr quint8 originTopLeft = TGA_ORIGIN_UPPER + TGA_ORIGIN_LEFT; // 0x20
|
||||
static constexpr quint8 alphaChannel8Bits = 0x08;
|
||||
|
||||
for (int i = 0; i < 12; i++) {
|
||||
s << targaMagic[i];
|
||||
}
|
||||
@ -438,11 +450,12 @@ bool TGAHandler::write(const QImage &image)
|
||||
s << quint16(img.width()); // width
|
||||
s << quint16(img.height()); // height
|
||||
s << quint8(hasAlpha ? 32 : 24); // depth (24 bit RGB + 8 bit alpha)
|
||||
s << quint8(hasAlpha ? 0x24 : 0x20); // top left image (0x20) + 8 bit alpha (0x4)
|
||||
s << quint8(hasAlpha ? originTopLeft + alphaChannel8Bits : originTopLeft); // top left image (0x20) + 8 bit alpha (0x8)
|
||||
|
||||
for (int y = 0; y < img.height(); y++) {
|
||||
auto ptr = reinterpret_cast<QRgb *>(img.scanLine(y));
|
||||
for (int x = 0; x < img.width(); x++) {
|
||||
const QRgb color = img.pixel(x, y);
|
||||
auto color = *(ptr + x);
|
||||
s << quint8(qBlue(color));
|
||||
s << quint8(qGreen(color));
|
||||
s << quint8(qRed(color));
|
||||
|
@ -3341,6 +3341,9 @@ bool XCFHandler::canRead(QIODevice *device)
|
||||
qCDebug(XCFPLUGIN) << "XCFHandler::canRead() called with no device";
|
||||
return false;
|
||||
}
|
||||
if (device->isSequential()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
qint64 oldPos = device->pos();
|
||||
|
||||
|