Compare commits
1 Commits
v5.99.0-rc
...
work/rempt
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b89c2d1b70 |
1
.gitattributes
vendored
@@ -1 +0,0 @@
|
||||
autotests/read/raw/RAW_KODAK_C330_FORMAT_NONE_YRGB.raw binary
|
||||
@@ -3,12 +3,5 @@
|
||||
|
||||
include:
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux-static.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/android.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/freebsd.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux-qt6.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/android-qt6.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/windows.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/windows-static.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/freebsd-qt6.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/windows-qt6.yml
|
||||
|
||||
@@ -6,4 +6,3 @@ Dependencies:
|
||||
|
||||
Options:
|
||||
test-before-installing: True
|
||||
require-passing-tests-on: [ 'Linux', 'FreeBSD', 'Windows' ]
|
||||
|
||||
@@ -3,19 +3,19 @@ cmake_minimum_required(VERSION 3.16)
|
||||
project(KImageFormats)
|
||||
|
||||
include(FeatureSummary)
|
||||
find_package(ECM 5.99.0 NO_MODULE)
|
||||
find_package(ECM 5.90.0 NO_MODULE)
|
||||
set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://commits.kde.org/extra-cmake-modules")
|
||||
feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES)
|
||||
|
||||
|
||||
set(CMAKE_MODULE_PATH ${KImageFormats_SOURCE_DIR}/cmake/find-modules ${ECM_MODULE_PATH})
|
||||
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH})
|
||||
|
||||
include(KDEInstallDirs)
|
||||
include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE)
|
||||
include(KDECMakeSettings)
|
||||
|
||||
include(KDEGitCommitHooks)
|
||||
include(ECMDeprecationSettings)
|
||||
|
||||
|
||||
include(CheckIncludeFiles)
|
||||
include(FindPkgConfig)
|
||||
|
||||
@@ -70,18 +70,8 @@ if(KIMAGEFORMATS_JXL)
|
||||
endif()
|
||||
add_feature_info(LibJXL LibJXL_FOUND "required for the QImage plugin for JPEG XL images")
|
||||
|
||||
# note: module FindLibRaw missing from https://invent.kde.org/frameworks/extra-cmake-modules
|
||||
find_package(LibRaw 0.20.2)
|
||||
set_package_properties(LibRaw PROPERTIES
|
||||
TYPE OPTIONAL
|
||||
PURPOSE "Required for the QImage plugin for RAW images"
|
||||
)
|
||||
|
||||
ecm_set_disabled_deprecation_versions(
|
||||
QT 5.15.2
|
||||
KF 5.95
|
||||
)
|
||||
|
||||
add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x050f02)
|
||||
add_definitions(-DKF_DISABLE_DEPRECATED_BEFORE_AND_AT=0x055900)
|
||||
add_subdirectory(src)
|
||||
if (BUILD_TESTING)
|
||||
add_subdirectory(autotests)
|
||||
|
||||
@@ -16,9 +16,8 @@ The following image formats have read-only support:
|
||||
- Animated Windows cursors (ani)
|
||||
- Gimp (xcf)
|
||||
- OpenEXR (exr)
|
||||
- Photoshop documents (psd, psb, pdd, psdt)
|
||||
- Photoshop documents (psd)
|
||||
- Sun Raster (ras)
|
||||
- Camera RAW images (arw, cr2, cr3, dcs, dng, ...)
|
||||
|
||||
The following image formats have read and write support:
|
||||
|
||||
|
||||
@@ -30,12 +30,6 @@ macro(kimageformats_read_tests)
|
||||
endmacro()
|
||||
|
||||
macro(kimageformats_write_tests)
|
||||
cmake_parse_arguments(KIF_RT "" "FUZZ" "" ${ARGN})
|
||||
set(_fuzzarg)
|
||||
if (KIF_RT_FUZZ)
|
||||
set(_fuzzarg -f ${KIF_RT_FUZZ})
|
||||
endif()
|
||||
|
||||
if (NOT TARGET writetest)
|
||||
add_executable(writetest writetest.cpp)
|
||||
target_link_libraries(writetest Qt${QT_MAJOR_VERSION}::Gui)
|
||||
@@ -43,22 +37,16 @@ macro(kimageformats_write_tests)
|
||||
PRIVATE IMAGEDIR="${CMAKE_CURRENT_SOURCE_DIR}/write")
|
||||
ecm_mark_as_test(writetest)
|
||||
endif()
|
||||
foreach(_testname ${KIF_RT_UNPARSED_ARGUMENTS})
|
||||
foreach(_testname ${ARGN})
|
||||
string(REGEX MATCH "-lossless$" _is_lossless "${_testname}")
|
||||
string(REGEX MATCH "-nodatacheck" _is_no_data_check "${_testname}")
|
||||
unset(lossless_arg)
|
||||
unset(no_data_check_arg)
|
||||
if (_is_lossless)
|
||||
set(lossless_arg "--lossless")
|
||||
string(REGEX REPLACE "-lossless$" "" _testname "${_testname}")
|
||||
endif()
|
||||
if (_is_no_data_check)
|
||||
set(no_data_check_arg "--no-data-check")
|
||||
string(REGEX REPLACE "-nodatacheck$" "" _testname "${_testname}")
|
||||
endif()
|
||||
add_test(
|
||||
NAME kimageformats-write-${_testname}
|
||||
COMMAND writetest ${lossless_arg} ${no_data_check_arg} ${_fuzzarg} ${_testname}
|
||||
COMMAND writetest ${lossless_arg} ${_testname}
|
||||
)
|
||||
endforeach(_testname)
|
||||
endmacro()
|
||||
@@ -86,28 +74,18 @@ if (TARGET avif)
|
||||
kimageformats_read_tests(
|
||||
avif
|
||||
)
|
||||
kimageformats_write_tests(
|
||||
avif-nodatacheck-lossless
|
||||
)
|
||||
endif()
|
||||
|
||||
if (LibHeif_FOUND)
|
||||
kimageformats_read_tests(
|
||||
heif
|
||||
)
|
||||
# because the plug-ins use RGB->YUV conversion which sometimes results in 1 value difference.
|
||||
kimageformats_write_tests(FUZZ 1
|
||||
heif-nodatacheck-lossless
|
||||
)
|
||||
endif()
|
||||
|
||||
if (LibJXL_FOUND AND LibJXLThreads_FOUND)
|
||||
kimageformats_read_tests(
|
||||
jxl
|
||||
)
|
||||
kimageformats_write_tests(
|
||||
jxl-nodatacheck-lossless
|
||||
)
|
||||
endif()
|
||||
|
||||
# Allow some fuzziness when reading this formats, to allow for
|
||||
@@ -139,12 +117,6 @@ if (OpenEXR_FOUND)
|
||||
# FIXME: OpenEXR tests
|
||||
endif()
|
||||
|
||||
if (LibRaw_FOUND)
|
||||
kimageformats_read_tests(
|
||||
raw
|
||||
)
|
||||
endif()
|
||||
|
||||
find_package(Qt${QT_MAJOR_VERSION}Test ${REQUIRED_QT_VERSION} CONFIG QUIET)
|
||||
|
||||
if(NOT TARGET Qt${QT_MAJOR_VERSION}::Test)
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2014 Alex Merry <alex.merry@kdemail.net>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||
*/
|
||||
|
||||
template<class Trait>
|
||||
static bool fuzzyeq(const QImage &im1, const QImage &im2, uchar fuzziness)
|
||||
{
|
||||
Q_ASSERT(im1.format() == im2.format());
|
||||
Q_ASSERT(im1.depth() == 24 || im1.depth() == 32 || im1.depth() == 64);
|
||||
|
||||
const int height = im1.height();
|
||||
const int width = im1.width();
|
||||
for (int i = 0; i < height; ++i) {
|
||||
const Trait *line1 = reinterpret_cast<const Trait *>(im1.scanLine(i));
|
||||
const Trait *line2 = reinterpret_cast<const Trait *>(im2.scanLine(i));
|
||||
for (int j = 0; j < width; ++j) {
|
||||
if (line1[j] > line2[j]) {
|
||||
if (line1[j] - line2[j] > fuzziness) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (line2[j] - line1[j] > fuzziness) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// allow each byte to be different by up to 1, to allow for rounding errors
|
||||
static bool fuzzyeq(const QImage &im1, const QImage &im2, uchar fuzziness)
|
||||
{
|
||||
return (im1.depth() == 64) ? fuzzyeq<quint16>(im1, im2, fuzziness) : fuzzyeq<quint8>(im1, im2, fuzziness);
|
||||
}
|
||||
|
Before Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 298 KiB |
|
Before Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 9.9 KiB |
|
Before Width: | Height: | Size: 114 KiB |
|
Before Width: | Height: | Size: 112 KiB |
|
Before Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 189 KiB |
|
Before Width: | Height: | Size: 117 KiB |
|
Before Width: | Height: | Size: 5.2 MiB |
|
Before Width: | Height: | Size: 115 KiB |
@@ -16,8 +16,6 @@
|
||||
|
||||
#include "../tests/format-enum.h"
|
||||
|
||||
#include "fuzzyeq.cpp"
|
||||
|
||||
static void writeImageData(const char *name, const QString &filename, const QImage &image)
|
||||
{
|
||||
QFile file(filename);
|
||||
@@ -33,6 +31,38 @@ static void writeImageData(const char *name, const QString &filename, const QIma
|
||||
}
|
||||
}
|
||||
|
||||
template<class Trait>
|
||||
static bool fuzzyeq(const QImage &im1, const QImage &im2, uchar fuzziness)
|
||||
{
|
||||
Q_ASSERT(im1.format() == im2.format());
|
||||
Q_ASSERT(im1.depth() == 24 || im1.depth() == 32 || im1.depth() == 64);
|
||||
|
||||
const int height = im1.height();
|
||||
const int width = im1.width();
|
||||
for (int i = 0; i < height; ++i) {
|
||||
const Trait *line1 = reinterpret_cast<const Trait *>(im1.scanLine(i));
|
||||
const Trait *line2 = reinterpret_cast<const Trait *>(im2.scanLine(i));
|
||||
for (int j = 0; j < width; ++j) {
|
||||
if (line1[j] > line2[j]) {
|
||||
if (line1[j] - line2[j] > fuzziness) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (line2[j] - line1[j] > fuzziness) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// allow each byte to be different by up to 1, to allow for rounding errors
|
||||
static bool fuzzyeq(const QImage &im1, const QImage &im2, uchar fuzziness)
|
||||
{
|
||||
return (im1.depth() == 64) ? fuzzyeq<quint16>(im1, im2, fuzziness) : fuzzyeq<quint8>(im1, im2, fuzziness);
|
||||
}
|
||||
|
||||
// Returns the original format if we support, or returns
|
||||
// format which we preferred to use for `fuzzyeq()`.
|
||||
// We do only support formats with 8-bits/16-bits pre pixel.
|
||||
@@ -94,6 +124,7 @@ int main(int argc, char **argv)
|
||||
QByteArray format = suffix.toLatin1();
|
||||
|
||||
QDir imgdir(QLatin1String(IMAGEDIR "/") + suffix);
|
||||
imgdir.setNameFilters(QStringList(QLatin1String("*.") + suffix));
|
||||
imgdir.setFilter(QDir::Files);
|
||||
|
||||
int passed = 0;
|
||||
@@ -112,9 +143,6 @@ int main(int argc, char **argv)
|
||||
|
||||
const QFileInfoList lstImgDir = imgdir.entryInfoList();
|
||||
for (const QFileInfo &fi : lstImgDir) {
|
||||
if (!fi.suffix().compare("png", Qt::CaseInsensitive)) {
|
||||
continue;
|
||||
}
|
||||
int suffixPos = fi.filePath().count() - suffix.count();
|
||||
QString inputfile = fi.filePath();
|
||||
QString expfile = fi.filePath().replace(suffixPos, suffix.count(), QStringLiteral("png"));
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
#include <QImageWriter>
|
||||
#include <QTextStream>
|
||||
|
||||
#include "fuzzyeq.cpp"
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
QCoreApplication app(argc, argv);
|
||||
@@ -33,13 +31,7 @@ int main(int argc, char **argv)
|
||||
parser.addPositionalArgument(QStringLiteral("format"), QStringLiteral("format to test."));
|
||||
QCommandLineOption lossless(QStringList() << QStringLiteral("l") << QStringLiteral("lossless"),
|
||||
QStringLiteral("Check that reading back the data gives the same image."));
|
||||
QCommandLineOption ignoreDataCheck({QStringLiteral("no-data-check")}, QStringLiteral("Don't check that write data is exactly the same."));
|
||||
QCommandLineOption fuzz(QStringList() << QStringLiteral("f") << QStringLiteral("fuzz"),
|
||||
QStringLiteral("Allow for some deviation in ARGB data."),
|
||||
QStringLiteral("max"));
|
||||
parser.addOption(lossless);
|
||||
parser.addOption(ignoreDataCheck);
|
||||
parser.addOption(fuzz);
|
||||
|
||||
parser.process(app);
|
||||
|
||||
@@ -52,26 +44,11 @@ int main(int argc, char **argv)
|
||||
parser.showHelp(1);
|
||||
}
|
||||
|
||||
uchar fuzziness = 0;
|
||||
if (parser.isSet(fuzz)) {
|
||||
bool ok;
|
||||
uint fuzzarg = parser.value(fuzz).toUInt(&ok);
|
||||
if (!ok || fuzzarg > 255) {
|
||||
QTextStream(stderr) << "Error: max fuzz argument must be a number between 0 and 255\n";
|
||||
parser.showHelp(1);
|
||||
}
|
||||
fuzziness = uchar(fuzzarg);
|
||||
}
|
||||
|
||||
QString suffix = args.at(0);
|
||||
QByteArray format = suffix.toLatin1();
|
||||
|
||||
QDir imgdir(QStringLiteral(IMAGEDIR));
|
||||
if (parser.isSet(ignoreDataCheck)) {
|
||||
imgdir.setNameFilters({QLatin1String("*.png")});
|
||||
} else {
|
||||
imgdir.setNameFilters(QStringList(QLatin1String("*.") + suffix));
|
||||
}
|
||||
imgdir.setNameFilters(QStringList(QLatin1String("*.") + suffix));
|
||||
imgdir.setFilter(QDir::Files);
|
||||
|
||||
int passed = 0;
|
||||
@@ -81,13 +58,8 @@ int main(int argc, char **argv)
|
||||
<< "Starting basic write tests for " << suffix << " images *********\n";
|
||||
const QFileInfoList lstImgDir = imgdir.entryInfoList();
|
||||
for (const QFileInfo &fi : lstImgDir) {
|
||||
QString pngfile;
|
||||
if (parser.isSet(ignoreDataCheck)) {
|
||||
pngfile = fi.filePath();
|
||||
} else {
|
||||
int suffixPos = fi.filePath().count() - suffix.count();
|
||||
pngfile = fi.filePath().replace(suffixPos, suffix.count(), QStringLiteral("png"));
|
||||
}
|
||||
int suffixPos = fi.filePath().count() - suffix.count();
|
||||
QString pngfile = fi.filePath().replace(suffixPos, suffix.count(), QStringLiteral("png"));
|
||||
QString pngfilename = QFileInfo(pngfile).fileName();
|
||||
|
||||
QImageReader pngReader(pngfile, "png");
|
||||
@@ -98,13 +70,29 @@ int main(int argc, char **argv)
|
||||
continue;
|
||||
}
|
||||
|
||||
QFile expFile(fi.filePath());
|
||||
if (!expFile.open(QIODevice::ReadOnly)) {
|
||||
QTextStream(stdout) << "ERROR: " << fi.fileName() << ": could not open " << fi.fileName() << ": " << expFile.errorString() << "\n";
|
||||
++failed;
|
||||
continue;
|
||||
}
|
||||
QByteArray expData = expFile.readAll();
|
||||
if (expData.isEmpty()) {
|
||||
// check if there was actually anything to read
|
||||
expFile.reset();
|
||||
char buf[1];
|
||||
qint64 result = expFile.read(buf, 1);
|
||||
if (result < 0) {
|
||||
QTextStream(stdout) << "ERROR: " << fi.fileName() << ": could not load " << fi.fileName() << ": " << expFile.errorString() << "\n";
|
||||
++failed;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray writtenData;
|
||||
{
|
||||
QBuffer buffer(&writtenData);
|
||||
QImageWriter imgWriter(&buffer, format.constData());
|
||||
if (parser.isSet(lossless)) {
|
||||
imgWriter.setQuality(100);
|
||||
}
|
||||
if (!imgWriter.write(pngImage)) {
|
||||
QTextStream(stdout) << "FAIL : " << fi.fileName() << ": failed to write image data\n";
|
||||
++failed;
|
||||
@@ -112,31 +100,10 @@ int main(int argc, char **argv)
|
||||
}
|
||||
}
|
||||
|
||||
if (!parser.isSet(ignoreDataCheck)) {
|
||||
QFile expFile(fi.filePath());
|
||||
if (!expFile.open(QIODevice::ReadOnly)) {
|
||||
QTextStream(stdout) << "ERROR: " << fi.fileName() << ": could not open " << fi.fileName() << ": " << expFile.errorString() << "\n";
|
||||
++failed;
|
||||
continue;
|
||||
}
|
||||
QByteArray expData = expFile.readAll();
|
||||
if (expData.isEmpty()) {
|
||||
// check if there was actually anything to read
|
||||
expFile.reset();
|
||||
char buf[1];
|
||||
qint64 result = expFile.read(buf, 1);
|
||||
if (result < 0) {
|
||||
QTextStream(stdout) << "ERROR: " << fi.fileName() << ": could not load " << fi.fileName() << ": " << expFile.errorString() << "\n";
|
||||
++failed;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (expData != writtenData) {
|
||||
QTextStream(stdout) << "FAIL : " << fi.fileName() << ": written data differs from " << fi.fileName() << "\n";
|
||||
++failed;
|
||||
continue;
|
||||
}
|
||||
if (expData != writtenData) {
|
||||
QTextStream(stdout) << "FAIL : " << fi.fileName() << ": written data differs from " << fi.fileName() << "\n";
|
||||
++failed;
|
||||
continue;
|
||||
}
|
||||
|
||||
QImage reReadImage;
|
||||
@@ -152,18 +119,8 @@ int main(int argc, char **argv)
|
||||
}
|
||||
|
||||
if (parser.isSet(lossless)) {
|
||||
if (!fuzzyeq(pngImage, reReadImage, fuzziness)) {
|
||||
if (pngImage != reReadImage) {
|
||||
QTextStream(stdout) << "FAIL : " << fi.fileName() << ": re-reading the data resulted in a different image\n";
|
||||
if (pngImage.size() == reReadImage.size()) {
|
||||
for (int i = 0; i < pngImage.width(); ++i) {
|
||||
for (int j = 0; j < pngImage.height(); ++j) {
|
||||
if (pngImage.pixel(i, j) != reReadImage.pixel(i, j)) {
|
||||
QTextStream(stdout) << "Pixel is different " << i << ',' << j << ' ' << pngImage.pixel(i, j) << ' ' << reReadImage.pixel(i, j)
|
||||
<< '\n';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
++failed;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
# - Find LibRaw
|
||||
# Find the LibRaw library <https://www.libraw.org>
|
||||
# This module defines
|
||||
# LibRaw_VERSION, the version string of LibRaw
|
||||
# LibRaw_INCLUDE_DIR, where to find libraw.h
|
||||
# LibRaw_LIBRARIES, the libraries needed to use LibRaw (non-thread-safe)
|
||||
# LibRaw_r_LIBRARIES, the libraries needed to use LibRaw (thread-safe)
|
||||
# LibRaw_DEFINITIONS, the definitions needed to use LibRaw (non-thread-safe)
|
||||
# LibRaw_r_DEFINITIONS, the definitions needed to use LibRaw (thread-safe)
|
||||
#
|
||||
# SPDX-FileCopyrightText: 2013 Pino Toscano <pino at kde dot org>
|
||||
# SPDX-FileCopyrightText: 2013 Gilles Caulier <caulier dot gilles at gmail dot com>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
FIND_PACKAGE(PkgConfig)
|
||||
|
||||
IF(PKG_CONFIG_FOUND)
|
||||
PKG_CHECK_MODULES(PC_LIBRAW libraw)
|
||||
SET(LibRaw_DEFINITIONS ${PC_LIBRAW_CFLAGS_OTHER})
|
||||
|
||||
PKG_CHECK_MODULES(PC_LIBRAW_R libraw_r)
|
||||
SET(LibRaw_r_DEFINITIONS ${PC_LIBRAW_R_CFLAGS_OTHER})
|
||||
ENDIF()
|
||||
|
||||
FIND_PATH(LibRaw_INCLUDE_DIR libraw.h
|
||||
HINTS
|
||||
${PC_LIBRAW_INCLUDEDIR}
|
||||
${PC_LibRaw_INCLUDE_DIRS}
|
||||
PATH_SUFFIXES libraw
|
||||
)
|
||||
|
||||
FIND_LIBRARY(LibRaw_LIBRARIES NAMES raw
|
||||
HINTS
|
||||
${PC_LIBRAW_LIBDIR}
|
||||
${PC_LIBRAW_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
FIND_LIBRARY(LibRaw_r_LIBRARIES NAMES raw_r
|
||||
HINTS
|
||||
${PC_LIBRAW_R_LIBDIR}
|
||||
${PC_LIBRAW_R_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
IF(LibRaw_INCLUDE_DIR)
|
||||
FILE(READ ${LibRaw_INCLUDE_DIR}/libraw_version.h _libraw_version_content)
|
||||
|
||||
STRING(REGEX MATCH "#define LIBRAW_MAJOR_VERSION[ \t]*([0-9]*)\n" _version_major_match ${_libraw_version_content})
|
||||
SET(_libraw_version_major "${CMAKE_MATCH_1}")
|
||||
|
||||
STRING(REGEX MATCH "#define LIBRAW_MINOR_VERSION[ \t]*([0-9]*)\n" _version_minor_match ${_libraw_version_content})
|
||||
SET(_libraw_version_minor "${CMAKE_MATCH_1}")
|
||||
|
||||
STRING(REGEX MATCH "#define LIBRAW_PATCH_VERSION[ \t]*([0-9]*)\n" _version_patch_match ${_libraw_version_content})
|
||||
SET(_libraw_version_patch "${CMAKE_MATCH_1}")
|
||||
|
||||
IF(_version_major_match AND _version_minor_match AND _version_patch_match)
|
||||
SET(LibRaw_VERSION "${_libraw_version_major}.${_libraw_version_minor}.${_libraw_version_patch}")
|
||||
ELSE()
|
||||
IF(NOT LibRaw_FIND_QUIETLY)
|
||||
MESSAGE(STATUS "Failed to get version information from ${LibRaw_INCLUDE_DIR}/libraw_version.h")
|
||||
ENDIF()
|
||||
ENDIF()
|
||||
ENDIF()
|
||||
|
||||
INCLUDE(FindPackageHandleStandardArgs)
|
||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS(LibRaw
|
||||
REQUIRED_VARS LibRaw_LIBRARIES LibRaw_INCLUDE_DIR
|
||||
VERSION_VAR LibRaw_VERSION
|
||||
)
|
||||
|
||||
MARK_AS_ADVANCED(LibRaw_VERSION
|
||||
LibRaw_INCLUDE_DIR
|
||||
LibRaw_LIBRARIES
|
||||
LibRaw_r_LIBRARIES
|
||||
LibRaw_DEFINITIONS
|
||||
LibRaw_r_DEFINITIONS
|
||||
)
|
||||
|
||||
if(LibRaw_FOUND AND NOT TARGET LibRaw::LibRaw)
|
||||
add_library(LibRaw::LibRaw UNKNOWN IMPORTED)
|
||||
set_target_properties(LibRaw::LibRaw PROPERTIES
|
||||
IMPORTED_LOCATION "${LibRaw_LIBRARIES}"
|
||||
INTERFACE_COMPILE_OPTIONS "${LibRaw_DEFINITIONS}"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${LibRaw_INCLUDE_DIR}"
|
||||
)
|
||||
endif()
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
function(kimageformats_add_plugin plugin)
|
||||
set(options)
|
||||
set(oneValueArgs)
|
||||
set(multiValueArgs SOURCES)
|
||||
cmake_parse_arguments(KIF_ADD_PLUGIN "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
|
||||
if(NOT KIF_ADD_PLUGIN_SOURCES)
|
||||
@@ -87,9 +86,6 @@ endif()
|
||||
if (LibJXL_FOUND AND LibJXLThreads_FOUND)
|
||||
kimageformats_add_plugin(kimg_jxl SOURCES jxl.cpp)
|
||||
target_link_libraries(kimg_jxl PkgConfig::LibJXL PkgConfig::LibJXLThreads)
|
||||
if (LibJXL_VERSION VERSION_GREATER_EQUAL "0.7.0")
|
||||
target_compile_definitions(kimg_jxl PRIVATE KIMG_JXL_API_VERSION=70)
|
||||
endif()
|
||||
install(FILES jxl.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||
endif()
|
||||
|
||||
@@ -130,15 +126,6 @@ install(FILES xcf.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugin
|
||||
|
||||
##################################
|
||||
|
||||
if (LibRaw_FOUND)
|
||||
kimageformats_add_plugin(kimg_raw SOURCES raw.cpp)
|
||||
kde_enable_exceptions()
|
||||
target_link_libraries(kimg_raw LibRaw::LibRaw)
|
||||
install(FILES raw.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||
endif()
|
||||
|
||||
##################################
|
||||
|
||||
if (KF5Archive_FOUND)
|
||||
|
||||
kimageformats_add_plugin(kimg_kra SOURCES kra.cpp)
|
||||
|
||||
@@ -12,8 +12,6 @@
|
||||
#include <QVariant>
|
||||
#include <QtEndian>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace
|
||||
{
|
||||
struct ChunkHeader {
|
||||
@@ -421,7 +419,7 @@ bool ANIHandler::ensureScanned() const
|
||||
}
|
||||
|
||||
// FIXME encoding
|
||||
const QString stringValue = QString::fromLocal8Bit(value.constData(), std::strlen(value.constData()));
|
||||
const QString stringValue = QString::fromLocal8Bit(value);
|
||||
if (chunkId == "INAM") {
|
||||
mutableThis->m_name = stringValue;
|
||||
} else if (chunkId == "IART") {
|
||||
|
||||
@@ -12,8 +12,6 @@
|
||||
#include <QColorSpace>
|
||||
|
||||
#include "avif_p.h"
|
||||
#include "util_p.h"
|
||||
|
||||
#include <cfloat>
|
||||
|
||||
QAVIFHandler::QAVIFHandler()
|
||||
@@ -69,7 +67,7 @@ bool QAVIFHandler::canRead(QIODevice *device)
|
||||
|
||||
bool QAVIFHandler::ensureParsed() const
|
||||
{
|
||||
if (m_parseState == ParseAvifSuccess || m_parseState == ParseAvifMetadata) {
|
||||
if (m_parseState == ParseAvifSuccess) {
|
||||
return true;
|
||||
}
|
||||
if (m_parseState == ParseAvifError) {
|
||||
@@ -81,28 +79,6 @@ bool QAVIFHandler::ensureParsed() const
|
||||
return that->ensureDecoder();
|
||||
}
|
||||
|
||||
bool QAVIFHandler::ensureOpened() const
|
||||
{
|
||||
if (m_parseState == ParseAvifSuccess) {
|
||||
return true;
|
||||
}
|
||||
if (m_parseState == ParseAvifError) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QAVIFHandler *that = const_cast<QAVIFHandler *>(this);
|
||||
if (ensureParsed()) {
|
||||
if (m_parseState == ParseAvifMetadata) {
|
||||
bool success = that->jumpToNextImage();
|
||||
that->m_parseState = success ? ParseAvifSuccess : ParseAvifError;
|
||||
return success;
|
||||
}
|
||||
}
|
||||
|
||||
that->m_parseState = ParseAvifError;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool QAVIFHandler::ensureDecoder()
|
||||
{
|
||||
if (m_decoder) {
|
||||
@@ -121,9 +97,6 @@ bool QAVIFHandler::ensureDecoder()
|
||||
|
||||
m_decoder = avifDecoderCreate();
|
||||
|
||||
m_decoder->ignoreExif = AVIF_TRUE;
|
||||
m_decoder->ignoreXMP = AVIF_TRUE;
|
||||
|
||||
#if AVIF_VERSION >= 80400
|
||||
m_decoder->maxThreads = qBound(1, QThread::idealThreadCount(), 64);
|
||||
#endif
|
||||
@@ -154,58 +127,39 @@ bool QAVIFHandler::ensureDecoder()
|
||||
return false;
|
||||
}
|
||||
|
||||
m_container_width = m_decoder->image->width;
|
||||
m_container_height = m_decoder->image->height;
|
||||
decodeResult = avifDecoderNextImage(m_decoder);
|
||||
|
||||
if ((m_container_width > 65535) || (m_container_height > 65535)) {
|
||||
qWarning("AVIF image (%dx%d) is too large!", m_container_width, m_container_height);
|
||||
m_parseState = ParseAvifError;
|
||||
return false;
|
||||
}
|
||||
if (decodeResult == AVIF_RESULT_OK) {
|
||||
m_container_width = m_decoder->image->width;
|
||||
m_container_height = m_decoder->image->height;
|
||||
|
||||
if ((m_container_width == 0) || (m_container_height == 0)) {
|
||||
qWarning("Empty image, nothing to decode");
|
||||
m_parseState = ParseAvifError;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_container_width > ((16384 * 16384) / m_container_height)) {
|
||||
qWarning("AVIF image (%dx%d) has more than 256 megapixels!", m_container_width, m_container_height);
|
||||
m_parseState = ParseAvifError;
|
||||
return false;
|
||||
}
|
||||
|
||||
// calculate final dimensions with crop and rotate operations applied
|
||||
int new_width = m_container_width;
|
||||
int new_height = m_container_height;
|
||||
|
||||
if (m_decoder->image->transformFlags & AVIF_TRANSFORM_CLAP) {
|
||||
if ((m_decoder->image->clap.widthD > 0) && (m_decoder->image->clap.heightD > 0) && (m_decoder->image->clap.horizOffD > 0)
|
||||
&& (m_decoder->image->clap.vertOffD > 0)) {
|
||||
int crop_width = (int)((double)(m_decoder->image->clap.widthN) / (m_decoder->image->clap.widthD) + 0.5);
|
||||
if (crop_width < new_width && crop_width > 0) {
|
||||
new_width = crop_width;
|
||||
}
|
||||
int crop_height = (int)((double)(m_decoder->image->clap.heightN) / (m_decoder->image->clap.heightD) + 0.5);
|
||||
if (crop_height < new_height && crop_height > 0) {
|
||||
new_height = crop_height;
|
||||
}
|
||||
if ((m_container_width > 32768) || (m_container_height > 32768)) {
|
||||
qWarning("AVIF image (%dx%d) is too large!", m_container_width, m_container_height);
|
||||
m_parseState = ParseAvifError;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_decoder->image->transformFlags & AVIF_TRANSFORM_IROT) {
|
||||
if (m_decoder->image->irot.angle == 1 || m_decoder->image->irot.angle == 3) {
|
||||
int tmp = new_width;
|
||||
new_width = new_height;
|
||||
new_height = tmp;
|
||||
if ((m_container_width == 0) || (m_container_height == 0)) {
|
||||
qWarning("Empty image, nothing to decode");
|
||||
m_parseState = ParseAvifError;
|
||||
return false;
|
||||
}
|
||||
|
||||
m_parseState = ParseAvifSuccess;
|
||||
if (decode_one_frame()) {
|
||||
return true;
|
||||
} else {
|
||||
m_parseState = ParseAvifError;
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
qWarning("ERROR: Failed to decode image: %s", avifResultToString(decodeResult));
|
||||
}
|
||||
|
||||
m_estimated_dimensions.setWidth(new_width);
|
||||
m_estimated_dimensions.setHeight(new_height);
|
||||
|
||||
m_parseState = ParseAvifMetadata;
|
||||
return true;
|
||||
avifDecoderDestroy(m_decoder);
|
||||
m_decoder = nullptr;
|
||||
m_parseState = ParseAvifError;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool QAVIFHandler::decode_one_frame()
|
||||
@@ -232,13 +186,13 @@ bool QAVIFHandler::decode_one_frame()
|
||||
}
|
||||
} else {
|
||||
if (loadalpha) {
|
||||
resultformat = QImage::Format_ARGB32;
|
||||
resultformat = QImage::Format_RGBA8888;
|
||||
} else {
|
||||
resultformat = QImage::Format_RGB32;
|
||||
resultformat = QImage::Format_RGBX8888;
|
||||
}
|
||||
}
|
||||
QImage result(m_decoder->image->width, m_decoder->image->height, resultformat);
|
||||
|
||||
QImage result = imageAlloc(m_decoder->image->width, m_decoder->image->height, resultformat);
|
||||
if (result.isNull()) {
|
||||
qWarning("Memory cannot be allocated");
|
||||
return false;
|
||||
@@ -325,38 +279,37 @@ bool QAVIFHandler::decode_one_frame()
|
||||
rgb.depth = 16;
|
||||
rgb.format = AVIF_RGB_FORMAT_RGBA;
|
||||
|
||||
if (!loadalpha && (m_decoder->image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400)) {
|
||||
resultformat = QImage::Format_Grayscale16;
|
||||
if (!loadalpha) {
|
||||
if (m_decoder->image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400) {
|
||||
resultformat = QImage::Format_Grayscale16;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
rgb.depth = 8;
|
||||
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
|
||||
rgb.format = AVIF_RGB_FORMAT_BGRA;
|
||||
#else
|
||||
rgb.format = AVIF_RGB_FORMAT_ARGB;
|
||||
#endif
|
||||
rgb.format = AVIF_RGB_FORMAT_RGBA;
|
||||
|
||||
#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;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!loadalpha && (m_decoder->image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400)) {
|
||||
resultformat = QImage::Format_Grayscale8;
|
||||
if (loadalpha) {
|
||||
resultformat = QImage::Format_ARGB32;
|
||||
} else {
|
||||
if (m_decoder->image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400) {
|
||||
resultformat = QImage::Format_Grayscale8;
|
||||
} else {
|
||||
resultformat = QImage::Format_RGB32;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -440,15 +393,13 @@ bool QAVIFHandler::decode_one_frame()
|
||||
m_current_image = result.convertToFormat(resultformat);
|
||||
}
|
||||
|
||||
m_estimated_dimensions = m_current_image.size();
|
||||
|
||||
m_must_jump_to_next_image = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool QAVIFHandler::read(QImage *image)
|
||||
{
|
||||
if (!ensureOpened()) {
|
||||
if (!ensureParsed()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -466,44 +417,15 @@ bool QAVIFHandler::read(QImage *image)
|
||||
bool QAVIFHandler::write(const QImage &image)
|
||||
{
|
||||
if (image.format() == QImage::Format_Invalid) {
|
||||
qWarning("No image data to save!");
|
||||
qWarning("No image data to save");
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((image.width() > 0) && (image.height() > 0)) {
|
||||
if ((image.width() > 65535) || (image.height() > 65535)) {
|
||||
qWarning("Image (%dx%d) is too large to save!", image.width(), image.height());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (image.width() > ((16384 * 16384) / image.height())) {
|
||||
qWarning("Image (%dx%d) will not be saved because it has more than 256 megapixels!", image.width(), image.height());
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((image.width() > 32768) || (image.height() > 32768)) {
|
||||
qWarning("Image (%dx%d) has a dimension above 32768 pixels, saved AVIF may not work in other software!", image.width(), image.height());
|
||||
}
|
||||
} else {
|
||||
qWarning("Image has zero dimension!");
|
||||
if ((image.width() > 32768) || (image.height() > 32768)) {
|
||||
qWarning("Image is too large");
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *encoder_name = avifCodecName(AVIF_CODEC_CHOICE_AUTO, AVIF_CODEC_FLAG_CAN_ENCODE);
|
||||
if (!encoder_name) {
|
||||
qWarning("Cannot save AVIF images because libavif was built without AV1 encoders!");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool lossless = false;
|
||||
if (m_quality >= 100) {
|
||||
if (avifCodecName(AVIF_CODEC_CHOICE_AOM, AVIF_CODEC_FLAG_CAN_ENCODE)) {
|
||||
lossless = true;
|
||||
} else {
|
||||
qWarning("You are using %s encoder. It is recommended to enable libAOM encoder in libavif to use lossless compression.", encoder_name);
|
||||
}
|
||||
}
|
||||
|
||||
int maxQuantizer = AVIF_QUANTIZER_WORST_QUALITY * (100 - qBound(0, m_quality, 100)) / 100;
|
||||
int minQuantizer = 0;
|
||||
int maxQuantizerAlpha = 0;
|
||||
@@ -696,47 +618,43 @@ bool QAVIFHandler::write(const QImage &image)
|
||||
|
||||
// in case primaries or trc were not identified
|
||||
if ((primaries_to_save == 2) || (transfer_to_save == 2)) {
|
||||
if (lossless) {
|
||||
iccprofile = tmpcolorimage.colorSpace().iccProfile();
|
||||
} else {
|
||||
// upgrade image to higher bit depth
|
||||
if (save_depth == 8) {
|
||||
save_depth = 10;
|
||||
if (tmpcolorimage.hasAlphaChannel()) {
|
||||
tmpcolorimage = tmpcolorimage.convertToFormat(QImage::Format_RGBA64);
|
||||
} else {
|
||||
tmpcolorimage = tmpcolorimage.convertToFormat(QImage::Format_RGBX64);
|
||||
}
|
||||
// upgrade image to higher bit depth
|
||||
if (save_depth == 8) {
|
||||
save_depth = 10;
|
||||
if (tmpcolorimage.hasAlphaChannel()) {
|
||||
tmpcolorimage = tmpcolorimage.convertToFormat(QImage::Format_RGBA64);
|
||||
} else {
|
||||
tmpcolorimage = tmpcolorimage.convertToFormat(QImage::Format_RGBX64);
|
||||
}
|
||||
}
|
||||
|
||||
if ((primaries_to_save == 2) && (transfer_to_save != 2)) { // other primaries but known trc
|
||||
primaries_to_save = (avifColorPrimaries)1; // AVIF_COLOR_PRIMARIES_BT709
|
||||
matrix_to_save = (avifMatrixCoefficients)1; // AVIF_MATRIX_COEFFICIENTS_BT709
|
||||
if ((primaries_to_save == 2) && (transfer_to_save != 2)) { // other primaries but known trc
|
||||
primaries_to_save = (avifColorPrimaries)1; // AVIF_COLOR_PRIMARIES_BT709
|
||||
matrix_to_save = (avifMatrixCoefficients)1; // AVIF_MATRIX_COEFFICIENTS_BT709
|
||||
|
||||
switch (transfer_to_save) {
|
||||
case 8: // AVIF_TRANSFER_CHARACTERISTICS_LINEAR
|
||||
tmpcolorimage.convertToColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, QColorSpace::TransferFunction::Linear));
|
||||
break;
|
||||
case 4: // AVIF_TRANSFER_CHARACTERISTICS_BT470M
|
||||
tmpcolorimage.convertToColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, 2.2f));
|
||||
break;
|
||||
case 5: // AVIF_TRANSFER_CHARACTERISTICS_BT470BG
|
||||
tmpcolorimage.convertToColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, 2.8f));
|
||||
break;
|
||||
default: // AVIF_TRANSFER_CHARACTERISTICS_SRGB + any other
|
||||
tmpcolorimage.convertToColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, QColorSpace::TransferFunction::SRgb));
|
||||
transfer_to_save = (avifTransferCharacteristics)13;
|
||||
break;
|
||||
}
|
||||
} else if ((primaries_to_save != 2) && (transfer_to_save == 2)) { // recognized primaries but other trc
|
||||
transfer_to_save = (avifTransferCharacteristics)13;
|
||||
tmpcolorimage.convertToColorSpace(tmpcolorimage.colorSpace().withTransferFunction(QColorSpace::TransferFunction::SRgb));
|
||||
} else { // unrecognized profile
|
||||
primaries_to_save = (avifColorPrimaries)1; // AVIF_COLOR_PRIMARIES_BT709
|
||||
transfer_to_save = (avifTransferCharacteristics)13;
|
||||
matrix_to_save = (avifMatrixCoefficients)1; // AVIF_MATRIX_COEFFICIENTS_BT709
|
||||
switch (transfer_to_save) {
|
||||
case 8: // AVIF_TRANSFER_CHARACTERISTICS_LINEAR
|
||||
tmpcolorimage.convertToColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, QColorSpace::TransferFunction::Linear));
|
||||
break;
|
||||
case 4: // AVIF_TRANSFER_CHARACTERISTICS_BT470M
|
||||
tmpcolorimage.convertToColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, 2.2f));
|
||||
break;
|
||||
case 5: // AVIF_TRANSFER_CHARACTERISTICS_BT470BG
|
||||
tmpcolorimage.convertToColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, 2.8f));
|
||||
break;
|
||||
default: // AVIF_TRANSFER_CHARACTERISTICS_SRGB + any other
|
||||
tmpcolorimage.convertToColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, QColorSpace::TransferFunction::SRgb));
|
||||
transfer_to_save = (avifTransferCharacteristics)13;
|
||||
break;
|
||||
}
|
||||
} else if ((primaries_to_save != 2) && (transfer_to_save == 2)) { // recognized primaries but other trc
|
||||
transfer_to_save = (avifTransferCharacteristics)13;
|
||||
tmpcolorimage.convertToColorSpace(tmpcolorimage.colorSpace().withTransferFunction(QColorSpace::TransferFunction::SRgb));
|
||||
} else { // unrecognized profile
|
||||
primaries_to_save = (avifColorPrimaries)1; // AVIF_COLOR_PRIMARIES_BT709
|
||||
transfer_to_save = (avifTransferCharacteristics)13;
|
||||
matrix_to_save = (avifMatrixCoefficients)1; // AVIF_MATRIX_COEFFICIENTS_BT709
|
||||
tmpcolorimage.convertToColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, QColorSpace::TransferFunction::SRgb));
|
||||
}
|
||||
}
|
||||
} else { // profile is unsupported by Qt
|
||||
@@ -746,9 +664,6 @@ bool QAVIFHandler::write(const QImage &image)
|
||||
}
|
||||
}
|
||||
|
||||
if (lossless && pixel_format == AVIF_PIXEL_FORMAT_YUV444) {
|
||||
matrix_to_save = (avifMatrixCoefficients)0;
|
||||
}
|
||||
avif = avifImageCreate(tmpcolorimage.width(), tmpcolorimage.height(), save_depth, pixel_format);
|
||||
avif->matrixCoefficients = matrix_to_save;
|
||||
|
||||
@@ -767,7 +682,9 @@ bool QAVIFHandler::write(const QImage &image)
|
||||
if (save_depth > 8) { // 10bit depth
|
||||
rgb.depth = 16;
|
||||
|
||||
if (!tmpcolorimage.hasAlphaChannel()) {
|
||||
if (tmpcolorimage.hasAlphaChannel()) {
|
||||
avif->alphaRange = AVIF_RANGE_FULL;
|
||||
} else {
|
||||
rgb.ignoreAlpha = AVIF_TRUE;
|
||||
}
|
||||
|
||||
@@ -777,16 +694,13 @@ bool QAVIFHandler::write(const QImage &image)
|
||||
|
||||
if (tmpcolorimage.hasAlphaChannel()) {
|
||||
rgb.format = AVIF_RGB_FORMAT_RGBA;
|
||||
avif->alphaRange = AVIF_RANGE_FULL;
|
||||
} else {
|
||||
rgb.format = AVIF_RGB_FORMAT_RGB;
|
||||
}
|
||||
}
|
||||
|
||||
#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;
|
||||
@@ -804,14 +718,14 @@ bool QAVIFHandler::write(const QImage &image)
|
||||
encoder->maxQuantizerAlpha = maxQuantizerAlpha;
|
||||
}
|
||||
|
||||
encoder->speed = 6;
|
||||
encoder->speed = 7;
|
||||
|
||||
res = avifEncoderWrite(encoder, avif, &raw);
|
||||
avifEncoderDestroy(encoder);
|
||||
avifImageDestroy(avif);
|
||||
|
||||
if (res == AVIF_RESULT_OK) {
|
||||
qint64 status = device()->write(reinterpret_cast<const char *>(raw.data), raw.size);
|
||||
qint64 status = device()->write((const char *)raw.data, raw.size);
|
||||
avifRWDataFree(&raw);
|
||||
|
||||
if (status > 0) {
|
||||
@@ -839,7 +753,7 @@ QVariant QAVIFHandler::option(ImageOption option) const
|
||||
|
||||
switch (option) {
|
||||
case Size:
|
||||
return m_estimated_dimensions;
|
||||
return m_current_image.size();
|
||||
case Animation:
|
||||
if (imageCount() >= 2) {
|
||||
return true;
|
||||
@@ -895,14 +809,6 @@ int QAVIFHandler::currentImageNumber() const
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (m_parseState == ParseAvifMetadata) {
|
||||
if (m_decoder->imageCount >= 2) {
|
||||
return -1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return m_decoder->imageIndex;
|
||||
}
|
||||
|
||||
@@ -912,14 +818,12 @@ bool QAVIFHandler::jumpToNextImage()
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_decoder->imageIndex >= 0) {
|
||||
if (m_decoder->imageCount < 2) {
|
||||
return true;
|
||||
}
|
||||
if (m_decoder->imageCount < 2) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (m_decoder->imageIndex >= m_decoder->imageCount - 1) { // start from beginning
|
||||
avifDecoderReset(m_decoder);
|
||||
}
|
||||
if (m_decoder->imageIndex >= m_decoder->imageCount - 1) { // start from beginning
|
||||
avifDecoderReset(m_decoder);
|
||||
}
|
||||
|
||||
avifResult decodeResult = avifDecoderNextImage(m_decoder);
|
||||
@@ -942,7 +846,6 @@ bool QAVIFHandler::jumpToNextImage()
|
||||
}
|
||||
|
||||
if (decode_one_frame()) {
|
||||
m_parseState = ParseAvifSuccess;
|
||||
return true;
|
||||
} else {
|
||||
m_parseState = ParseAvifError;
|
||||
@@ -958,7 +861,7 @@ bool QAVIFHandler::jumpToImage(int imageNumber)
|
||||
|
||||
if (m_decoder->imageCount < 2) { // not an animation
|
||||
if (imageNumber == 0) {
|
||||
return ensureOpened();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@@ -968,8 +871,7 @@ bool QAVIFHandler::jumpToImage(int imageNumber)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (imageNumber == m_decoder->imageIndex) { // we are here already
|
||||
m_must_jump_to_next_image = false;
|
||||
if (imageNumber == m_decoder->imageCount) { // we are here already
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -993,7 +895,6 @@ bool QAVIFHandler::jumpToImage(int imageNumber)
|
||||
}
|
||||
|
||||
if (decode_one_frame()) {
|
||||
m_parseState = ParseAvifSuccess;
|
||||
return true;
|
||||
} else {
|
||||
m_parseState = ParseAvifError;
|
||||
@@ -1003,7 +904,7 @@ bool QAVIFHandler::jumpToImage(int imageNumber)
|
||||
|
||||
int QAVIFHandler::nextImageDelay() const
|
||||
{
|
||||
if (!ensureOpened()) {
|
||||
if (!ensureParsed()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
#include <QImage>
|
||||
#include <QImageIOPlugin>
|
||||
#include <QPointF>
|
||||
#include <QSize>
|
||||
#include <QVariant>
|
||||
#include <avif/avif.h>
|
||||
#include <qimageiohandler.h>
|
||||
@@ -46,7 +45,6 @@ public:
|
||||
private:
|
||||
static QPointF CompatibleChromacity(qreal chrX, qreal chrY);
|
||||
bool ensureParsed() const;
|
||||
bool ensureOpened() const;
|
||||
bool ensureDecoder();
|
||||
bool decode_one_frame();
|
||||
|
||||
@@ -54,7 +52,6 @@ private:
|
||||
ParseAvifError = -1,
|
||||
ParseAvifNotParsed = 0,
|
||||
ParseAvifSuccess = 1,
|
||||
ParseAvifMetadata = 2,
|
||||
};
|
||||
|
||||
ParseAvifState m_parseState;
|
||||
@@ -62,7 +59,6 @@ private:
|
||||
|
||||
uint32_t m_container_width;
|
||||
uint32_t m_container_height;
|
||||
QSize m_estimated_dimensions;
|
||||
|
||||
QByteArray m_rawData;
|
||||
avifROData m_rawAvifData;
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
#include <QPainter>
|
||||
#include <QPrinter>
|
||||
#include <QProcess>
|
||||
#include <QStandardPaths>
|
||||
#include <QTemporaryFile>
|
||||
|
||||
// logging category for this framework, default: log stuff >= warning
|
||||
@@ -177,12 +176,6 @@ bool EPSHandler::read(QImage *image)
|
||||
|
||||
// create GS command line
|
||||
|
||||
const QString gsExec = QStandardPaths::findExecutable(QStringLiteral("gs"));
|
||||
if (gsExec.isEmpty()) {
|
||||
qCWarning(EPSPLUGIN) << "Couldn't find gs exectuable (from GhostScript) in PATH.";
|
||||
return false;
|
||||
}
|
||||
|
||||
QStringList gsArgs;
|
||||
gsArgs << QLatin1String("-sOutputFile=") + tmpFile.fileName() << QStringLiteral("-q") << QStringLiteral("-g%1x%2").arg(wantedWidth).arg(wantedHeight)
|
||||
<< QStringLiteral("-dSAFER") << QStringLiteral("-dPARANOIDSAFER") << QStringLiteral("-dNOPAUSE") << QStringLiteral("-sDEVICE=ppm")
|
||||
@@ -199,7 +192,7 @@ bool EPSHandler::read(QImage *image)
|
||||
|
||||
QProcess converter;
|
||||
converter.setProcessChannelMode(QProcess::ForwardedErrorChannel);
|
||||
converter.start(gsExec, gsArgs);
|
||||
converter.start(QStringLiteral("gs"), gsArgs);
|
||||
if (!converter.waitForStarted(3000)) {
|
||||
qCWarning(EPSPLUGIN) << "Reading EPS files requires gs (from GhostScript)";
|
||||
return false;
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
*/
|
||||
|
||||
#include "exr_p.h"
|
||||
#include "util_p.h"
|
||||
|
||||
#include <IexThrowErrnoExc.h>
|
||||
#include <ImathBox.h>
|
||||
@@ -192,7 +191,7 @@ bool EXRHandler::read(QImage *outImage)
|
||||
width = dw.max.x - dw.min.x + 1;
|
||||
height = dw.max.y - dw.min.y + 1;
|
||||
|
||||
QImage image = imageAlloc(width, height, QImage::Format_RGB32);
|
||||
QImage image(width, height, QImage::Format_RGB32);
|
||||
if (image.isNull()) {
|
||||
qWarning() << "Failed to allocate image, invalid size?" << QSize(width, height);
|
||||
return false;
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
*/
|
||||
|
||||
#include "hdr_p.h"
|
||||
#include "util_p.h"
|
||||
|
||||
#include <QDataStream>
|
||||
#include <QImage>
|
||||
@@ -94,7 +93,7 @@ static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &i
|
||||
uchar code;
|
||||
|
||||
// Create dst image.
|
||||
img = imageAlloc(width, height, QImage::Format_RGB32);
|
||||
img = QImage(width, height, QImage::Format_RGB32);
|
||||
if (img.isNull()) {
|
||||
qCDebug(HDRPLUGIN) << "Couldn't create image with size" << width << height << "and format RGB32";
|
||||
return false;
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
|
||||
#include "heif_p.h"
|
||||
#include "libheif/heif_cxx.h"
|
||||
#include "util_p.h"
|
||||
|
||||
#include <QColorSpace>
|
||||
#include <QDebug>
|
||||
@@ -433,7 +432,7 @@ bool HEIFHandler::ensureDecoder()
|
||||
return false;
|
||||
}
|
||||
|
||||
m_current_image = imageAlloc(imageSize, target_image_format);
|
||||
m_current_image = QImage(imageSize, target_image_format);
|
||||
if (m_current_image.isNull()) {
|
||||
m_parseState = ParseHeicError;
|
||||
qWarning() << "Unable to allocate memory!";
|
||||
|
||||
@@ -10,11 +10,8 @@
|
||||
#include <QtGlobal>
|
||||
|
||||
#include "jxl_p.h"
|
||||
#include "util_p.h"
|
||||
|
||||
#include <jxl/encode.h>
|
||||
#include <jxl/thread_parallel_runner.h>
|
||||
#include <string.h>
|
||||
|
||||
QJpegXLHandler::QJpegXLHandler()
|
||||
: m_parseState(ParseJpegXLNotParsed)
|
||||
@@ -145,10 +142,6 @@ bool QJpegXLHandler::ensureDecoder()
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef KIMG_JXL_API_VERSION
|
||||
JxlDecoderCloseInput(m_decoder);
|
||||
#endif
|
||||
|
||||
JxlDecoderStatus status = JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING | JXL_DEC_FRAME);
|
||||
if (status == JXL_DEC_ERROR) {
|
||||
qWarning("ERROR: JxlDecoderSubscribeEvents failed");
|
||||
@@ -181,30 +174,19 @@ bool QJpegXLHandler::ensureDecoder()
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_basicinfo.xsize > 65535 || m_basicinfo.ysize > 65535) {
|
||||
if (m_basicinfo.xsize > 32768 || m_basicinfo.ysize > 32768) {
|
||||
qWarning("JXL image (%dx%d) is too large", m_basicinfo.xsize, m_basicinfo.ysize);
|
||||
m_parseState = ParseJpegXLError;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sizeof(void *) <= 4) {
|
||||
} else if (sizeof(void *) <= 4) {
|
||||
/* On 32bit systems, there is limited address space.
|
||||
* We skip imagess bigger than 8192 x 8192 pixels.
|
||||
* If we don't do it, abort() in libjxl may close whole application */
|
||||
if (m_basicinfo.xsize > ((8192 * 8192) / m_basicinfo.ysize)) {
|
||||
if ((m_basicinfo.xsize * m_basicinfo.ysize) > 67108864) {
|
||||
qWarning("JXL image (%dx%d) is too large for 32bit build of the plug-in", m_basicinfo.xsize, m_basicinfo.ysize);
|
||||
m_parseState = ParseJpegXLError;
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
/* On 64bit systems
|
||||
* We skip images bigger than 16384 x 16384 pixels.
|
||||
* It is an artificial limit not to use extreme amount of memory */
|
||||
if (m_basicinfo.xsize > ((16384 * 16384) / m_basicinfo.ysize)) {
|
||||
qWarning("JXL image (%dx%d) is bigger than security limit 256 megapixels", m_basicinfo.xsize, m_basicinfo.ysize);
|
||||
m_parseState = ParseJpegXLError;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
m_parseState = ParseJpegXLBasicInfoParsed;
|
||||
@@ -361,7 +343,7 @@ bool QJpegXLHandler::decode_one_frame()
|
||||
return false;
|
||||
}
|
||||
|
||||
m_current_image = imageAlloc(m_basicinfo.xsize, m_basicinfo.ysize, m_input_image_format);
|
||||
m_current_image = QImage(m_basicinfo.xsize, m_basicinfo.ysize, m_input_image_format);
|
||||
if (m_current_image.isNull()) {
|
||||
qWarning("Memory cannot be allocated");
|
||||
m_parseState = ParseJpegXLError;
|
||||
@@ -429,99 +411,17 @@ bool QJpegXLHandler::write(const QImage &image)
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((image.width() > 0) && (image.height() > 0)) {
|
||||
if ((image.width() > 65535) || (image.height() > 65535)) {
|
||||
qWarning("Image (%dx%d) is too large to save!", image.width(), image.height());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sizeof(void *) <= 4) {
|
||||
if (image.width() > ((8192 * 8192) / image.height())) {
|
||||
qWarning("Image (%dx%d) is too large save via 32bit build of JXL plug-in", image.width(), image.height());
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (image.width() > ((16384 * 16384) / image.height())) {
|
||||
qWarning("Image (%dx%d) will not be saved because it has more than 256 megapixels", image.width(), image.height());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
qWarning("Image has zero dimension!");
|
||||
if ((image.width() > 32768) || (image.height() > 32768)) {
|
||||
qWarning("Image is too large");
|
||||
return false;
|
||||
}
|
||||
|
||||
int save_depth = 8; // 8 or 16
|
||||
// depth detection
|
||||
switch (image.format()) {
|
||||
case QImage::Format_BGR30:
|
||||
case QImage::Format_A2BGR30_Premultiplied:
|
||||
case QImage::Format_RGB30:
|
||||
case QImage::Format_A2RGB30_Premultiplied:
|
||||
case QImage::Format_Grayscale16:
|
||||
case QImage::Format_RGBX64:
|
||||
case QImage::Format_RGBA64:
|
||||
case QImage::Format_RGBA64_Premultiplied:
|
||||
save_depth = 16;
|
||||
break;
|
||||
case QImage::Format_RGB32:
|
||||
case QImage::Format_ARGB32:
|
||||
case QImage::Format_ARGB32_Premultiplied:
|
||||
case QImage::Format_RGB888:
|
||||
case QImage::Format_RGBX8888:
|
||||
case QImage::Format_RGBA8888:
|
||||
case QImage::Format_RGBA8888_Premultiplied:
|
||||
save_depth = 8;
|
||||
break;
|
||||
default:
|
||||
if (image.depth() > 32) {
|
||||
save_depth = 16;
|
||||
} else {
|
||||
save_depth = 8;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
JxlEncoder *encoder = JxlEncoderCreate(nullptr);
|
||||
if (!encoder) {
|
||||
qWarning("Failed to create Jxl encoder");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_quality > 100) {
|
||||
m_quality = 100;
|
||||
} else if (m_quality < 0) {
|
||||
m_quality = 90;
|
||||
}
|
||||
|
||||
JxlBasicInfo output_info;
|
||||
JxlEncoderInitBasicInfo(&output_info);
|
||||
|
||||
bool convert_color_profile;
|
||||
QByteArray iccprofile;
|
||||
|
||||
if (image.colorSpace().isValid() && (m_quality < 100)) {
|
||||
if (image.colorSpace().primaries() != QColorSpace::Primaries::SRgb || image.colorSpace().transferFunction() != QColorSpace::TransferFunction::SRgb) {
|
||||
convert_color_profile = true;
|
||||
} else {
|
||||
convert_color_profile = false;
|
||||
}
|
||||
} else { // lossless or no profile or Qt-unsupported ICC profile
|
||||
convert_color_profile = false;
|
||||
iccprofile = image.colorSpace().iccProfile();
|
||||
if (iccprofile.size() > 0 || m_quality == 100) {
|
||||
output_info.uses_original_profile = JXL_TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
if (save_depth == 16 && (image.hasAlphaChannel() || output_info.uses_original_profile)) {
|
||||
output_info.have_container = JXL_TRUE;
|
||||
JxlEncoderUseContainer(encoder, JXL_TRUE);
|
||||
#ifdef KIMG_JXL_API_VERSION
|
||||
JxlEncoderSetCodestreamLevel(encoder, 10);
|
||||
#endif
|
||||
}
|
||||
|
||||
void *runner = nullptr;
|
||||
int num_worker_threads = qBound(1, QThread::idealThreadCount(), 64);
|
||||
|
||||
@@ -535,48 +435,58 @@ bool QJpegXLHandler::write(const QImage &image)
|
||||
}
|
||||
}
|
||||
|
||||
JxlEncoderOptions *encoder_options = JxlEncoderOptionsCreate(encoder, nullptr);
|
||||
|
||||
if (m_quality > 100) {
|
||||
m_quality = 100;
|
||||
} else if (m_quality < 0) {
|
||||
m_quality = 90;
|
||||
}
|
||||
|
||||
JxlEncoderOptionsSetDistance(encoder_options, (100.0f - m_quality) / 10.0f);
|
||||
|
||||
JxlEncoderOptionsSetLossless(encoder_options, (m_quality == 100) ? JXL_TRUE : JXL_FALSE);
|
||||
|
||||
JxlBasicInfo output_info;
|
||||
JxlEncoderInitBasicInfo(&output_info);
|
||||
|
||||
JxlColorEncoding color_profile;
|
||||
JxlColorEncodingSetToSRGB(&color_profile, JXL_FALSE);
|
||||
|
||||
bool convert_color_profile;
|
||||
QByteArray iccprofile;
|
||||
|
||||
if (image.colorSpace().isValid()) {
|
||||
if (image.colorSpace().primaries() != QColorSpace::Primaries::SRgb || image.colorSpace().transferFunction() != QColorSpace::TransferFunction::SRgb) {
|
||||
convert_color_profile = true;
|
||||
} else {
|
||||
convert_color_profile = false;
|
||||
}
|
||||
} else { // no profile or Qt-unsupported ICC profile
|
||||
convert_color_profile = false;
|
||||
iccprofile = image.colorSpace().iccProfile();
|
||||
if (iccprofile.size() > 0) {
|
||||
output_info.uses_original_profile = 1;
|
||||
}
|
||||
}
|
||||
|
||||
JxlPixelFormat pixel_format;
|
||||
QImage::Format tmpformat;
|
||||
JxlEncoderStatus status;
|
||||
|
||||
pixel_format.data_type = JXL_TYPE_UINT16;
|
||||
pixel_format.endianness = JXL_NATIVE_ENDIAN;
|
||||
pixel_format.align = 0;
|
||||
|
||||
output_info.orientation = JXL_ORIENT_IDENTITY;
|
||||
output_info.num_color_channels = 3;
|
||||
output_info.animation.tps_numerator = 10;
|
||||
output_info.animation.tps_denominator = 1;
|
||||
|
||||
if (save_depth > 8) { // 16bit depth
|
||||
pixel_format.data_type = JXL_TYPE_UINT16;
|
||||
|
||||
output_info.bits_per_sample = 16;
|
||||
|
||||
if (image.hasAlphaChannel()) {
|
||||
tmpformat = QImage::Format_RGBA64;
|
||||
pixel_format.num_channels = 4;
|
||||
output_info.alpha_bits = 16;
|
||||
output_info.num_extra_channels = 1;
|
||||
} else {
|
||||
tmpformat = QImage::Format_RGBX64;
|
||||
pixel_format.num_channels = 3;
|
||||
output_info.alpha_bits = 0;
|
||||
}
|
||||
} else { // 8bit depth
|
||||
pixel_format.data_type = JXL_TYPE_UINT8;
|
||||
|
||||
output_info.bits_per_sample = 8;
|
||||
|
||||
if (image.hasAlphaChannel()) {
|
||||
tmpformat = QImage::Format_RGBA8888;
|
||||
pixel_format.num_channels = 4;
|
||||
output_info.alpha_bits = 8;
|
||||
output_info.num_extra_channels = 1;
|
||||
} else {
|
||||
tmpformat = QImage::Format_RGB888;
|
||||
pixel_format.num_channels = 3;
|
||||
output_info.alpha_bits = 0;
|
||||
}
|
||||
if (image.hasAlphaChannel()) {
|
||||
tmpformat = QImage::Format_RGBA64;
|
||||
pixel_format.num_channels = 4;
|
||||
output_info.alpha_bits = 16;
|
||||
output_info.num_extra_channels = 1;
|
||||
} else {
|
||||
tmpformat = QImage::Format_RGBX64;
|
||||
pixel_format.num_channels = 3;
|
||||
output_info.alpha_bits = 0;
|
||||
}
|
||||
|
||||
const QImage tmpimage =
|
||||
@@ -584,7 +494,7 @@ bool QJpegXLHandler::write(const QImage &image)
|
||||
|
||||
const size_t xsize = tmpimage.width();
|
||||
const size_t ysize = tmpimage.height();
|
||||
const size_t buffer_size = (save_depth > 8) ? (2 * pixel_format.num_channels * xsize * ysize) : (pixel_format.num_channels * xsize * ysize);
|
||||
const size_t buffer_size = 2 * pixel_format.num_channels * xsize * ysize;
|
||||
|
||||
if (xsize == 0 || ysize == 0 || tmpimage.isNull()) {
|
||||
qWarning("Unable to allocate memory for output image");
|
||||
@@ -597,6 +507,12 @@ bool QJpegXLHandler::write(const QImage &image)
|
||||
|
||||
output_info.xsize = tmpimage.width();
|
||||
output_info.ysize = tmpimage.height();
|
||||
output_info.bits_per_sample = 16;
|
||||
output_info.intensity_target = 255.0f;
|
||||
output_info.orientation = JXL_ORIENT_IDENTITY;
|
||||
output_info.num_color_channels = 3;
|
||||
output_info.animation.tps_numerator = 10;
|
||||
output_info.animation.tps_denominator = 1;
|
||||
|
||||
status = JxlEncoderSetBasicInfo(encoder, &output_info);
|
||||
if (status != JXL_ENC_SUCCESS) {
|
||||
@@ -619,9 +535,6 @@ bool QJpegXLHandler::write(const QImage &image)
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
JxlColorEncoding color_profile;
|
||||
JxlColorEncodingSetToSRGB(&color_profile, JXL_FALSE);
|
||||
|
||||
status = JxlEncoderSetColorEncoding(encoder, &color_profile);
|
||||
if (status != JXL_ENC_SUCCESS) {
|
||||
qWarning("JxlEncoderSetColorEncoding failed!");
|
||||
@@ -633,74 +546,39 @@ bool QJpegXLHandler::write(const QImage &image)
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef KIMG_JXL_API_VERSION
|
||||
JxlEncoderFrameSettings *encoder_options = JxlEncoderFrameSettingsCreate(encoder, nullptr);
|
||||
|
||||
JxlEncoderSetFrameDistance(encoder_options, (100.0f - m_quality) / 10.0f);
|
||||
|
||||
JxlEncoderSetFrameLossless(encoder_options, (m_quality == 100) ? JXL_TRUE : JXL_FALSE);
|
||||
#else
|
||||
JxlEncoderOptions *encoder_options = JxlEncoderOptionsCreate(encoder, nullptr);
|
||||
|
||||
JxlEncoderOptionsSetDistance(encoder_options, (100.0f - m_quality) / 10.0f);
|
||||
|
||||
JxlEncoderOptionsSetLossless(encoder_options, (m_quality == 100) ? JXL_TRUE : JXL_FALSE);
|
||||
#endif
|
||||
|
||||
if (image.hasAlphaChannel() || ((save_depth == 8) && (xsize % 4 == 0))) {
|
||||
if (image.hasAlphaChannel()) {
|
||||
status = JxlEncoderAddImageFrame(encoder_options, &pixel_format, (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];
|
||||
if (!tmp_buffer) {
|
||||
qWarning("Memory allocation error");
|
||||
if (runner) {
|
||||
JxlThreadParallelRunnerDestroy(runner);
|
||||
}
|
||||
JxlEncoderDestroy(encoder);
|
||||
return false;
|
||||
uint16_t *tmp_buffer = new (std::nothrow) uint16_t[3 * xsize * ysize];
|
||||
if (!tmp_buffer) {
|
||||
qWarning("Memory allocation error");
|
||||
if (runner) {
|
||||
JxlThreadParallelRunnerDestroy(runner);
|
||||
}
|
||||
|
||||
uint16_t *dest_pixels = tmp_buffer;
|
||||
for (int y = 0; y < tmpimage.height(); y++) {
|
||||
const uint16_t *src_pixels = reinterpret_cast<const uint16_t *>(tmpimage.constScanLine(y));
|
||||
for (int x = 0; x < tmpimage.width(); x++) {
|
||||
// R
|
||||
*dest_pixels = *src_pixels;
|
||||
dest_pixels++;
|
||||
src_pixels++;
|
||||
// G
|
||||
*dest_pixels = *src_pixels;
|
||||
dest_pixels++;
|
||||
src_pixels++;
|
||||
// B
|
||||
*dest_pixels = *src_pixels;
|
||||
dest_pixels++;
|
||||
src_pixels += 2; // skipalpha
|
||||
}
|
||||
}
|
||||
status = JxlEncoderAddImageFrame(encoder_options, &pixel_format, (void *)tmp_buffer, buffer_size);
|
||||
delete[] tmp_buffer;
|
||||
} else { // 8bit depth without alpha channel
|
||||
uchar *tmp_buffer8 = new (std::nothrow) uchar[3 * xsize * ysize];
|
||||
if (!tmp_buffer8) {
|
||||
qWarning("Memory allocation error");
|
||||
if (runner) {
|
||||
JxlThreadParallelRunnerDestroy(runner);
|
||||
}
|
||||
JxlEncoderDestroy(encoder);
|
||||
return false;
|
||||
}
|
||||
|
||||
uchar *dest_pixels8 = tmp_buffer8;
|
||||
const size_t rowbytes = 3 * xsize;
|
||||
for (int y = 0; y < tmpimage.height(); y++) {
|
||||
memcpy(dest_pixels8, tmpimage.constScanLine(y), rowbytes);
|
||||
dest_pixels8 += rowbytes;
|
||||
}
|
||||
status = JxlEncoderAddImageFrame(encoder_options, &pixel_format, (void *)tmp_buffer8, buffer_size);
|
||||
delete[] tmp_buffer8;
|
||||
JxlEncoderDestroy(encoder);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16_t *dest_pixels = tmp_buffer;
|
||||
for (int y = 0; y < tmpimage.height(); y++) {
|
||||
const uint16_t *src_pixels = reinterpret_cast<const uint16_t *>(tmpimage.constScanLine(y));
|
||||
for (int x = 0; x < tmpimage.width(); x++) {
|
||||
// R
|
||||
*dest_pixels = *src_pixels;
|
||||
dest_pixels++;
|
||||
src_pixels++;
|
||||
// G
|
||||
*dest_pixels = *src_pixels;
|
||||
dest_pixels++;
|
||||
src_pixels++;
|
||||
// B
|
||||
*dest_pixels = *src_pixels;
|
||||
dest_pixels++;
|
||||
src_pixels += 2; // skipalpha
|
||||
}
|
||||
}
|
||||
status = JxlEncoderAddImageFrame(encoder_options, &pixel_format, (void *)tmp_buffer, buffer_size);
|
||||
delete[] tmp_buffer;
|
||||
}
|
||||
|
||||
if (status == JXL_ENC_ERROR) {
|
||||
@@ -745,7 +623,7 @@ bool QJpegXLHandler::write(const QImage &image)
|
||||
compressed.resize(next_out - compressed.data());
|
||||
|
||||
if (compressed.size() > 0) {
|
||||
qint64 write_status = device()->write(reinterpret_cast<const char *>(compressed.data()), compressed.size());
|
||||
qint64 write_status = device()->write((const char *)compressed.data(), compressed.size());
|
||||
|
||||
if (write_status > 0) {
|
||||
return true;
|
||||
@@ -936,10 +814,6 @@ bool QJpegXLHandler::rewind()
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef KIMG_JXL_API_VERSION
|
||||
JxlDecoderCloseInput(m_decoder);
|
||||
#endif
|
||||
|
||||
if (m_basicinfo.uses_original_profile) {
|
||||
if (JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_FULL_IMAGE) != JXL_DEC_SUCCESS) {
|
||||
qWarning("ERROR: JxlDecoderSubscribeEvents failed");
|
||||
|
||||
@@ -18,6 +18,11 @@
|
||||
|
||||
static constexpr char s_magic[] = "application/x-krita";
|
||||
static constexpr int s_magic_size = sizeof(s_magic) - 1; // -1 to remove the last \0
|
||||
static constexpr int s_krita3_offset = 0x26;
|
||||
static constexpr int s_krita4_offset = 0x2B;
|
||||
static constexpr int s_krita4_64_offset = 0x40;
|
||||
static constexpr int s_krita5_offset = 0x26;
|
||||
static constexpr int s_krita5_64_offset = 0x3A;
|
||||
|
||||
KraHandler::KraHandler()
|
||||
{
|
||||
@@ -58,10 +63,8 @@ bool KraHandler::canRead(QIODevice *device)
|
||||
return false;
|
||||
}
|
||||
|
||||
char buff[57];
|
||||
if (device->peek(buff, sizeof(buff)) == sizeof(buff)) {
|
||||
return memcmp(buff + 0x26, s_magic, s_magic_size) == 0;
|
||||
}
|
||||
ByteArray ba = device->peek(43 + s_magic_size);
|
||||
return (ba.contains(s_magic));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,11 @@
|
||||
|
||||
static constexpr char s_magic[] = "image/openraster";
|
||||
static constexpr int s_magic_size = sizeof(s_magic) - 1; // -1 to remove the last \0
|
||||
static constexpr int s_krita3_offset = 0x26;
|
||||
static constexpr int s_krita4_offset = 0x2B;
|
||||
static constexpr int s_krita4_64_offset = 0x40;
|
||||
static constexpr int s_krita5_offset = 0x26;
|
||||
static constexpr int s_krita5_64_offset = 0x3A;
|
||||
|
||||
OraHandler::OraHandler()
|
||||
{
|
||||
@@ -57,10 +62,11 @@ bool OraHandler::canRead(QIODevice *device)
|
||||
return false;
|
||||
}
|
||||
|
||||
char buff[54];
|
||||
if (device->peek(buff, sizeof(buff)) == sizeof(buff)) {
|
||||
return memcmp(buff + 0x26, s_magic, s_magic_size) == 0;
|
||||
}
|
||||
if (device->peek(s_krita3_offset + s_magic_size).contains(s_magic)) return true;
|
||||
if (device->peek(s_krita4_offset + s_magic_size).contains(s_magic)) return true;
|
||||
if (device->peek(s_krita4_64_offset + s_magic_size).contains(s_magic)) return true;
|
||||
if (device->peek(s_krita5_offset + s_magic_size).contains(s_magic)) return true;
|
||||
if (device->peek(s_krita5_64_offset + s_magic_size).contains(s_magic)) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
*/
|
||||
|
||||
#include "pcx_p.h"
|
||||
#include "util_p.h"
|
||||
|
||||
#include <QColor>
|
||||
#include <QDataStream>
|
||||
@@ -263,7 +262,7 @@ static void readImage1(QImage &img, QDataStream &s, const PCXHEADER &header)
|
||||
{
|
||||
QByteArray buf(header.BytesPerLine, 0);
|
||||
|
||||
img = imageAlloc(header.width(), header.height(), QImage::Format_Mono);
|
||||
img = QImage(header.width(), header.height(), QImage::Format_Mono);
|
||||
img.setColorCount(2);
|
||||
|
||||
if (img.isNull()) {
|
||||
@@ -295,7 +294,7 @@ static void readImage4(QImage &img, QDataStream &s, const PCXHEADER &header)
|
||||
QByteArray buf(header.BytesPerLine * 4, 0);
|
||||
QByteArray pixbuf(header.width(), 0);
|
||||
|
||||
img = imageAlloc(header.width(), header.height(), QImage::Format_Indexed8);
|
||||
img = QImage(header.width(), header.height(), QImage::Format_Indexed8);
|
||||
img.setColorCount(16);
|
||||
if (img.isNull()) {
|
||||
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());
|
||||
@@ -339,7 +338,7 @@ static void readImage8(QImage &img, QDataStream &s, const PCXHEADER &header)
|
||||
{
|
||||
QByteArray buf(header.BytesPerLine, 0);
|
||||
|
||||
img = imageAlloc(header.width(), header.height(), QImage::Format_Indexed8);
|
||||
img = QImage(header.width(), header.height(), QImage::Format_Indexed8);
|
||||
img.setColorCount(256);
|
||||
|
||||
if (img.isNull()) {
|
||||
@@ -389,7 +388,7 @@ static void readImage24(QImage &img, QDataStream &s, const PCXHEADER &header)
|
||||
QByteArray g_buf(header.BytesPerLine, 0);
|
||||
QByteArray b_buf(header.BytesPerLine, 0);
|
||||
|
||||
img = imageAlloc(header.width(), header.height(), QImage::Format_RGB32);
|
||||
img = QImage(header.width(), header.height(), QImage::Format_RGB32);
|
||||
|
||||
if (img.isNull()) {
|
||||
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());
|
||||
@@ -684,12 +683,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];
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
*/
|
||||
|
||||
#include "pic_p.h"
|
||||
|
||||
#include "rle_p.h"
|
||||
#include "util_p.h"
|
||||
|
||||
#include <QDataStream>
|
||||
#include <QDebug>
|
||||
@@ -238,7 +238,7 @@ bool SoftimagePICHandler::read(QImage *image)
|
||||
}
|
||||
}
|
||||
|
||||
QImage img = imageAlloc(m_header.width, m_header.height, fmt);
|
||||
QImage img(m_header.width, m_header.height, fmt);
|
||||
if (img.isNull()) {
|
||||
qDebug() << "Failed to allocate image, invalid dimensions?" << QSize(m_header.width, m_header.height) << fmt;
|
||||
return false;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[Desktop Entry]
|
||||
Type=Service
|
||||
X-KDE-ServiceTypes=QImageIOPlugins
|
||||
X-KDE-ImageFormat=psd,psb,pdd,psdt
|
||||
X-KDE-ImageFormat=psd
|
||||
X-KDE-MimeType=image/vnd.adobe.photoshop
|
||||
X-KDE-Read=true
|
||||
X-KDE-Write=false
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"Keys": [ "psd", "psb", "pdd", "psdt" ],
|
||||
"MimeTypes": [ "image/vnd.adobe.photoshop", "image/vnd.adobe.photoshop", "image/vnd.adobe.photoshop", "image/vnd.adobe.photoshop" ]
|
||||
"Keys": [ "psd" ],
|
||||
"MimeTypes": [ "image/vnd.adobe.photoshop" ]
|
||||
}
|
||||
|
||||
@@ -18,9 +18,6 @@ public:
|
||||
bool canRead() const override;
|
||||
bool read(QImage *image) override;
|
||||
|
||||
bool supportsOption(QImageIOHandler::ImageOption option) const override;
|
||||
QVariant option(QImageIOHandler::ImageOption option) const override;
|
||||
|
||||
static bool canRead(QIODevice *device);
|
||||
};
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
*/
|
||||
|
||||
#include "ras_p.h"
|
||||
#include "util_p.h"
|
||||
|
||||
#include <QDataStream>
|
||||
#include <QDebug>
|
||||
@@ -103,7 +102,8 @@ static bool LoadRAS(QDataStream &s, const RasHeader &ras, QImage &img)
|
||||
{
|
||||
s.device()->seek(RasHeader::SIZE);
|
||||
|
||||
if (ras.ColorMapLength > kMaxQVectorSize) {
|
||||
// QVector uses some extra space for stuff, hence the 32 here suggested by thiago
|
||||
if (ras.ColorMapLength > std::numeric_limits<int>::max() - 32) {
|
||||
qWarning() << "LoadRAS() unsupported image color map length in file header" << ras.ColorMapLength;
|
||||
return false;
|
||||
}
|
||||
@@ -127,7 +127,8 @@ static bool LoadRAS(QDataStream &s, const RasHeader &ras, QImage &img)
|
||||
qWarning() << "LoadRAS() mistmatch between height and width" << ras.Width << ras.Height << ras.Length << ras.Depth;
|
||||
return false;
|
||||
}
|
||||
if (ras.Length > kMaxQVectorSize) {
|
||||
// QVector uses some extra space for stuff, hence the 32 here suggested by thiago
|
||||
if (ras.Length > std::numeric_limits<int>::max() - 32) {
|
||||
qWarning() << "LoadRAS() unsupported image length in file header" << ras.Length;
|
||||
return false;
|
||||
}
|
||||
@@ -151,7 +152,8 @@ static bool LoadRAS(QDataStream &s, const RasHeader &ras, QImage &img)
|
||||
}
|
||||
|
||||
// Allocate image
|
||||
img = imageAlloc(ras.Width, ras.Height, QImage::Format_ARGB32);
|
||||
img = QImage(ras.Width, ras.Height, QImage::Format_ARGB32);
|
||||
|
||||
if (img.isNull()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,899 +0,0 @@
|
||||
/*
|
||||
RAW support for QImage.
|
||||
|
||||
SPDX-FileCopyrightText: 2022 Mirco Miranda <mircomir@outlook.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "raw_p.h"
|
||||
#include "util_p.h"
|
||||
|
||||
#include <QColorSpace>
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QImage>
|
||||
#include <QSet>
|
||||
|
||||
#if defined(Q_OS_WINDOWS) && !defined(NOMINMAX)
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#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.
|
||||
// If the result changes then the problem is in the local wrapper and must be corrected.
|
||||
// WARNING: using the LibRaw's streams instead of the local wrapper from a Qt program falls in the LOCALE
|
||||
// bug when the RAW file needs the scanf_one() function (e.g. *.MOS files). See also raw_scanf_one().
|
||||
//#define EXCLUDE_LibRaw_QIODevice // Uncomment this code if you think that the problem is LibRaw_QIODevice (default commented)
|
||||
#endif
|
||||
|
||||
namespace // Private.
|
||||
{
|
||||
|
||||
// smart pointer for processed image
|
||||
using pi_unique_ptr = std::unique_ptr<libraw_processed_image_t, std::function<void(libraw_processed_image_t *)>>;
|
||||
|
||||
// clang-format off
|
||||
// Known formats supported by LibRaw (in alphabetical order and lower case)
|
||||
const auto supported_formats = QSet<QByteArray>{
|
||||
"3fr",
|
||||
"arw", "arq",
|
||||
"bay", "bmq",
|
||||
"cr2", "cr3", "cap", "cine", "cs1", "crw",
|
||||
"dcs", "dc2", "dcr", "dng", "drf", "dxo",
|
||||
"eip", "erf",
|
||||
"fff",
|
||||
"hdr",
|
||||
"iiq",
|
||||
"k25", "kc2", "kdc",
|
||||
"mdc", "mef", "mfw", "mos", "mrw",
|
||||
"nef", "nrw",
|
||||
"obm", "orf", "ori",
|
||||
"pef", "ptx", "pxn",
|
||||
"qtk",
|
||||
"r3d", "raf", "raw", "rdc", "rw2", "rwl", "rwz",
|
||||
"sr2", "srf", "srw", "sti",
|
||||
"x3f"
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
inline int raw_scanf_one(const QByteArray &ba, const char *fmt, void *val)
|
||||
{
|
||||
// WARNING: Here it would be nice to use sscanf like LibRaw does but there
|
||||
// is a Big Trouble: THE LOCALE! LibRaw is also affected by the
|
||||
// issue if used in a Qt program:
|
||||
// If you use sscanf here the conversion is wrong when performed
|
||||
// with Italian locale (where the decimal separator is the comma).
|
||||
// The solution should be to use std::locale::global() but it's global!
|
||||
// I don't want to do it. So, don't use code like that:
|
||||
// return sscanf(QByteArray(ba).append('\0').data(), fmt, val);
|
||||
|
||||
// LibRaw is asking only "%d" and "%f" for now. This code is not affected
|
||||
// by the LOCALE bug.
|
||||
auto s = QString::fromLatin1(ba);
|
||||
if (strcmp(fmt, "%d") == 0) {
|
||||
auto ok = false;
|
||||
auto d = QLocale::c().toInt(s, &ok);
|
||||
if (!ok) {
|
||||
return EOF;
|
||||
}
|
||||
*(static_cast<int *>(val)) = d;
|
||||
} else {
|
||||
auto ok = false;
|
||||
auto f = QLocale::c().toFloat(s, &ok);
|
||||
if (!ok) {
|
||||
return EOF;
|
||||
}
|
||||
*(static_cast<float *>(val)) = f;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief The LibRaw_QIODevice class
|
||||
* Implementation of the LibRaw stream interface over a QIODevice.
|
||||
*/
|
||||
class LibRaw_QIODevice : public LibRaw_abstract_datastream
|
||||
{
|
||||
public:
|
||||
explicit LibRaw_QIODevice(QIODevice *device)
|
||||
{
|
||||
m_device = device;
|
||||
}
|
||||
virtual ~LibRaw_QIODevice() override
|
||||
{
|
||||
}
|
||||
virtual int valid() override
|
||||
{
|
||||
return m_device != nullptr;
|
||||
}
|
||||
virtual int read(void *ptr, size_t sz, size_t nmemb) override
|
||||
{
|
||||
auto read = m_device->read(reinterpret_cast<char *>(ptr), sz * nmemb);
|
||||
if (read < 1) {
|
||||
return 0;
|
||||
}
|
||||
if (auto o = read % sz) {
|
||||
seek(-(sz - o), SEEK_CUR);
|
||||
}
|
||||
return read / sz;
|
||||
}
|
||||
virtual int eof() override
|
||||
{
|
||||
return m_device->atEnd() ? 1 : 0;
|
||||
}
|
||||
virtual int seek(INT64 o, int whence) override
|
||||
{
|
||||
auto pos = o;
|
||||
auto size = m_device->size();
|
||||
if (whence == SEEK_CUR) {
|
||||
pos = m_device->pos() + o;
|
||||
}
|
||||
if (whence == SEEK_END) {
|
||||
pos = size + o;
|
||||
}
|
||||
if (pos < 0 || pos > size || m_device->isSequential()) {
|
||||
return -1;
|
||||
}
|
||||
return m_device->seek(pos) ? 0 : -1;
|
||||
}
|
||||
virtual INT64 tell() override
|
||||
{
|
||||
return m_device->pos();
|
||||
}
|
||||
virtual INT64 size() override
|
||||
{
|
||||
return m_device->size();
|
||||
}
|
||||
virtual char *gets(char *s, int sz) override
|
||||
{
|
||||
if (m_device->readLine(s, sz) > 0) {
|
||||
return s;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
virtual int scanf_one(const char *fmt, void *val) override
|
||||
{
|
||||
QByteArray ba;
|
||||
for (int xcnt = 0; xcnt < 24 && !m_device->atEnd(); ++xcnt) {
|
||||
char c;
|
||||
if (!m_device->getChar(&c)) {
|
||||
return EOF;
|
||||
}
|
||||
if (ba.isEmpty() && (c == ' ' || c == '\t')) {
|
||||
continue;
|
||||
}
|
||||
if (c == '\0' || c == ' ' || c == '\t' || c == '\n') {
|
||||
break;
|
||||
}
|
||||
ba.append(c);
|
||||
}
|
||||
return raw_scanf_one(ba, fmt, val);
|
||||
}
|
||||
virtual int get_char() override
|
||||
{
|
||||
unsigned char c;
|
||||
if (!m_device->getChar(reinterpret_cast<char *>(&c))) {
|
||||
return EOF;
|
||||
}
|
||||
return int(c);
|
||||
}
|
||||
#if (LIBRAW_VERSION < LIBRAW_MAKE_VERSION(0, 21, 0)) || defined(LIBRAW_OLD_VIDEO_SUPPORT)
|
||||
virtual void *make_jas_stream() override
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
private:
|
||||
QIODevice *m_device;
|
||||
};
|
||||
|
||||
bool addTag(const QString &tag, QStringList &lines)
|
||||
{
|
||||
auto ok = !tag.isEmpty();
|
||||
if (ok) {
|
||||
lines << tag;
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
QString createTag(QString value, const char *tag)
|
||||
{
|
||||
if (!value.isEmpty()) {
|
||||
value = QStringLiteral("<%1>%2</%1>").arg(QString::fromLatin1(tag), value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
QString createTag(char *asciiz, const char *tag)
|
||||
{
|
||||
auto value = QString::fromUtf8(asciiz);
|
||||
return createTag(value, tag);
|
||||
}
|
||||
|
||||
QString createTimeTag(time_t time, const char *tag)
|
||||
{
|
||||
auto value = QDateTime::fromSecsSinceEpoch(time, Qt::UTC);
|
||||
if (value.isValid() && time > 0) {
|
||||
return createTag(value.toString(Qt::ISODate), tag);
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString createFlashTag(short flash, const char *tag)
|
||||
{
|
||||
QStringList l;
|
||||
auto lc = QLocale::c();
|
||||
// EXIF specifications
|
||||
auto t = QStringLiteral("true");
|
||||
auto f = QStringLiteral("false");
|
||||
l << QStringLiteral("<exif:Fired>%1</exif:Fired>").arg((flash & 1) ? t : f);
|
||||
l << QStringLiteral("<exif:Function>%1</exif:Function>").arg((flash & (1 << 5)) ? t : f);
|
||||
l << QStringLiteral("<exif:RedEyeMode>%1</exif:RedEyeMode>").arg((flash & (1 << 6)) ? t : f);
|
||||
l << QStringLiteral("<exif:Mode>%1</exif:Mode>").arg(lc.toString((int(flash) >> 3) & 3));
|
||||
l << QStringLiteral("<exif:Return>%1</exif:Return>").arg(lc.toString((int(flash) >> 1) & 3));
|
||||
return createTag(l.join(QChar()), tag);
|
||||
}
|
||||
|
||||
QString createTag(quint64 n, const char *tag, quint64 invalid = 0)
|
||||
{
|
||||
if (n != invalid) {
|
||||
return createTag(QLocale::c().toString(n), tag);
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString createTag(qint16 n, const char *tag, qint16 invalid = 0)
|
||||
{
|
||||
if (n != invalid) {
|
||||
return createTag(QLocale::c().toString(n), tag);
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString createTag(quint16 n, const char *tag, quint16 invalid = 0)
|
||||
{
|
||||
if (n != invalid) {
|
||||
return createTag(QLocale::c().toString(n), tag);
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString createTag(float f, const char *tag, qint32 mul = 1)
|
||||
{
|
||||
if (f != 0) {
|
||||
if (mul > 1)
|
||||
return QStringLiteral("<%1>%2/%3</%1>").arg(QString::fromLatin1(tag), QLocale::c().toString(int(f * mul))).arg(mul);
|
||||
return QStringLiteral("<%1>%2</%1>").arg(QString::fromLatin1(tag), QLocale::c().toString(f));
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString createTag(libraw_gps_info_t gps, const char *tag)
|
||||
{
|
||||
auto tmp = QString::fromLatin1(tag);
|
||||
if (tmp.contains(QStringLiteral("Latitude"), Qt::CaseInsensitive)) {
|
||||
if (gps.latref != '\0') {
|
||||
auto lc = QLocale::c();
|
||||
auto value = QStringLiteral("%1,%2%3")
|
||||
.arg(lc.toString(gps.latitude[0], 'f', 0))
|
||||
.arg(lc.toString(gps.latitude[1] + gps.latitude[2] / 60, 'f', 4))
|
||||
.arg(QChar::fromLatin1(gps.latref));
|
||||
return createTag(value, tag);
|
||||
}
|
||||
}
|
||||
if (tmp.contains(QStringLiteral("Longitude"), Qt::CaseInsensitive)) {
|
||||
if (gps.longref != '\0') {
|
||||
auto lc = QLocale::c();
|
||||
auto value = QStringLiteral("%1,%2%3")
|
||||
.arg(lc.toString(gps.longitude[0], 'f', 0))
|
||||
.arg(lc.toString(gps.longitude[1] + gps.longitude[2] / 60, 'f', 4))
|
||||
.arg(QChar::fromLatin1(gps.longref));
|
||||
return createTag(value, tag);
|
||||
}
|
||||
}
|
||||
if (tmp.contains(QStringLiteral("Altitude"), Qt::CaseInsensitive)) {
|
||||
if (gps.altitude != 0) {
|
||||
return createTag(gps.altitude, tag, 1000);
|
||||
}
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString createXmpPacket()
|
||||
{
|
||||
QStringList lines;
|
||||
lines << QStringLiteral("<?xpacket begin=\"\" id=\"W5M0MpCehiHzreSzNTczkc9d\"?>");
|
||||
lines << QStringLiteral("<x:xmpmeta xmlns:x=\"adobe:ns:meta/\" x:xmptk=\"KIMG RAW Plugin\">");
|
||||
lines << QStringLiteral("<rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">");
|
||||
lines << QStringLiteral("</rdf:RDF>");
|
||||
lines << QStringLiteral("</x:xmpmeta>");
|
||||
for (auto i = 30; i > 0; --i)
|
||||
lines << QString::fromLatin1(QByteArray(80, ' '));
|
||||
lines << QStringLiteral("<?xpacket end=\"w\"?>");
|
||||
return lines.join(QChar::fromLatin1('\n'));
|
||||
}
|
||||
|
||||
QString updateXmpPacket(const QString &xmpPacket, LibRaw *rawProcessor)
|
||||
{
|
||||
auto rdfEnd = xmpPacket.indexOf(QStringLiteral("</rdf:RDF>"), Qt::CaseInsensitive);
|
||||
if (rdfEnd < 0) {
|
||||
return updateXmpPacket(createXmpPacket(), rawProcessor);
|
||||
}
|
||||
|
||||
auto lines = QStringList() << xmpPacket.left(rdfEnd);
|
||||
lines << QStringLiteral("<rdf:Description rdf:about=\"\"");
|
||||
lines << QStringLiteral(" xmlns:xmp=\"http://ns.adobe.com/xap/1.0/\"");
|
||||
lines << QStringLiteral(" xmlns:dc=\"http://purl.org/dc/elements/1.1/\"");
|
||||
lines << QStringLiteral(" xmlns:aux=\"http://ns.adobe.com/exif/1.0/aux/\"");
|
||||
lines << QStringLiteral(" xmlns:xmpMM=\"http://ns.adobe.com/xap/1.0/mm/\"");
|
||||
lines << QStringLiteral(" xmlns:stEvt=\"http://ns.adobe.com/xap/1.0/sType/ResourceEvent#\"");
|
||||
lines << QStringLiteral(" xmlns:stRef=\"http://ns.adobe.com/xap/1.0/sType/ResourceRef#\"");
|
||||
lines << QStringLiteral(" xmlns:tiff=\"http://ns.adobe.com/tiff/1.0/\"");
|
||||
lines << QStringLiteral(" xmlns:exif=\"http://ns.adobe.com/exif/1.0/\"");
|
||||
lines << QStringLiteral(" xmlns:xmpRights=\"http://ns.adobe.com/xap/1.0/rights/\">");
|
||||
lines << QStringLiteral("<xmpMM:History>");
|
||||
lines << QStringLiteral("<rdf:Seq>");
|
||||
lines << QStringLiteral("<rdf:li rdf:parseType=\"Resource\">");
|
||||
lines << QStringLiteral("<stEvt:action>converted</stEvt:action>");
|
||||
lines << QStringLiteral("<stEvt:parameters>Converted from RAW to Qt Image using KIMG RAW plugin</stEvt:parameters>");
|
||||
lines << QStringLiteral("<stEvt:softwareAgent>LibRaw %1</stEvt:softwareAgent>").arg(QString::fromLatin1(LibRaw::version()));
|
||||
lines << QStringLiteral("<stEvt:when>%1</stEvt:when>").arg(QDateTime::currentDateTimeUtc().toString(Qt::ISODate));
|
||||
lines << QStringLiteral("</rdf:li>");
|
||||
lines << QStringLiteral("</rdf:Seq>");
|
||||
lines << QStringLiteral("</xmpMM:History>");
|
||||
|
||||
auto &&iparams = rawProcessor->imgdata.idata;
|
||||
addTag(createTag(iparams.normalized_model, "tiff:Model"), lines);
|
||||
addTag(createTag(iparams.normalized_make, "tiff:Make"), lines);
|
||||
addTag(createTag(iparams.software, "xmp:CreatorTool"), lines);
|
||||
|
||||
auto &&iother = rawProcessor->imgdata.other;
|
||||
addTag(createTag(createTag(createTag(iother.desc, "rdf:li"), "rdf:Alt"), "dc:description"), lines);
|
||||
addTag(createTag(createTag(createTag(iother.artist, "rdf:li"), "rdf:Seq"), "dc:creator"), lines);
|
||||
addTag(createTag(createTag(createTag(iother.iso_speed, "rdf:li"), "rdf:Seq"), "exif:ISOSpeedRatings"), lines);
|
||||
addTag(createTag(iother.shutter, "exif:ExposureTime", 1000), lines);
|
||||
addTag(createTag(iother.aperture, "exif:ApertureValue", 1000), lines);
|
||||
addTag(createTag(iother.focal_len, "exif:FocalLength", 1000), lines);
|
||||
addTag(createTimeTag(iother.timestamp, "xmp:CreateDate"), lines);
|
||||
addTag(createTag(iother.parsed_gps, "exif:GPSLatitude"), lines);
|
||||
addTag(createTag(iother.parsed_gps, "exif:GPSLongitude"), lines);
|
||||
addTag(createTag(iother.parsed_gps, "exif:GPSAltitude"), lines);
|
||||
|
||||
auto &&shotinfo = rawProcessor->imgdata.shootinginfo;
|
||||
addTag(createTag(shotinfo.ExposureMode, "exif:ExposureMode", short(-1)), lines);
|
||||
addTag(createTag(shotinfo.MeteringMode, "exif:MeteringMode", short(-1)), lines);
|
||||
addTag(createTag(shotinfo.BodySerial, "aux:SerialNumber"), lines);
|
||||
|
||||
auto &&color = rawProcessor->imgdata.color;
|
||||
addTag(createFlashTag(color.flash_used, "exif:Flash"), lines);
|
||||
|
||||
auto &&lens = rawProcessor->imgdata.lens;
|
||||
addTag(createTag(lens.FocalLengthIn35mmFormat, "exif:FocalLengthIn35mmFilm"), lines);
|
||||
addTag(createTag(lens.Lens, "aux:Lens"), lines);
|
||||
addTag(createTag(lens.LensSerial, "aux:LensSerialNumber"), lines);
|
||||
addTag(createTag(lens.nikon.LensIDNumber ? quint64(lens.nikon.LensIDNumber) : lens.makernotes.LensID, "aux:LensID"), lines);
|
||||
|
||||
auto &&makernotes = rawProcessor->imgdata.makernotes;
|
||||
addTag(createTag(makernotes.common.firmware, "aux:Firmware"), lines);
|
||||
|
||||
lines << QStringLiteral("</rdf:Description>");
|
||||
lines << xmpPacket.mid(rdfEnd);
|
||||
|
||||
return lines.join(QChar::fromLatin1('\n'));
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline void rgbToRgbX(uchar *target, const uchar *source, qint32 targetSize, qint32 sourceSize)
|
||||
{
|
||||
auto s = reinterpret_cast<const T *>(source);
|
||||
auto t = reinterpret_cast<T *>(target);
|
||||
auto width = std::min(targetSize / 4, sourceSize / 3) / qint32(sizeof(T));
|
||||
for (qint32 x = 0; x < width; ++x) {
|
||||
t[x * 4 + 0] = s[x * 3 + 0];
|
||||
t[x * 4 + 1] = s[x * 3 + 1];
|
||||
t[x * 4 + 2] = s[x * 3 + 2];
|
||||
t[x * 4 + 3] = std::numeric_limits<T>::max();
|
||||
}
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
#define C_IQ(a) (((a) & 0xF) << 4)
|
||||
#define C_OC(a) (((a) & 0xF) << 8)
|
||||
#define C_CW(a) (((a) & 0x1) << 12)
|
||||
#define C_AW(a) (((a) & 0x1) << 13)
|
||||
#define C_BT(a) (((a) & 0x1) << 14)
|
||||
#define C_HS(a) (((a) & 0x1) << 15)
|
||||
#define C_CE(a) (((a) & 0x1) << 16)
|
||||
#define C_NR(a) (((a) & 0x3) << 17)
|
||||
#define C_FC(a) (((a) & 0x1) << 19)
|
||||
#define C_SR(a) (((a) & 0x1) << 20)
|
||||
#define C_PRESET(a) ((a) & 0xF)
|
||||
|
||||
#define T_IQ(a) (((a) >> 4) & 0xF)
|
||||
#define T_OC(a) (((a) >> 8) & 0xF)
|
||||
#define T_CW(a) (((a) >> 12) & 0x1)
|
||||
#define T_AW(a) (((a) >> 13) & 0x1)
|
||||
#define T_BT(a) (((a) >> 14) & 0x1)
|
||||
#define T_HS(a) (((a) >> 15) & 0x1)
|
||||
#define T_CE(a) (((a) >> 16) & 0x1)
|
||||
#define T_NR(a) (((a) >> 17) & 0x3)
|
||||
#define T_FC(a) (((a) >> 19) & 0x1)
|
||||
#define T_SR(a) (((a) >> 20) & 0x1)
|
||||
#define T_PRESET(a) ((a) & 0xF)
|
||||
// clang-format on
|
||||
|
||||
#define DEFAULT_QUALITY (C_IQ(3) | C_OC(1) | C_CW(1) | C_AW(1) | C_BT(1) | C_HS(0))
|
||||
|
||||
void setParams(QImageIOHandler *handler, LibRaw *rawProcessor)
|
||||
{
|
||||
// *** Set raw params
|
||||
#if (LIBRAW_VERSION < LIBRAW_MAKE_VERSION(0, 21, 0))
|
||||
auto &&rawparams = rawProcessor->imgdata.params;
|
||||
#else
|
||||
auto &&rawparams = rawProcessor->imgdata.rawparams;
|
||||
#endif
|
||||
// Select one raw image from input file (0 - first, ...)
|
||||
rawparams.shot_select = handler->currentImageNumber();
|
||||
|
||||
// *** Set processing parameters
|
||||
|
||||
// NOTE: The default value set below are the ones that gave the best results
|
||||
// on a large sample of images (https://raw.pixls.us/data/)
|
||||
|
||||
/**
|
||||
* @brief quality
|
||||
* Plugin quality option.
|
||||
*/
|
||||
qint32 quality = -1;
|
||||
if (handler->supportsOption(QImageIOHandler::Quality)) {
|
||||
quality = handler->option(QImageIOHandler::Quality).toInt();
|
||||
}
|
||||
if (quality < 0) {
|
||||
quality = DEFAULT_QUALITY;
|
||||
}
|
||||
|
||||
switch (T_PRESET(quality)) {
|
||||
case 0:
|
||||
break;
|
||||
case 1:
|
||||
quality = C_IQ(0) | C_OC(1) | C_CW(1) | C_AW(1) | C_BT(0) | C_HS(1);
|
||||
break;
|
||||
case 2:
|
||||
quality = C_IQ(0) | C_OC(1) | C_CW(1) | C_AW(1) | C_BT(0) | C_HS(0);
|
||||
break;
|
||||
case 3:
|
||||
quality = C_IQ(3) | C_OC(1) | C_CW(1) | C_AW(1) | C_BT(0) | C_HS(0);
|
||||
break;
|
||||
case 4:
|
||||
quality = C_IQ(3) | C_OC(1) | C_CW(1) | C_AW(1) | C_BT(1) | C_HS(0);
|
||||
break;
|
||||
case 5:
|
||||
quality = C_IQ(3) | C_OC(2) | C_CW(1) | C_AW(1) | C_BT(1) | C_HS(0);
|
||||
break;
|
||||
case 6:
|
||||
quality = C_IQ(3) | C_OC(4) | C_CW(1) | C_AW(1) | C_BT(1) | C_HS(0);
|
||||
break;
|
||||
case 7:
|
||||
quality = C_IQ(11) | C_OC(1) | C_CW(1) | C_AW(1) | C_BT(0) | C_HS(0);
|
||||
break;
|
||||
case 8:
|
||||
quality = C_IQ(11) | C_OC(1) | C_CW(1) | C_AW(1) | C_BT(1) | C_HS(0);
|
||||
break;
|
||||
case 9:
|
||||
quality = C_IQ(11) | C_OC(2) | C_CW(1) | C_AW(1) | C_BT(1) | C_HS(0);
|
||||
break;
|
||||
case 10:
|
||||
quality = C_IQ(11) | C_OC(4) | C_CW(1) | C_AW(1) | C_BT(1) | C_HS(0);
|
||||
break;
|
||||
default:
|
||||
quality = DEFAULT_QUALITY;
|
||||
break;
|
||||
}
|
||||
|
||||
auto &¶ms = rawProcessor->imgdata.params;
|
||||
|
||||
/**
|
||||
* @brief use_camera_wb
|
||||
* Use camera white balance, if possible (0 - off, 1 - on)
|
||||
*
|
||||
* This should to be set. Alternatively, a calibrated white balance should be set on each camera.
|
||||
*/
|
||||
params.use_camera_wb = T_CW(quality);
|
||||
|
||||
/*!
|
||||
* \brief use_auto_wb
|
||||
* Average the whole image for white balance (0 - off, 1 - on)
|
||||
*
|
||||
* This is usefull if no camera white balance is available.
|
||||
*/
|
||||
params.use_auto_wb = T_AW(quality);
|
||||
|
||||
/**
|
||||
* @brief output_bps
|
||||
* Bits per pixel (8 or 16)
|
||||
*
|
||||
* Professional cameras (and even some smartphones) generate images at 10 or more bits per sample.
|
||||
* When using 16-bit images, the highest quality should be maintained.
|
||||
*/
|
||||
params.output_bps = T_BT(quality) ? 16 : 8;
|
||||
|
||||
/**
|
||||
* @brief output_color
|
||||
* Output colorspace (0 - raw, 1 - sRGB, 2 - Adobe, 3 - Wide, 4 - ProPhoto, 5 - XYZ, 6 - ACES, 7 - DCI-P3, 8 - Rec2020)
|
||||
*
|
||||
* sRGB allows you to view images correctly on programs that do not support ICC profiles. When most
|
||||
* Programs will support icc profiles, ProPhoto may be a better choice.
|
||||
* @note sRgb is the LibRaw default: if grayscale image is loaded, LibRaw switches to 0 (Raw) automatically.
|
||||
*/
|
||||
params.output_color = T_OC(quality);
|
||||
|
||||
/**
|
||||
* @brief user_qual
|
||||
* Interpolation quality (0 - linear, 1 - VNG, 2 - PPG, 3 - AHD, 4 - DCB, 11 - DHT, 12 - AAHD)
|
||||
*
|
||||
* DHT seems the best option - See In-Depth Demosaicing Algorithm Analysis (https://www.libraw.org/node/2306)
|
||||
* but, when used, some FUJI RAF files of my library are poorly rendered (e.g. completely green). This is the
|
||||
* why I used AHD: a good compromise between quality and performance with no rendering errors.
|
||||
*/
|
||||
params.user_qual = T_IQ(quality);
|
||||
|
||||
/**
|
||||
* @brief half_size
|
||||
* Generate an half-size image (0 - off, 1 - on)
|
||||
*
|
||||
* Very fast and useful for generating previews.
|
||||
*/
|
||||
params.half_size = T_HS(quality);
|
||||
|
||||
/**
|
||||
* @dcb_enhance_fl
|
||||
* DCB color enhance filter (0 - off, 1 - on)
|
||||
*/
|
||||
params.dcb_enhance_fl = T_CE(quality);
|
||||
|
||||
/**
|
||||
* @fbdd_noiserd
|
||||
* FBDD noise reduction (0 - off, 1 - light, 2 - full)
|
||||
*/
|
||||
params.fbdd_noiserd = std::min(2, T_NR(quality));
|
||||
|
||||
/**
|
||||
* @four_color_rgb
|
||||
* Interpolate RGGB as four colors (0 - off, 1 - on)
|
||||
*/
|
||||
params.four_color_rgb = T_FC(quality);
|
||||
|
||||
/**
|
||||
* @use_fuji_rotate
|
||||
* Don't stretch or rotate raw pixels (0 - off, 1 - on)
|
||||
*/
|
||||
params.use_fuji_rotate = T_SR(quality) ? 0 : 1;
|
||||
}
|
||||
|
||||
bool LoadRAW(QImageIOHandler *handler, QImage &img)
|
||||
{
|
||||
std::unique_ptr<LibRaw> rawProcessor(new LibRaw);
|
||||
|
||||
// *** Set parameters
|
||||
setParams(handler, rawProcessor.get());
|
||||
|
||||
// *** Open the stream
|
||||
auto device = handler->device();
|
||||
#ifndef EXCLUDE_LibRaw_QIODevice
|
||||
LibRaw_QIODevice stream(device);
|
||||
if (rawProcessor->open_datastream(&stream) != LIBRAW_SUCCESS) {
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
auto ba = device->readAll();
|
||||
if (rawProcessor->open_buffer(ba.data(), ba.size()) != LIBRAW_SUCCESS) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
// *** Unpacking selected image
|
||||
if (rawProcessor->unpack() != LIBRAW_SUCCESS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// *** Process selected image
|
||||
if (rawProcessor->dcraw_process() != LIBRAW_SUCCESS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// *** Convert to QImage
|
||||
pi_unique_ptr processedImage(rawProcessor->dcraw_make_mem_image(), LibRaw::dcraw_clear_mem);
|
||||
if (processedImage == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
if ((processedImage->type != LIBRAW_IMAGE_BITMAP) ||
|
||||
(processedImage->colors != 1 && processedImage->colors != 3 && processedImage->colors != 4) ||
|
||||
(processedImage->bits != 8 && processedImage->bits != 16)) {
|
||||
return false;
|
||||
}
|
||||
// clang-format on
|
||||
|
||||
auto format = QImage::Format_Invalid;
|
||||
switch (processedImage->colors) {
|
||||
case 1: // Gray images (tested with image attached on https://bugs.kde.org/show_bug.cgi?id=401371)
|
||||
format = processedImage->bits == 8 ? QImage::Format_Grayscale8 : QImage::Format_Grayscale16;
|
||||
break;
|
||||
case 3: // Images with R G B components
|
||||
format = processedImage->bits == 8 ? QImage::Format_RGB888 : QImage::Format_RGBX64;
|
||||
break;
|
||||
case 4: // Images with R G B components + Alpha (never seen)
|
||||
format = processedImage->bits == 8 ? QImage::Format_RGBA8888 : QImage::Format_RGBA64;
|
||||
break;
|
||||
}
|
||||
|
||||
if (format == QImage::Format_Invalid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
img = imageAlloc(processedImage->width, processedImage->height, format);
|
||||
if (img.isNull()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto rawBytesPerLine = qint32(processedImage->width * processedImage->bits * processedImage->colors + 7) / 8;
|
||||
auto lineSize = std::min(qint32(img.bytesPerLine()), rawBytesPerLine);
|
||||
for (int y = 0, h = img.height(); y < h; ++y) {
|
||||
auto scanline = img.scanLine(y);
|
||||
if (format == QImage::Format_RGBX64)
|
||||
rgbToRgbX<quint16>(scanline, processedImage->data + rawBytesPerLine * y, img.bytesPerLine(), rawBytesPerLine);
|
||||
else
|
||||
memcpy(scanline, processedImage->data + rawBytesPerLine * y, lineSize);
|
||||
}
|
||||
|
||||
// *** Set the color space
|
||||
auto &¶ms = rawProcessor->imgdata.params;
|
||||
if (params.output_color == 0) {
|
||||
auto &&color = rawProcessor->imgdata.color;
|
||||
if (auto profile = reinterpret_cast<char *>(color.profile)) {
|
||||
img.setColorSpace(QColorSpace::fromIccProfile(QByteArray(profile, color.profile_length)));
|
||||
}
|
||||
}
|
||||
if (processedImage->colors >= 3) {
|
||||
if (params.output_color == 1) {
|
||||
img.setColorSpace(QColorSpace(QColorSpace::SRgb));
|
||||
}
|
||||
if (params.output_color == 2) {
|
||||
img.setColorSpace(QColorSpace(QColorSpace::AdobeRgb));
|
||||
}
|
||||
if (params.output_color == 4) {
|
||||
img.setColorSpace(QColorSpace(QColorSpace::ProPhotoRgb));
|
||||
}
|
||||
}
|
||||
|
||||
// *** Set the metadata
|
||||
auto &&iparams = rawProcessor->imgdata.idata;
|
||||
|
||||
auto xmpPacket = QString();
|
||||
if (auto xmpdata = iparams.xmpdata) {
|
||||
if (auto xmplen = iparams.xmplen)
|
||||
xmpPacket = QString::fromUtf8(xmpdata, xmplen);
|
||||
}
|
||||
// Add info from LibRAW structs (e.g. GPS position, info about lens, info about shot and flash, etc...)
|
||||
img.setText(QStringLiteral("XML:com.adobe.xmp"), updateXmpPacket(xmpPacket, rawProcessor.get()));
|
||||
|
||||
auto model = QString::fromUtf8(iparams.normalized_model);
|
||||
if (!model.isEmpty()) {
|
||||
img.setText(QStringLiteral("Model"), model);
|
||||
}
|
||||
auto manufacturer = QString::fromUtf8(iparams.normalized_make);
|
||||
if (!manufacturer.isEmpty()) {
|
||||
img.setText(QStringLiteral("Manufacturer"), manufacturer);
|
||||
}
|
||||
auto software = QString::fromUtf8(iparams.software);
|
||||
if (!software.isEmpty()) {
|
||||
img.setText(QStringLiteral("Software"), software);
|
||||
}
|
||||
|
||||
auto &&iother = rawProcessor->imgdata.other;
|
||||
auto description = QString::fromUtf8(iother.desc);
|
||||
if (!description.isEmpty()) {
|
||||
img.setText(QStringLiteral("Description"), description);
|
||||
}
|
||||
auto artist = QString::fromUtf8(iother.artist);
|
||||
if (!artist.isEmpty()) {
|
||||
img.setText(QStringLiteral("Author"), artist);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // Private
|
||||
|
||||
RAWHandler::RAWHandler()
|
||||
: m_imageNumber(0)
|
||||
, m_imageCount(0)
|
||||
, m_quality(-1)
|
||||
{
|
||||
}
|
||||
|
||||
bool RAWHandler::canRead() const
|
||||
{
|
||||
if (canRead(device())) {
|
||||
setFormat("raw");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RAWHandler::read(QImage *image)
|
||||
{
|
||||
auto dev = device();
|
||||
|
||||
// Check image file format.
|
||||
if (dev->atEnd()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QImage img;
|
||||
if (!LoadRAW(this, img)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*image = img;
|
||||
return true;
|
||||
}
|
||||
|
||||
void RAWHandler::setOption(ImageOption option, const QVariant &value)
|
||||
{
|
||||
if (option == QImageIOHandler::Quality) {
|
||||
bool ok = false;
|
||||
auto q = value.toInt(&ok);
|
||||
if (ok) {
|
||||
m_quality = q;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool RAWHandler::supportsOption(ImageOption option) const
|
||||
{
|
||||
#ifndef EXCLUDE_LibRaw_QIODevice
|
||||
if (option == QImageIOHandler::Size) {
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (option == QImageIOHandler::Quality) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
QVariant RAWHandler::option(ImageOption option) const
|
||||
{
|
||||
QVariant v;
|
||||
|
||||
#ifndef EXCLUDE_LibRaw_QIODevice
|
||||
if (option == QImageIOHandler::Size) {
|
||||
auto d = device();
|
||||
d->startTransaction();
|
||||
std::unique_ptr<LibRaw> rawProcessor(new LibRaw);
|
||||
LibRaw_QIODevice stream(d);
|
||||
#if (LIBRAW_VERSION < LIBRAW_MAKE_VERSION(0, 21, 0))
|
||||
rawProcessor->imgdata.params.shot_select = currentImageNumber();
|
||||
#else
|
||||
rawProcessor->imgdata.rawparams.shot_select = currentImageNumber();
|
||||
#endif
|
||||
if (rawProcessor->open_datastream(&stream) == LIBRAW_SUCCESS) {
|
||||
if (rawProcessor->unpack() == LIBRAW_SUCCESS) {
|
||||
auto w = libraw_get_iwidth(&rawProcessor->imgdata);
|
||||
auto h = libraw_get_iheight(&rawProcessor->imgdata);
|
||||
// flip & 4: taken from LibRaw code
|
||||
v = (rawProcessor->imgdata.sizes.flip & 4) ? QSize(h, w) : QSize(w, h);
|
||||
}
|
||||
}
|
||||
d->rollbackTransaction();
|
||||
}
|
||||
#endif
|
||||
|
||||
if (option == QImageIOHandler::Quality) {
|
||||
v = m_quality;
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
bool RAWHandler::jumpToNextImage()
|
||||
{
|
||||
return jumpToImage(m_imageNumber + 1);
|
||||
}
|
||||
|
||||
bool RAWHandler::jumpToImage(int imageNumber)
|
||||
{
|
||||
if (imageNumber >= imageCount()) {
|
||||
return false;
|
||||
}
|
||||
m_imageNumber = imageNumber;
|
||||
return true;
|
||||
}
|
||||
|
||||
int RAWHandler::imageCount() const
|
||||
{
|
||||
// NOTE: image count is cached for performance reason
|
||||
auto &&count = m_imageCount;
|
||||
if (count > 0) {
|
||||
return count;
|
||||
}
|
||||
|
||||
count = QImageIOHandler::imageCount();
|
||||
|
||||
#ifndef EXCLUDE_LibRaw_QIODevice
|
||||
auto d = device();
|
||||
d->startTransaction();
|
||||
|
||||
std::unique_ptr<LibRaw> rawProcessor(new LibRaw);
|
||||
LibRaw_QIODevice stream(d);
|
||||
if (rawProcessor->open_datastream(&stream) == LIBRAW_SUCCESS) {
|
||||
count = rawProcessor->imgdata.rawdata.iparams.raw_count;
|
||||
}
|
||||
|
||||
d->rollbackTransaction();
|
||||
#endif
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
int RAWHandler::currentImageNumber() const
|
||||
{
|
||||
return m_imageNumber;
|
||||
}
|
||||
|
||||
bool RAWHandler::canRead(QIODevice *device)
|
||||
{
|
||||
device->startTransaction();
|
||||
|
||||
std::unique_ptr<LibRaw> rawProcessor(new LibRaw);
|
||||
|
||||
#ifndef EXCLUDE_LibRaw_QIODevice
|
||||
LibRaw_QIODevice stream(device);
|
||||
auto ok = rawProcessor->open_datastream(&stream) == LIBRAW_SUCCESS;
|
||||
#else
|
||||
auto ba = device->readAll();
|
||||
auto ok = rawProcessor->open_buffer(ba.data(), ba.size()) == LIBRAW_SUCCESS;
|
||||
#endif
|
||||
|
||||
device->rollbackTransaction();
|
||||
return ok;
|
||||
}
|
||||
|
||||
QImageIOPlugin::Capabilities RAWPlugin::capabilities(QIODevice *device, const QByteArray &format) const
|
||||
{
|
||||
if (supported_formats.contains(QByteArray(format).toLower())) {
|
||||
return Capabilities(CanRead);
|
||||
}
|
||||
if (!format.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
if (!device->isOpen()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
Capabilities cap;
|
||||
if (device->isReadable() && RAWHandler::canRead(device)) {
|
||||
cap |= CanRead;
|
||||
}
|
||||
return cap;
|
||||
}
|
||||
|
||||
QImageIOHandler *RAWPlugin::create(QIODevice *device, const QByteArray &format) const
|
||||
{
|
||||
QImageIOHandler *handler = new RAWHandler;
|
||||
handler->setDevice(device);
|
||||
handler->setFormat(format);
|
||||
return handler;
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
[Desktop Entry]
|
||||
Type=Service
|
||||
X-KDE-ServiceTypes=QImageIOPlugins
|
||||
X-KDE-ImageFormat=3fr,arw,arq,bay,bmq,crw,cr2,cr3,cap,cine,cs1,dcs,dc2,dcr,dng,drf,dxo,eip,erf,fff,hdr,iiq,k25,kdc,kc2,mdc,mef,mfw,mos,mrw,nef,nrw,obm,orf,ori,pef,ptx,pxn,qtk,r3d,raf,raw,rdc,rwl,rw2,rwz,sr2,srf,srw,sti,x3f
|
||||
X-KDE-MimeType=image/x-hasselblad-3fr,image/x-sony-arw,image/x-arq,image/x-bay,image/x-bmq,image/x-canon-crw,image/x-canon-cr2,image/x-canon-cr3,image/x-cap,image/x-cine,image/x-cs1,image/x-kodak-dcs,image/x-dc2,image/x-kodak-dcr,image/x-adobe-dng,image/x-drf,image/x-dxo,image/x-epson-eip,image/x-epson-erf,image/x-fff,image/x-hdr,image/x-iiq,image/x-kodak-k25,image/x-kodak-kdc,image/x-kodak-kc2,image/x-minolta-mdc,image/x-mamiya-mef,image/x-mfw,image/x-aptus-mos,image/x-minolta-mrw,image/x-nikon-nef,image/x-nikon-nrw,image/x-obm,image/x-olympus-orf,image/x-ori,image/x-pentax-pef,image/x-ptx,image/x-pxn,image/x-qtk,image/x-r3d,image/x-fuji-raf,image/x-raw,image/x-rdc,image/x-rwl,image/x-panasonic-rw2,image/x-rwz,image/x-sony-sr2,image/x-sony-srf,image/x-samsung-srw,image/x-sti,image/x-sigma-x3f
|
||||
X-KDE-Read=true
|
||||
X-KDE-Write=false
|
||||
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"Keys": [
|
||||
"3fr",
|
||||
"arw", "arq",
|
||||
"bay", "bmq",
|
||||
"crw", "cr2", "cr3", "cap", "cine", "cs1",
|
||||
"dcs", "dc2", "dcr", "dng", "drf", "dxo",
|
||||
"eip", "erf",
|
||||
"fff",
|
||||
"hdr",
|
||||
"iiq",
|
||||
"k25", "kdc", "kc2",
|
||||
"mdc", "mef", "mfw", "mos", "mrw",
|
||||
"nef", "nrw",
|
||||
"obm", "orf", "ori",
|
||||
"pef", "ptx", "pxn",
|
||||
"qtk",
|
||||
"r3d", "raf", "raw", "rdc", "rwl", "rw2", "rwz",
|
||||
"sr2", "srf", "srw", "sti",
|
||||
"x3f"
|
||||
],
|
||||
"MimeTypes": [
|
||||
"image/x-hasselblad-3fr",
|
||||
"image/x-sony-arw", "image/x-arq",
|
||||
"image/x-bay", "image/x-bmq",
|
||||
"image/x-canon-crw", "image/x-canon-cr2", "image/x-canon-cr3", "image/x-cap", "image/x-cine", "image/x-cs1",
|
||||
"image/x-kodak-dcs", "image/x-dc2", "image/x-kodak-dcr", "image/x-adobe-dng", "image/x-drf", "image/x-dxo",
|
||||
"image/x-epson-eip", "image/x-epson-erf",
|
||||
"image/x-fff",
|
||||
"image/x-hdr",
|
||||
"image/x-iiq",
|
||||
"image/x-kodak-k25", "image/x-kodak-kdc", "image/x-kodak-kc2",
|
||||
"image/x-minolta-mdc", "image/x-mamiya-mef", "image/x-mfw", "image/x-aptus-mos", "image/x-minolta-mrw",
|
||||
"image/x-nikon-nef", "image/x-nikon-nrw",
|
||||
"image/x-obm", "image/x-olympus-orf", "image/x-ori",
|
||||
"image/x-pentax-pef", "image/x-ptx", "image/x-pxn",
|
||||
"image/x-qtk",
|
||||
"image/x-r3d", "image/x-fuji-raf", "image/x-raw", "image/x-rdc", "image/x-rwl", "image/x-panasonic-rw2", "image/x-rwz",
|
||||
"image/x-sony-sr2", "image/x-sony-srf", "image/x-samsung-srw", "image/x-sti",
|
||||
"image/x-sigma-x3f"
|
||||
]
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
/*
|
||||
This file is part of the KDE project
|
||||
|
||||
SPDX-FileCopyrightText: 2022 Mirco Miranda <mircomir@outlook.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef KIMG_RAW_P_H
|
||||
#define KIMG_RAW_P_H
|
||||
|
||||
#include <QImageIOPlugin>
|
||||
|
||||
class RAWHandler : public QImageIOHandler
|
||||
{
|
||||
public:
|
||||
RAWHandler();
|
||||
|
||||
bool canRead() const override;
|
||||
bool read(QImage *image) override;
|
||||
|
||||
void setOption(ImageOption option, const QVariant &value) override;
|
||||
bool supportsOption(QImageIOHandler::ImageOption option) const override;
|
||||
QVariant option(QImageIOHandler::ImageOption option) const override;
|
||||
|
||||
bool jumpToNextImage() override;
|
||||
bool jumpToImage(int imageNumber) override;
|
||||
int imageCount() const override;
|
||||
int currentImageNumber() const override;
|
||||
|
||||
static bool canRead(QIODevice *device);
|
||||
|
||||
private:
|
||||
qint32 m_imageNumber;
|
||||
|
||||
mutable qint32 m_imageCount;
|
||||
|
||||
/**
|
||||
* @brief m_quality
|
||||
* Change the quality of the conversion. If -1, default quality is used.
|
||||
* @note Verify that the quality change support has been compiled with supportsOption()
|
||||
*
|
||||
* 3 2 1 0
|
||||
* 1 0 9 8 7 6 5 4 3 2 1 0 9 87 6 5 4 3 2 1098 7654 3210
|
||||
* _ _ _ _ _ _ _ _ _ _ _ S F NN E H B A W CCCC IIII PPPP
|
||||
*
|
||||
* Where:
|
||||
*
|
||||
* _: reserved
|
||||
* P: preset values: *** if set, other flags are ignored! ***
|
||||
* - 0: Use other flags (no preset)
|
||||
* - 1: I = 0, C = 1, B = 0, W = 1, A = 1, H = 1 (Linear, sRGB, 8-bits, Camera White, Auto White, Half-size)
|
||||
* - 2: I = 0, C = 1, B = 0, W = 1, A = 1, H = 0 (Linear, sRGB, 8-bits, Camera White, Auto White)
|
||||
* - 3: I = 3, C = 1, B = 0, W = 1, A = 1, H = 0 (AHD, sRGB, 8-bits, Camera White, Auto White)
|
||||
* - 4: I = 3, C = 1, B = 1, W = 1, A = 1, H = 0 (AHD, sRGB, 16-bits, Camera White, Auto White)
|
||||
* - 5: I = 3, C = 2, B = 1, W = 1, A = 1, H = 0 (AHD, Adobe, 16-bits, Camera White, Auto White)
|
||||
* - 6: I = 3, C = 4, B = 1, W = 1, A = 1, H = 0 (AHD, ProPhoto, 16-bits, Camera White, Auto White)
|
||||
* - 7: I = 11, C = 1, B = 0, W = 1, A = 1, H = 0 (DHT, sRGB, 8-bits, Camera White, Auto White)
|
||||
* - 8: I = 11, C = 1, B = 1, W = 1, A = 1, H = 0 (DHT, sRGB, 16-bits, Camera White, Auto White)
|
||||
* - 9: I = 11, C = 2, B = 1, W = 1, A = 1, H = 0 (DHT, Adobe, 16-bits, Camera White, Auto White)
|
||||
* - 10: I = 11, C = 4, B = 1, W = 1, A = 1, H = 0 (DHT, ProPhoto, 16-bits, Camera White, Auto White)
|
||||
* - 11: reserved
|
||||
* - 12: reserved
|
||||
* - 13: reserved
|
||||
* - 14: reserved
|
||||
* - 15: reserved
|
||||
* I: interpolation quality (0 - linear, 1 - VNG, 2 - PPG, 3 - AHD, 4 - DCB, 11 - DHT, 12 - AAHD)
|
||||
* C: output colorspace (0 - raw, 1 - sRGB, 2 - Adobe, 3 - Wide, 4 - ProPhoto, 5 - XYZ, 6 - ACES, 7 - DCI-P3, 8 - Rec2020)
|
||||
* W: use camera white balace (0 - off, 1 - on)
|
||||
* A: use auto white balance (0 - off, 1 - on)
|
||||
* B: output bit per sample (0 - 8-bits, 1 - 16-bits)
|
||||
* H: half size image (0 - off, 1 - on)
|
||||
* E: DCB color enhance (0 - off, 1 - on)
|
||||
* N: FBDD noise reduction (0 - off, 1 - light, 2 - full)
|
||||
* F: Interpolate RGGB as four colors (0 - off, 1 - on)
|
||||
* S: Don't stretch or rotate raw pixels (0 - rotate and stretch, 1 - don't rotate and stretch)
|
||||
*
|
||||
* @note It is safe to set both W and A: W is used if camera white balance is found, otherwise A is used.
|
||||
*/
|
||||
qint32 m_quality;
|
||||
};
|
||||
|
||||
class RAWPlugin : public QImageIOPlugin
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QImageIOHandlerFactoryInterface" FILE "raw.json")
|
||||
|
||||
public:
|
||||
Capabilities capabilities(QIODevice *device, const QByteArray &format) const override;
|
||||
QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const override;
|
||||
};
|
||||
|
||||
#endif // KIMG_RAW_P_H
|
||||
@@ -20,7 +20,6 @@
|
||||
*/
|
||||
|
||||
#include "rgb_p.h"
|
||||
#include "util_p.h"
|
||||
|
||||
#include <QMap>
|
||||
#include <QVector>
|
||||
@@ -325,7 +324,7 @@ bool SGIImage::readImage(QImage &img)
|
||||
return false;
|
||||
}
|
||||
|
||||
img = imageAlloc(_xsize, _ysize, QImage::Format_RGB32);
|
||||
img = QImage(_xsize, _ysize, QImage::Format_RGB32);
|
||||
if (img.isNull()) {
|
||||
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(_xsize, _ysize);
|
||||
return false;
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
*/
|
||||
|
||||
#include "tga_p.h"
|
||||
#include "util_p.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
@@ -173,7 +172,7 @@ struct TgaHeaderInfo {
|
||||
static bool LoadTGA(QDataStream &s, const TgaHeader &tga, QImage &img)
|
||||
{
|
||||
// Create image.
|
||||
img = imageAlloc(tga.width, tga.height, QImage::Format_RGB32);
|
||||
img = QImage(tga.width, tga.height, QImage::Format_RGB32);
|
||||
if (img.isNull()) {
|
||||
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(tga.width, tga.height);
|
||||
return false;
|
||||
@@ -185,7 +184,7 @@ static bool LoadTGA(QDataStream &s, const TgaHeader &tga, QImage &img)
|
||||
const int numAlphaBits = tga.flags & 0xf;
|
||||
// However alpha exists only in the 32 bit format.
|
||||
if ((tga.pixel_size == 32) && (tga.flags & 0xf)) {
|
||||
img = imageAlloc(tga.width, tga.height, QImage::Format_ARGB32);
|
||||
img = QImage(tga.width, tga.height, QImage::Format_ARGB32);
|
||||
if (img.isNull()) {
|
||||
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(tga.width, tga.height);
|
||||
return false;
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2022 Albert Astals Cid <aacid@kde.org>
|
||||
SPDX-FileCopyrightText: 2022 Mirco Miranda <mircomir@outlook.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef UTIL_P_H
|
||||
#define UTIL_P_H
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include <QImage>
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
#include <QImageIOHandler>
|
||||
#endif
|
||||
|
||||
// QVector uses some extra space for stuff, hence the 32 here suggested by Thiago Macieira
|
||||
static constexpr int kMaxQVectorSize = std::numeric_limits<int>::max() - 32;
|
||||
|
||||
// On Qt 6 to make the plugins fail to allocate if the image size is greater than QImageReader::allocationLimit()
|
||||
// it is necessary to allocate the image with QImageIOHandler::allocateImage().
|
||||
inline QImage imageAlloc(const QSize &size, const QImage::Format &format)
|
||||
{
|
||||
QImage img;
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
img = QImage(size, format);
|
||||
#else
|
||||
if (!QImageIOHandler::allocateImage(size, format, &img)) {
|
||||
img = QImage(); // paranoia
|
||||
}
|
||||
#endif
|
||||
return img;
|
||||
}
|
||||
|
||||
inline QImage imageAlloc(qint32 width, qint32 height, const QImage::Format &format)
|
||||
{
|
||||
return imageAlloc(QSize(width, height), format);
|
||||
}
|
||||
|
||||
#endif // UTIL_P_H
|
||||
@@ -6,7 +6,6 @@
|
||||
SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
*/
|
||||
|
||||
#include "util_p.h"
|
||||
#include "xcf_p.h"
|
||||
|
||||
#include <QDebug>
|
||||
@@ -17,12 +16,8 @@
|
||||
#include <QStack>
|
||||
#include <QVector>
|
||||
#include <QtEndian>
|
||||
#include <QColorSpace>
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
#include <QImageReader>
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "gimp_p.h"
|
||||
@@ -139,7 +134,7 @@ public:
|
||||
PROP_SAMPLE_POINTS = 39,
|
||||
MAX_SUPPORTED_PROPTYPE, // should always be at the end so its value is last + 1
|
||||
};
|
||||
Q_ENUM(PropType)
|
||||
Q_ENUM(PropType);
|
||||
|
||||
//! Compression type used in layer tiles.
|
||||
enum XcfCompressionType {
|
||||
@@ -149,7 +144,7 @@ public:
|
||||
COMPRESS_ZLIB = 2, /* unused */
|
||||
COMPRESS_FRACTAL = 3, /* unused */
|
||||
};
|
||||
Q_ENUM(XcfCompressionType)
|
||||
Q_ENUM(XcfCompressionType);
|
||||
|
||||
enum LayerModeType {
|
||||
GIMP_LAYER_MODE_NORMAL_LEGACY,
|
||||
@@ -216,7 +211,7 @@ public:
|
||||
GIMP_LAYER_MODE_PASS_THROUGH,
|
||||
GIMP_LAYER_MODE_COUNT,
|
||||
};
|
||||
Q_ENUM(LayerModeType)
|
||||
Q_ENUM(LayerModeType);
|
||||
|
||||
//! Type of individual layers in an XCF file.
|
||||
enum GimpImageType {
|
||||
@@ -227,7 +222,7 @@ public:
|
||||
INDEXED_GIMAGE,
|
||||
INDEXEDA_GIMAGE,
|
||||
};
|
||||
Q_ENUM(GimpImageType)
|
||||
Q_ENUM(GimpImageType);
|
||||
|
||||
//! Type of individual layers in an XCF file.
|
||||
enum GimpColorSpace {
|
||||
@@ -356,8 +351,6 @@ private:
|
||||
bool initialized; //!< Is the QImage initialized?
|
||||
QImage image; //!< final QImage
|
||||
|
||||
QHash<QString,QByteArray> parasites; //!< parasites data
|
||||
|
||||
XCFImage(void)
|
||||
: initialized(false)
|
||||
{
|
||||
@@ -412,7 +405,6 @@ private:
|
||||
bool composeTiles(XCFImage &xcf_image);
|
||||
void setGrayPalette(QImage &image);
|
||||
void setPalette(XCFImage &xcf_image, QImage &image);
|
||||
void setImageParasites(const XCFImage &xcf_image, QImage &image);
|
||||
static void assignImageBytes(Layer &layer, uint i, uint j);
|
||||
bool loadHierarchy(QDataStream &xcf_io, Layer &layer);
|
||||
bool loadLevel(QDataStream &xcf_io, Layer &layer, qint32 bpp);
|
||||
@@ -673,9 +665,6 @@ bool XCFImageFormat::readXCF(QIODevice *device, QImage *outImage)
|
||||
return false;
|
||||
}
|
||||
|
||||
// The image was created: now I can set metadata and ICC color profile inside it.
|
||||
setImageParasites(xcf_image, xcf_image.image);
|
||||
|
||||
*outImage = xcf_image.image;
|
||||
return true;
|
||||
}
|
||||
@@ -726,15 +715,15 @@ bool XCFImageFormat::loadImageProperties(QDataStream &xcf_io, XCFImage &xcf_imag
|
||||
property.readBytes(tag, size);
|
||||
|
||||
quint32 flags;
|
||||
QByteArray data;
|
||||
char *data = nullptr;
|
||||
property >> flags >> data;
|
||||
|
||||
// WARNING: you cannot add metadata to QImage here because it can be null.
|
||||
// Adding a metadata to a QImage when it is null, does nothing (metas are lost).
|
||||
if(tag) // store metadata for future use
|
||||
xcf_image.parasites.insert(QString::fromUtf8(tag), data);
|
||||
if (tag && strncmp(tag, "gimp-comment", strlen("gimp-comment")) == 0) {
|
||||
xcf_image.image.setText(QStringLiteral("Comment"), QString::fromUtf8(data));
|
||||
}
|
||||
|
||||
delete[] tag;
|
||||
delete[] data;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -1097,27 +1086,12 @@ bool XCFImageFormat::composeTiles(XCFImage &xcf_image)
|
||||
qCDebug(XCFPLUGIN) << "LAYER: height=" << layer.height << ", width=" << layer.width;
|
||||
qCDebug(XCFPLUGIN) << "LAYER: rows=" << layer.nrows << ", columns=" << layer.ncols;
|
||||
|
||||
// NOTE: starting from GIMP 2.10, images can be very large. The 32K limit for width and height is obsolete
|
||||
// and it was changed to 300000 (the same as Photoshop Big image). This plugin was able to open an RGB
|
||||
// image of 108000x40000 pixels saved with GIMP 2.10
|
||||
// SANITY CHECK: Catch corrupted XCF image file where the width or height
|
||||
// of a tile is reported are bogus. See Bug# 234030.
|
||||
if (layer.width > 300000 || layer.height > 300000 || (sizeof(void *) == 4 && layer.width * layer.height > 16384 * 16384)) {
|
||||
if (layer.width > 32767 || layer.height > 32767 || (sizeof(void *) == 4 && layer.width * layer.height > 16384 * 16384)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Qt 6 image allocation limit calculation: we have to check the limit here because the image is splitted in
|
||||
// tiles of 64x64 pixels. The required memory to build the image is at least doubled because tiles are loaded
|
||||
// and then the final image is created by copying the tiles inside it.
|
||||
// NOTE: on Windows to open a 10GiB image the plugin uses 28GiB of RAM
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
qint64 channels = 1 + (layer.type == RGB_GIMAGE ? 2 : 0) + (layer.type == RGBA_GIMAGE ? 3 : 0);
|
||||
if (qint64(layer.width) * qint64(layer.height) * channels * 2ll / 1024ll / 1024ll > QImageReader::allocationLimit()) {
|
||||
qCDebug(XCFPLUGIN) << "Rejecting image as it exceeds the current allocation limit of" << QImageReader::allocationLimit() << "megabytes";
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
layer.image_tiles.resize(layer.nrows);
|
||||
|
||||
if (layer.type == GRAYA_GIMAGE || layer.type == INDEXEDA_GIMAGE) {
|
||||
@@ -1260,77 +1234,6 @@ void XCFImageFormat::setPalette(XCFImage &xcf_image, QImage &image)
|
||||
image.setColorTable(xcf_image.palette);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Copy the parasites info to QImage.
|
||||
* \param xcf_image XCF image containing the parasites read from the data stream.
|
||||
* \param image image to apply the parasites data.
|
||||
* \note Some comment taken from https://gitlab.gnome.org/GNOME/gimp/-/blob/master/devel-docs/parasites.txt
|
||||
*/
|
||||
void XCFImageFormat::setImageParasites(const XCFImage &xcf_image, QImage &image)
|
||||
{
|
||||
auto&& p = xcf_image.parasites;
|
||||
auto keys = p.keys();
|
||||
for (auto &&key : std::as_const(keys)) {
|
||||
auto value = p.value(key);
|
||||
if(value.isEmpty())
|
||||
continue;
|
||||
|
||||
// "icc-profile" (IMAGE, PERSISTENT | UNDOABLE)
|
||||
// This contains an ICC profile describing the color space the
|
||||
// image was produced in. TIFF images stored in PhotoShop do
|
||||
// oftentimes contain embedded profiles. An experimental color
|
||||
// manager exists to use this parasite, and it will be used
|
||||
// for interchange between TIFF and PNG (identical profiles)
|
||||
if (key == QStringLiteral("icc-profile")) {
|
||||
auto cs = QColorSpace::fromIccProfile(value);
|
||||
if(cs.isValid())
|
||||
image.setColorSpace(cs);
|
||||
continue;
|
||||
}
|
||||
|
||||
// "gimp-comment" (IMAGE, PERSISTENT)
|
||||
// Standard GIF-style image comments. This parasite should be
|
||||
// human-readable text in UTF-8 encoding. A trailing \0 might
|
||||
// be included and is not part of the comment. Note that image
|
||||
// comments may also be present in the "gimp-metadata" parasite.
|
||||
if (key == QStringLiteral("gimp-comment")) {
|
||||
value.replace('\0', QByteArray());
|
||||
image.setText(QStringLiteral("Comment"), QString::fromUtf8(value));
|
||||
continue;
|
||||
}
|
||||
|
||||
// "gimp-image-metadata"
|
||||
// Saved by GIMP 2.10.30 but it is not mentioned in the specification.
|
||||
// It is an XML block with the properties set using GIMP.
|
||||
if (key == QStringLiteral("gimp-image-metadata")) {
|
||||
// NOTE: I arbitrary defined the metadata "XML:org.gimp.xml" because it seems
|
||||
// a GIMP proprietary XML format (no xmlns defined)
|
||||
value.replace('\0', QByteArray());
|
||||
image.setText(QStringLiteral("XML:org.gimp.xml"), QString::fromUtf8(value));
|
||||
continue;
|
||||
}
|
||||
|
||||
#if 0 // Unable to generate it using latest GIMP version
|
||||
// "gimp-metadata" (IMAGE, PERSISTENT)
|
||||
// The metadata associated with the image, serialized as one XMP
|
||||
// packet. This metadata includes the contents of any XMP, EXIF
|
||||
// and IPTC blocks from the original image, as well as
|
||||
// user-specified values such as image comment, copyright,
|
||||
// license, etc.
|
||||
if (key == QStringLiteral("gimp-metadata")) {
|
||||
// NOTE: "XML:com.adobe.xmp" is the meta set by Qt reader when an
|
||||
// XMP packet is found (e.g. when reading a PNG saved by Photoshop).
|
||||
// I reused the same key because some programs could search for it.
|
||||
value.replace('\0', QByteArray());
|
||||
image.setText(QStringLiteral("XML:com.adobe.xmp"), QString::fromUtf8(value));
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
* Copy the bytes from the tile buffer into the image tile QImage, taking into
|
||||
* account all the myriad different modes.
|
||||
@@ -1936,7 +1839,7 @@ bool XCFImageFormat::initializeImage(XCFImage &xcf_image)
|
||||
switch (layer.type) {
|
||||
case RGB_GIMAGE:
|
||||
if (layer.opacity == OPAQUE_OPACITY) {
|
||||
image = imageAlloc(xcf_image.width, xcf_image.height, QImage::Format_RGB32);
|
||||
image = QImage(xcf_image.width, xcf_image.height, QImage::Format_RGB32);
|
||||
if (image.isNull()) {
|
||||
return false;
|
||||
}
|
||||
@@ -1945,7 +1848,7 @@ bool XCFImageFormat::initializeImage(XCFImage &xcf_image)
|
||||
} // else, fall through to 32-bit representation
|
||||
Q_FALLTHROUGH();
|
||||
case RGBA_GIMAGE:
|
||||
image = imageAlloc(xcf_image.width, xcf_image.height, QImage::Format_ARGB32);
|
||||
image = QImage(xcf_image.width, xcf_image.height, QImage::Format_ARGB32);
|
||||
if (image.isNull()) {
|
||||
return false;
|
||||
}
|
||||
@@ -1954,7 +1857,7 @@ bool XCFImageFormat::initializeImage(XCFImage &xcf_image)
|
||||
|
||||
case GRAY_GIMAGE:
|
||||
if (layer.opacity == OPAQUE_OPACITY) {
|
||||
image = imageAlloc(xcf_image.width, xcf_image.height, QImage::Format_Indexed8);
|
||||
image = QImage(xcf_image.width, xcf_image.height, QImage::Format_Indexed8);
|
||||
image.setColorCount(256);
|
||||
if (image.isNull()) {
|
||||
return false;
|
||||
@@ -1965,7 +1868,7 @@ bool XCFImageFormat::initializeImage(XCFImage &xcf_image)
|
||||
} // else, fall through to 32-bit representation
|
||||
Q_FALLTHROUGH();
|
||||
case GRAYA_GIMAGE:
|
||||
image = imageAlloc(xcf_image.width, xcf_image.height, QImage::Format_ARGB32);
|
||||
image = QImage(xcf_image.width, xcf_image.height, QImage::Format_ARGB32);
|
||||
if (image.isNull()) {
|
||||
return false;
|
||||
}
|
||||
@@ -1986,7 +1889,7 @@ bool XCFImageFormat::initializeImage(XCFImage &xcf_image)
|
||||
// or two-color palette. Have to ask about this...
|
||||
|
||||
if (xcf_image.num_colors <= 2) {
|
||||
image = imageAlloc(xcf_image.width, xcf_image.height, QImage::Format_MonoLSB);
|
||||
image = QImage(xcf_image.width, xcf_image.height, QImage::Format_MonoLSB);
|
||||
image.setColorCount(xcf_image.num_colors);
|
||||
if (image.isNull()) {
|
||||
return false;
|
||||
@@ -1994,7 +1897,7 @@ bool XCFImageFormat::initializeImage(XCFImage &xcf_image)
|
||||
image.fill(0);
|
||||
setPalette(xcf_image, image);
|
||||
} else if (xcf_image.num_colors <= 256) {
|
||||
image = imageAlloc(xcf_image.width, xcf_image.height, QImage::Format_Indexed8);
|
||||
image = QImage(xcf_image.width, xcf_image.height, QImage::Format_Indexed8);
|
||||
image.setColorCount(xcf_image.num_colors);
|
||||
if (image.isNull()) {
|
||||
return false;
|
||||
@@ -2012,7 +1915,7 @@ bool XCFImageFormat::initializeImage(XCFImage &xcf_image)
|
||||
xcf_image.palette[1] = xcf_image.palette[0];
|
||||
xcf_image.palette[0] = qRgba(255, 255, 255, 0);
|
||||
|
||||
image = imageAlloc(xcf_image.width, xcf_image.height, QImage::Format_MonoLSB);
|
||||
image = QImage(xcf_image.width, xcf_image.height, QImage::Format_MonoLSB);
|
||||
image.setColorCount(xcf_image.num_colors);
|
||||
if (image.isNull()) {
|
||||
return false;
|
||||
@@ -2028,7 +1931,7 @@ bool XCFImageFormat::initializeImage(XCFImage &xcf_image)
|
||||
}
|
||||
|
||||
xcf_image.palette[0] = qRgba(255, 255, 255, 0);
|
||||
image = imageAlloc(xcf_image.width, xcf_image.height, QImage::Format_Indexed8);
|
||||
image = QImage(xcf_image.width, xcf_image.height, QImage::Format_Indexed8);
|
||||
image.setColorCount(xcf_image.num_colors);
|
||||
if (image.isNull()) {
|
||||
return false;
|
||||
@@ -2039,7 +1942,7 @@ bool XCFImageFormat::initializeImage(XCFImage &xcf_image)
|
||||
// No room for a transparent color, so this has to be promoted to
|
||||
// true color. (There is no equivalent PNG representation output
|
||||
// from The GIMP as of v1.2.)
|
||||
image = imageAlloc(xcf_image.width, xcf_image.height, QImage::Format_ARGB32);
|
||||
image = QImage(xcf_image.width, xcf_image.height, QImage::Format_ARGB32);
|
||||
if (image.isNull()) {
|
||||
return false;
|
||||
}
|
||||
@@ -2050,11 +1953,11 @@ bool XCFImageFormat::initializeImage(XCFImage &xcf_image)
|
||||
|
||||
if (xcf_image.x_resolution > 0 && xcf_image.y_resolution > 0) {
|
||||
const float dpmx = xcf_image.x_resolution * INCHESPERMETER;
|
||||
if (dpmx > float(std::numeric_limits<int>::max())) {
|
||||
if (dpmx > std::numeric_limits<int>::max()) {
|
||||
return false;
|
||||
}
|
||||
const float dpmy = xcf_image.y_resolution * INCHESPERMETER;
|
||||
if (dpmy > float(std::numeric_limits<int>::max())) {
|
||||
if (dpmy > std::numeric_limits<int>::max()) {
|
||||
return false;
|
||||
}
|
||||
image.setDotsPerMeterX((int)dpmx);
|
||||
@@ -3289,52 +3192,6 @@ bool XCFHandler::write(const QImage &)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool XCFHandler::supportsOption(ImageOption option) const
|
||||
{
|
||||
if (option == QImageIOHandler::Size)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
QVariant XCFHandler::option(ImageOption option) const
|
||||
{
|
||||
QVariant v;
|
||||
|
||||
if (option == QImageIOHandler::Size) {
|
||||
/*
|
||||
* The image structure always starts at offset 0 in the XCF file.
|
||||
* byte[9] "gimp xcf " File type identification
|
||||
* byte[4] version XCF version
|
||||
* "file": version 0
|
||||
* "v001": version 1
|
||||
* "v002": version 2
|
||||
* "v003": version 3
|
||||
* byte 0 Zero marks the end of the version tag.
|
||||
* uint32 width Width of canvas
|
||||
* uint32 height Height of canvas
|
||||
*/
|
||||
if (auto d = device()) {
|
||||
// transactions works on both random and sequential devices
|
||||
d->startTransaction();
|
||||
auto ba9 = d->read(9); // "gimp xcf "
|
||||
auto ba5 = d->read(4+1); // version + null terminator
|
||||
auto ba = d->read(8); // width and height
|
||||
d->rollbackTransaction();
|
||||
if (ba9 == QByteArray("gimp xcf ") && ba5.size() == 5) {
|
||||
QDataStream ds(ba);
|
||||
quint32 width;
|
||||
ds >> width;
|
||||
quint32 height;
|
||||
ds >> height;
|
||||
if (ds.status() == QDataStream::Ok)
|
||||
v = QVariant::fromValue(QSize(width, height));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
bool XCFHandler::canRead(QIODevice *device)
|
||||
{
|
||||
if (!device) {
|
||||
|
||||
@@ -20,9 +20,6 @@ public:
|
||||
bool read(QImage *image) override;
|
||||
bool write(const QImage &image) override;
|
||||
|
||||
bool supportsOption(QImageIOHandler::ImageOption option) const override;
|
||||
QVariant option(QImageIOHandler::ImageOption option) const override;
|
||||
|
||||
static bool canRead(QIODevice *device);
|
||||
};
|
||||
|
||||
|
||||