Compare commits

..

1 Commits

Author SHA1 Message Date
Fushan Wen
485e084fa9 QImage: use rvalue overloads more
to reuse internal buffers
2023-09-16 23:29:30 +08:00
43 changed files with 219 additions and 1181 deletions

View File

@@ -2,9 +2,7 @@
# SPDX-License-Identifier: CC0-1.0 # SPDX-License-Identifier: CC0-1.0
include: include:
- project: sysadmin/ci-utilities - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux-qt6.yml
file: - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/android-qt6.yml
- /gitlab-templates/linux-qt6.yml - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/freebsd-qt6.yml
- /gitlab-templates/android-qt6.yml - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/windows-qt6.yml
- /gitlab-templates/freebsd-qt6.yml
- /gitlab-templates/windows-qt6.yml

View File

@@ -1,5 +1,5 @@
Dependencies: Dependencies:
- 'on': ['Linux', 'FreeBSD', 'macOS', 'Windows', 'Android'] - 'on': ['Linux', 'FreeBSD', 'macOS', 'Windows']
'require': 'require':
'frameworks/extra-cmake-modules': '@same' 'frameworks/extra-cmake-modules': '@same'
'frameworks/karchive' : '@same' 'frameworks/karchive' : '@same'

View File

@@ -1,11 +1,9 @@
cmake_minimum_required(VERSION 3.16) cmake_minimum_required(VERSION 3.16)
set(KF_VERSION "6.3.0") # handled by release scripts project(KImageFormats)
set(KF_DEP_VERSION "6.3.0") # handled by release scripts
project(KImageFormats VERSION ${KF_VERSION})
include(FeatureSummary) include(FeatureSummary)
find_package(ECM 6.3.0 NO_MODULE) find_package(ECM 5.240.0 NO_MODULE)
set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://commits.kde.org/extra-cmake-modules") set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://commits.kde.org/extra-cmake-modules")
feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES) feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES)
@@ -24,7 +22,7 @@ include(FindPkgConfig)
set(REQUIRED_QT_VERSION 6.5.0) set(REQUIRED_QT_VERSION 6.5.0)
find_package(Qt6Gui ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE) find_package(Qt6Gui ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE)
find_package(KF6Archive ${KF_DEP_VERSION}) find_package(KF6Archive)
set_package_properties(KF6Archive PROPERTIES set_package_properties(KF6Archive PROPERTIES
TYPE OPTIONAL TYPE OPTIONAL
PURPOSE "Required for the QImage plugin for Krita and OpenRaster images" PURPOSE "Required for the QImage plugin for Krita and OpenRaster images"
@@ -83,7 +81,7 @@ set_package_properties(LibRaw PROPERTIES
) )
ecm_set_disabled_deprecation_versions( ecm_set_disabled_deprecation_versions(
QT 6.5 QT 6.4
KF 5.102 KF 5.102
) )
@@ -93,7 +91,6 @@ if (BUILD_TESTING)
add_subdirectory(tests) add_subdirectory(tests)
endif() endif()
include(ECMFeatureSummary) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES)
ecm_feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES)
kde_configure_git_pre_commit_hook(CHECKS CLANG_FORMAT) kde_configure_git_pre_commit_hook(CHECKS CLANG_FORMAT)

View File

@@ -1,9 +0,0 @@
Copyright (c) <year> <owner>
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -1,11 +0,0 @@
Copyright (c) <year> <owner>.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -17,6 +17,7 @@ The following image formats have read-only support:
- Camera RAW images (arw, cr2, cr3, dcs, dng, ...) - Camera RAW images (arw, cr2, cr3, dcs, dng, ...)
- Gimp (xcf) - Gimp (xcf)
- Krita (kra) - Krita (kra)
- OpenEXR (exr)
- OpenRaster (ora) - OpenRaster (ora)
- Photoshop documents (psd, psb, pdd, psdt) - Photoshop documents (psd, psb, pdd, psdt)
- Radiance HDR (hdr) - Radiance HDR (hdr)
@@ -28,7 +29,6 @@ The following image formats have read and write support:
- Encapsulated PostScript (eps) - Encapsulated PostScript (eps)
- High Efficiency Image File Format (heif). Can be enabled with the KIMAGEFORMATS_HEIF build option. - High Efficiency Image File Format (heif). Can be enabled with the KIMAGEFORMATS_HEIF build option.
- JPEG XL (jxl) - JPEG XL (jxl)
- OpenEXR (exr)
- Personal Computer Exchange (pcx) - Personal Computer Exchange (pcx)
- Quite OK Image format (qoi) - Quite OK Image format (qoi)
- SGI images (rgb, rgba, sgi, bw) - SGI images (rgb, rgba, sgi, bw)
@@ -45,6 +45,10 @@ of Qt is the license. As such, if you write an imageformat plugin and
you are willing to sign the Qt Project contributor agreement, it may be you are willing to sign the Qt Project contributor agreement, it may be
better to submit the plugin directly to the Qt Project. better to submit the plugin directly to the Qt Project.
Note that the imageformat plugins provided by this module also provide a
desktop file. This is for the benefit of KImageIO in the KDE4 Support
framework.
## Duplicated Plugins ## Duplicated Plugins
The TGA plugin supports more formats than Qt's own TGA plugin; The TGA plugin supports more formats than Qt's own TGA plugin;

View File

@@ -97,12 +97,6 @@ if (LibHeif_FOUND)
kimageformats_write_tests(FUZZ 1 kimageformats_write_tests(FUZZ 1
heif-nodatacheck-lossless heif-nodatacheck-lossless
) )
if (LibHeif_VERSION VERSION_GREATER_EQUAL "1.17.0")
kimageformats_read_tests(FUZZ 1
hej2
)
endif()
endif() endif()
if (LibJXL_FOUND AND LibJXLThreads_FOUND) if (LibJXL_FOUND AND LibJXLThreads_FOUND)
@@ -144,11 +138,6 @@ if (OpenEXR_FOUND)
kimageformats_read_tests( kimageformats_read_tests(
exr exr
) )
# Color space conversions from sRGB to linear on saving and
# from linear to sRGB on loading result in some rounding errors.
kimageformats_write_tests(FUZZ 5
exr-nodatacheck-lossless
)
endif() endif()
if (LibRaw_FOUND) if (LibRaw_FOUND)

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 91 KiB

View File

@@ -7,7 +7,6 @@
#include <stdio.h> #include <stdio.h>
#include <QBuffer> #include <QBuffer>
#include <QColorSpace>
#include <QCommandLineParser> #include <QCommandLineParser>
#include <QCoreApplication> #include <QCoreApplication>
#include <QDir> #include <QDir>
@@ -24,8 +23,8 @@ int main(int argc, char **argv)
QCoreApplication app(argc, argv); QCoreApplication app(argc, argv);
QCoreApplication::removeLibraryPath(QStringLiteral(PLUGIN_DIR)); QCoreApplication::removeLibraryPath(QStringLiteral(PLUGIN_DIR));
QCoreApplication::addLibraryPath(QStringLiteral(PLUGIN_DIR)); QCoreApplication::addLibraryPath(QStringLiteral(PLUGIN_DIR));
QCoreApplication::setApplicationName(QStringLiteral("writetest")); QCoreApplication::setApplicationName(QStringLiteral("readtest"));
QCoreApplication::setApplicationVersion(QStringLiteral("1.1.0")); QCoreApplication::setApplicationVersion(QStringLiteral("1.0.0"));
QCommandLineParser parser; QCommandLineParser parser;
parser.setApplicationDescription(QStringLiteral("Performs basic image conversion checking.")); parser.setApplicationDescription(QStringLiteral("Performs basic image conversion checking."));
@@ -149,14 +148,6 @@ int main(int argc, char **argv)
++failed; ++failed;
continue; continue;
} }
if (reReadImage.colorSpace().isValid()) {
QColorSpace toColorSpace;
if (pngImage.colorSpace().isValid()) {
reReadImage.convertToColorSpace(pngImage.colorSpace());
} else {
reReadImage.convertToColorSpace(QColorSpace(QColorSpace::SRgb));
}
}
reReadImage = reReadImage.convertToFormat(pngImage.format()); reReadImage = reReadImage.convertToFormat(pngImage.format());
} }

View File

@@ -1,3 +1,4 @@
maintainer: alexmerry
description: Image format plugins for Qt description: Image format plugins for Qt
tier: 2 tier: 2
type: functional type: functional

View File

@@ -41,7 +41,7 @@ endif()
################################## ##################################
if(OpenEXR_FOUND) if(OpenEXR_FOUND)
kimageformats_add_plugin(kimg_exr SOURCES exr.cpp scanlineconverter.cpp) kimageformats_add_plugin(kimg_exr SOURCES exr.cpp)
if(TARGET OpenEXR::OpenEXR) if(TARGET OpenEXR::OpenEXR)
target_link_libraries(kimg_exr PRIVATE OpenEXR::OpenEXR) target_link_libraries(kimg_exr PRIVATE OpenEXR::OpenEXR)
else() else()

View File

@@ -383,7 +383,7 @@ bool ANIHandler::ensureScanned() const
// TODO should we check that the number of rate entries matches nSteps? // TODO should we check that the number of rate entries matches nSteps?
auto *dataPtr = data.data(); auto *dataPtr = data.data();
QList<int> list; QVector<int> list;
for (int i = 0; i < data.size(); i += sizeof(quint32_le)) { for (int i = 0; i < data.size(); i += sizeof(quint32_le)) {
const auto entry = *(reinterpret_cast<const quint32_le *>(dataPtr + i)); const auto entry = *(reinterpret_cast<const quint32_le *>(dataPtr + i));
list.append(entry); list.append(entry);

View File

@@ -41,14 +41,14 @@ private:
int m_frameCount = 0; // "physical" frames int m_frameCount = 0; // "physical" frames
int m_imageCount = 0; // logical images int m_imageCount = 0; // logical images
// Stores a custom sequence of images // Stores a custom sequence of images
QList<int> m_imageSequence; QVector<int> m_imageSequence;
// and the corresponding offsets where they are // and the corresponding offsets where they are
// since we can't read the image data sequentally in this case then // since we can't read the image data sequentally in this case then
QList<qint64> m_frameOffsets; QVector<qint64> m_frameOffsets;
qint64 m_firstFrameOffset = 0; qint64 m_firstFrameOffset = 0;
int m_displayRate = 0; int m_displayRate = 0;
QList<int> m_displayRates; QVector<int> m_displayRates;
QString m_name; QString m_name;
QString m_artist; QString m_artist;

View File

@@ -16,34 +16,9 @@
#include <cfloat> #include <cfloat>
/*
Quality range - compression/subsampling
100 - lossless RGB compression
< KIMG_AVIF_QUALITY_BEST, 100 ) - YUV444 color subsampling
< KIMG_AVIF_QUALITY_HIGH, KIMG_AVIF_QUALITY_BEST ) - YUV422 color subsampling
< 0, KIMG_AVIF_QUALITY_HIGH ) - YUV420 color subsampling
< 0, KIMG_AVIF_QUALITY_LOW ) - lossy compression of alpha channel
*/
#ifndef KIMG_AVIF_DEFAULT_QUALITY
#define KIMG_AVIF_DEFAULT_QUALITY 68
#endif
#ifndef KIMG_AVIF_QUALITY_BEST
#define KIMG_AVIF_QUALITY_BEST 90
#endif
#ifndef KIMG_AVIF_QUALITY_HIGH
#define KIMG_AVIF_QUALITY_HIGH 80
#endif
#ifndef KIMG_AVIF_QUALITY_LOW
#define KIMG_AVIF_QUALITY_LOW 51
#endif
QAVIFHandler::QAVIFHandler() QAVIFHandler::QAVIFHandler()
: m_parseState(ParseAvifNotParsed) : m_parseState(ParseAvifNotParsed)
, m_quality(KIMG_AVIF_DEFAULT_QUALITY) , m_quality(52)
, m_container_width(0) , m_container_width(0)
, m_container_height(0) , m_container_height(0)
, m_rawAvifData(AVIF_DATA_EMPTY) , m_rawAvifData(AVIF_DATA_EMPTY)
@@ -355,10 +330,6 @@ bool QAVIFHandler::decode_one_frame()
avifRGBImage rgb; avifRGBImage rgb;
avifRGBImageSetDefaults(&rgb, m_decoder->image); avifRGBImageSetDefaults(&rgb, m_decoder->image);
#if AVIF_VERSION >= 1000000
rgb.maxThreads = m_decoder->maxThreads;
#endif
if (m_decoder->image->depth > 8) { if (m_decoder->image->depth > 8) {
rgb.depth = 16; rgb.depth = 16;
rgb.format = AVIF_RGB_FORMAT_RGBA; rgb.format = AVIF_RGB_FORMAT_RGBA;
@@ -544,17 +515,9 @@ bool QAVIFHandler::write(const QImage &image)
} }
} }
if (m_quality > 100) {
m_quality = 100;
} else if (m_quality < 0) {
m_quality = KIMG_AVIF_DEFAULT_QUALITY;
}
#if AVIF_VERSION < 1000000
int maxQuantizer = AVIF_QUANTIZER_WORST_QUALITY * (100 - qBound(0, m_quality, 100)) / 100; int maxQuantizer = AVIF_QUANTIZER_WORST_QUALITY * (100 - qBound(0, m_quality, 100)) / 100;
int minQuantizer = 0; int minQuantizer = 0;
int maxQuantizerAlpha = 0; int maxQuantizerAlpha = 0;
#endif
avifResult res; avifResult res;
bool save_grayscale; // true - monochrome, false - colors bool save_grayscale; // true - monochrome, false - colors
@@ -600,15 +563,13 @@ bool QAVIFHandler::write(const QImage &image)
break; break;
} }
#if AVIF_VERSION < 1000000 // quality settings
// deprecated quality settings
if (maxQuantizer > 20) { if (maxQuantizer > 20) {
minQuantizer = maxQuantizer - 20; minQuantizer = maxQuantizer - 20;
if (maxQuantizer > 40) { // we decrease quality of alpha channel here if (maxQuantizer > 40) { // we decrease quality of alpha channel here
maxQuantizerAlpha = maxQuantizer - 40; maxQuantizerAlpha = maxQuantizer - 40;
} }
} }
#endif
if (save_grayscale && !image.hasAlphaChannel()) { // we are going to save grayscale image without alpha channel if (save_grayscale && !image.hasAlphaChannel()) { // we are going to save grayscale image without alpha channel
if (save_depth > 8) { if (save_depth > 8) {
@@ -681,8 +642,8 @@ bool QAVIFHandler::write(const QImage &image)
QImage tmpcolorimage = image.convertToFormat(tmpformat); QImage tmpcolorimage = image.convertToFormat(tmpformat);
avifPixelFormat pixel_format = AVIF_PIXEL_FORMAT_YUV420; avifPixelFormat pixel_format = AVIF_PIXEL_FORMAT_YUV420;
if (m_quality >= KIMG_AVIF_QUALITY_HIGH) { if (maxQuantizer < 20) {
if (m_quality >= KIMG_AVIF_QUALITY_BEST) { if (maxQuantizer < 10) {
pixel_format = AVIF_PIXEL_FORMAT_YUV444; // best quality pixel_format = AVIF_PIXEL_FORMAT_YUV444; // best quality
} else { } else {
pixel_format = AVIF_PIXEL_FORMAT_YUV422; // high quality pixel_format = AVIF_PIXEL_FORMAT_YUV422; // high quality
@@ -842,8 +803,6 @@ bool QAVIFHandler::write(const QImage &image)
avifRWData raw = AVIF_DATA_EMPTY; avifRWData raw = AVIF_DATA_EMPTY;
avifEncoder *encoder = avifEncoderCreate(); avifEncoder *encoder = avifEncoderCreate();
encoder->maxThreads = qBound(1, QThread::idealThreadCount(), 64); encoder->maxThreads = qBound(1, QThread::idealThreadCount(), 64);
#if AVIF_VERSION < 1000000
encoder->minQuantizer = minQuantizer; encoder->minQuantizer = minQuantizer;
encoder->maxQuantizer = maxQuantizer; encoder->maxQuantizer = maxQuantizer;
@@ -851,17 +810,6 @@ bool QAVIFHandler::write(const QImage &image)
encoder->minQuantizerAlpha = AVIF_QUANTIZER_LOSSLESS; encoder->minQuantizerAlpha = AVIF_QUANTIZER_LOSSLESS;
encoder->maxQuantizerAlpha = maxQuantizerAlpha; encoder->maxQuantizerAlpha = maxQuantizerAlpha;
} }
#else
encoder->quality = m_quality;
if (image.hasAlphaChannel()) {
if (m_quality >= KIMG_AVIF_QUALITY_LOW) {
encoder->qualityAlpha = 100;
} else {
encoder->qualityAlpha = 100 - (KIMG_AVIF_QUALITY_LOW - m_quality) / 2;
}
}
#endif
encoder->speed = 6; encoder->speed = 6;
@@ -918,7 +866,7 @@ void QAVIFHandler::setOption(ImageOption option, const QVariant &value)
if (m_quality > 100) { if (m_quality > 100) {
m_quality = 100; m_quality = 100;
} else if (m_quality < 0) { } else if (m_quality < 0) {
m_quality = KIMG_AVIF_DEFAULT_QUALITY; m_quality = 52;
} }
return; return;
default: default:
@@ -1091,11 +1039,6 @@ int QAVIFHandler::loopCount() const
return 0; return 0;
} }
#if AVIF_VERSION >= 1000000
if (m_decoder->repetitionCount >= 0) {
return m_decoder->repetitionCount;
}
#endif
// Endless loop to work around https://github.com/AOMediaCodec/libavif/issues/347 // Endless loop to work around https://github.com/AOMediaCodec/libavif/issues/347
return -1; return -1;
} }

View File

@@ -1,5 +1,6 @@
/* /*
The high dynamic range EXR format support for QImage. KImageIO Routines to read (and perhaps in the future, write) images
in the high dynamic range EXR format.
SPDX-FileCopyrightText: 2003 Brad Hards <bradh@frogmouth.net> SPDX-FileCopyrightText: 2003 Brad Hards <bradh@frogmouth.net>
SPDX-FileCopyrightText: 2023 Mirco Miranda <mircomir@outlook.com> SPDX-FileCopyrightText: 2023 Mirco Miranda <mircomir@outlook.com>
@@ -15,52 +16,15 @@
*/ */
//#define EXR_USE_LEGACY_CONVERSIONS // default commented -> you should define it in your cmake file //#define EXR_USE_LEGACY_CONVERSIONS // default commented -> you should define it in your cmake file
/* *** EXR_CONVERT_TO_SRGB *** /* *** EXR_ALLOW_LINEAR_COLORSPACE ***
* If defined, the linear data is converted to sRGB on read to accommodate * If defined, the linear data is kept and it is the display program that
* programs that do not support color profiles. * must convert to the monitor profile. Otherwise the data is converted to sRGB
* Otherwise the data are kept as is and it is the display program that * to accommodate programs that do not support color profiles.
* must convert to the monitor profile.
* NOTE: If EXR_USE_LEGACY_CONVERSIONS is active, this is ignored. * NOTE: If EXR_USE_LEGACY_CONVERSIONS is active, this is ignored.
*/ */
//#define EXR_CONVERT_TO_SRGB // default: commented -> you should define it in your cmake file //#define EXR_ALLOW_LINEAR_COLORSPACE // default: commented -> you should define it in your cmake file
/* *** EXR_STORE_XMP_ATTRIBUTE ***
* If defined, disables the stores XMP values in a non-standard attribute named "xmp".
* The QImage metadata used is "XML:com.adobe.xmp".
* NOTE: The use of non-standard attributes is possible but discouraged by the specification. However,
* metadata is essential for good image management and programs like darktable also set this
* attribute. Gimp reads the "xmp" attribute and Darktable writes it as well.
*/
//#define EXR_DISABLE_XMP_ATTRIBUTE // default: commented -> you should define it in your cmake file
/* *** EXR_MAX_IMAGE_WIDTH and EXR_MAX_IMAGE_HEIGHT ***
* The maximum size in pixel allowed by the plugin.
*/
#ifndef EXR_MAX_IMAGE_WIDTH
#define EXR_MAX_IMAGE_WIDTH 300000
#endif
#ifndef EXR_MAX_IMAGE_HEIGHT
#define EXR_MAX_IMAGE_HEIGHT EXR_MAX_IMAGE_WIDTH
#endif
/* *** EXR_LINES_PER_BLOCK ***
* Allows certain compression schemes to work in multithreading
* Requires up to "LINES_PER_BLOCK * MAX_IMAGE_WIDTH * 8"
* additional RAM (e.g. if 128, up to 307MiB of RAM).
* There is a performance gain with the following parameters (based on empirical tests):
* - PIZ compression needs 64+ lines
* - ZIPS compression needs 8+ lines
* - ZIP compression needs 32+ lines
* - Others not tested
*
* NOTE: The OpenEXR documentation states that the higher the better :)
*/
#ifndef EXR_LINES_PER_BLOCK
#define EXR_LINES_PER_BLOCK 128
#endif
#include "exr_p.h" #include "exr_p.h"
#include "scanlineconverter_p.h"
#include "util_p.h" #include "util_p.h"
#include <IexThrowErrnoExc.h> #include <IexThrowErrnoExc.h>
@@ -75,9 +39,10 @@
#include <ImfInt64.h> #include <ImfInt64.h>
#include <ImfIntAttribute.h> #include <ImfIntAttribute.h>
#include <ImfLineOrderAttribute.h> #include <ImfLineOrderAttribute.h>
#include <ImfPreviewImage.h>
#include <ImfRgbaFile.h> #include <ImfRgbaFile.h>
#include <ImfStandardAttributes.h> #include <ImfStandardAttributes.h>
#include <ImfStringAttribute.h>
#include <ImfVecAttribute.h>
#include <ImfVersion.h> #include <ImfVersion.h>
#include <iostream> #include <iostream>
@@ -88,13 +53,12 @@
#include <QFloat16> #include <QFloat16>
#include <QImage> #include <QImage>
#include <QImageIOPlugin> #include <QImageIOPlugin>
#include <QLocale>
#include <QThread>
#include <QTimeZone> #include <QTimeZone>
// Allow the code to works on all QT versions supported by KDE // Allow the code to works on all QT versions supported by KDE
// project (Qt 5.15 and Qt 6.x) to easy backports fixes. // project (Qt 5.15 and Qt 6.x) to easy backports fixes.
#if !defined(EXR_USE_LEGACY_CONVERSIONS) #if (QT_VERSION_MAJOR >= 6) && !defined(EXR_USE_LEGACY_CONVERSIONS)
// If uncommented, the image is rendered in a float16 format, the result is very precise // If uncommented, the image is rendered in a float16 format, the result is very precise
#define EXR_USE_QT6_FLOAT_IMAGE // default uncommented #define EXR_USE_QT6_FLOAT_IMAGE // default uncommented
#endif #endif
@@ -158,57 +122,6 @@ void K_IStream::clear()
// TODO // TODO
} }
class K_OStream : public Imf::OStream
{
public:
K_OStream(QIODevice *dev, const QByteArray &fileName)
: OStream(fileName.data())
, m_dev(dev)
{
}
void write(const char c[/*n*/], int n) override;
#if OPENEXR_VERSION_MAJOR > 2
uint64_t tellp() override;
void seekp(uint64_t pos) override;
#else
Imf::Int64 tellp() override;
void seekp(Imf::Int64 pos) override;
#endif
private:
QIODevice *m_dev;
};
void K_OStream::write(const char c[], int n)
{
qint64 result = m_dev->write(c, n);
if (result > 0) {
return;
} else { // negative value {
Iex::throwErrnoExc("Error in write", result);
}
return;
}
#if OPENEXR_VERSION_MAJOR > 2
uint64_t K_OStream::tellp()
#else
Imf::Int64 K_OStream::tellg()
#endif
{
return m_dev->pos();
}
#if OPENEXR_VERSION_MAJOR > 2
void K_OStream::seekp(uint64_t pos)
#else
void K_OStream::seekg(Imf::Int64 pos)
#endif
{
m_dev->seek(pos);
}
#ifdef EXR_USE_LEGACY_CONVERSIONS #ifdef EXR_USE_LEGACY_CONVERSIONS
// source: https://openexr.com/en/latest/ReadingAndWritingImageFiles.html // source: https://openexr.com/en/latest/ReadingAndWritingImageFiles.html
inline unsigned char gamma(float x) inline unsigned char gamma(float x)
@@ -226,14 +139,7 @@ inline QRgb RgbaToQrgba(struct Imf::Rgba &imagePixel)
#endif #endif
EXRHandler::EXRHandler() EXRHandler::EXRHandler()
: m_compressionRatio(-1)
, m_quality(-1)
, m_imageNumber(0)
, m_imageCount(0)
, m_startPos(-1)
{ {
// Set the number of threads to use (0 is allowed)
Imf::setGlobalThreadCount(QThread::idealThreadCount() / 2);
} }
bool EXRHandler::canRead() const bool EXRHandler::canRead() const
@@ -245,574 +151,113 @@ bool EXRHandler::canRead() const
return false; return false;
} }
static QImage::Format imageFormat(const Imf::RgbaInputFile &file) bool EXRHandler::read(QImage *outImage)
{ {
auto isRgba = file.channels() & Imf::RgbaChannels::WRITE_A; try {
int width;
int height;
K_IStream istr(device(), QByteArray());
Imf::RgbaInputFile file(istr);
Imath::Box2i dw = file.dataWindow();
bool isRgba = file.channels() & Imf::RgbaChannels::WRITE_A;
width = dw.max.x - dw.min.x + 1;
height = dw.max.y - dw.min.y + 1;
#if defined(EXR_USE_LEGACY_CONVERSIONS) #if defined(EXR_USE_LEGACY_CONVERSIONS)
return (isRgba ? QImage::Format_ARGB32 : QImage::Format_RGB32); QImage image = imageAlloc(width, height, isRgba ? QImage::Format_ARGB32 : QImage::Format_RGB32);
#elif defined(EXR_USE_QT6_FLOAT_IMAGE) #elif defined(EXR_USE_QT6_FLOAT_IMAGE)
return (isRgba ? QImage::Format_RGBA16FPx4 : QImage::Format_RGBX16FPx4); QImage image = imageAlloc(width, height, isRgba ? QImage::Format_RGBA16FPx4 : QImage::Format_RGBX16FPx4);
#else #else
return (isRgba ? QImage::Format_RGBA64 : QImage::Format_RGBX64); QImage image = imageAlloc(width, height, isRgba ? QImage::Format_RGBA64 : QImage::Format_RGBX64);
#endif #endif
if (image.isNull()) {
qWarning() << "Failed to allocate image, invalid size?" << QSize(width, height);
return false;
} }
/*!
* \brief viewList
* \param header
* \return The list of available views.
*/
static QStringList viewList(const Imf::Header &h)
{
QStringList l;
if (auto views = h.findTypedAttribute<Imf::StringVectorAttribute>("multiView")) {
for (auto &&v : views->value()) {
l << QString::fromStdString(v);
}
}
return l;
}
#ifdef QT_DEBUG
void printAttributes(const Imf::Header &h)
{
for (auto i = h.begin(); i != h.end(); ++i) {
qDebug() << i.name();
}
}
#endif
/*!
* \brief readMetadata
* Reads EXR attributes from the \a header and set its as metadata in the \a image.
*/
static void readMetadata(const Imf::Header &header, QImage &image)
{
// set some useful metadata // set some useful metadata
if (auto comments = header.findTypedAttribute<Imf::StringAttribute>("comments")) { auto &&h = file.header();
if (auto comments = h.findTypedAttribute<Imf::StringAttribute>("comments")) {
image.setText(QStringLiteral("Comment"), QString::fromStdString(comments->value())); image.setText(QStringLiteral("Comment"), QString::fromStdString(comments->value()));
} }
if (auto owner = h.findTypedAttribute<Imf::StringAttribute>("owner")) {
if (auto owner = header.findTypedAttribute<Imf::StringAttribute>("owner")) {
image.setText(QStringLiteral("Owner"), QString::fromStdString(owner->value())); image.setText(QStringLiteral("Owner"), QString::fromStdString(owner->value()));
} }
if (auto capDate = h.findTypedAttribute<Imf::StringAttribute>("capDate")) {
if (auto lat = header.findTypedAttribute<Imf::FloatAttribute>("latitude")) {
image.setText(QStringLiteral("Latitude"), QLocale::c().toString(lat->value()));
}
if (auto lon = header.findTypedAttribute<Imf::FloatAttribute>("longitude")) {
image.setText(QStringLiteral("Longitude"), QLocale::c().toString(lon->value()));
}
if (auto alt = header.findTypedAttribute<Imf::FloatAttribute>("altitude")) {
image.setText(QStringLiteral("Altitude"), QLocale::c().toString(alt->value()));
}
if (auto capDate = header.findTypedAttribute<Imf::StringAttribute>("capDate")) {
float off = 0; float off = 0;
if (auto utcOffset = header.findTypedAttribute<Imf::FloatAttribute>("utcOffset")) { if (auto utcOffset = h.findTypedAttribute<Imf::FloatAttribute>("utcOffset")) {
off = utcOffset->value(); off = utcOffset->value();
} }
auto dateTime = QDateTime::fromString(QString::fromStdString(capDate->value()), QStringLiteral("yyyy:MM:dd HH:mm:ss")); auto dateTime = QDateTime::fromString(QString::fromStdString(capDate->value()), QStringLiteral("yyyy:MM:dd HH:mm:ss"));
if (dateTime.isValid()) { if (dateTime.isValid()) {
dateTime.setTimeZone(QTimeZone::fromSecondsAheadOfUtc(off)); dateTime.setTimeZone(QTimeZone::fromSecondsAheadOfUtc(off));
image.setText(QStringLiteral("CreationDate"), dateTime.toString(Qt::ISODate)); image.setText(QStringLiteral("Date"), dateTime.toString(Qt::ISODate));
} }
} }
if (auto xDensity = h.findTypedAttribute<Imf::FloatAttribute>("xDensity")) {
if (auto xDensity = header.findTypedAttribute<Imf::FloatAttribute>("xDensity")) {
float par = 1; float par = 1;
if (auto pixelAspectRatio = header.findTypedAttribute<Imf::FloatAttribute>("pixelAspectRatio")) { if (auto pixelAspectRatio = h.findTypedAttribute<Imf::FloatAttribute>("pixelAspectRatio")) {
par = pixelAspectRatio->value(); par = pixelAspectRatio->value();
} }
image.setDotsPerMeterX(qRound(xDensity->value() * 100.0 / 2.54)); image.setDotsPerMeterX(qRound(xDensity->value() * 100.0 / 2.54));
image.setDotsPerMeterY(qRound(xDensity->value() * par * 100.0 / 2.54)); image.setDotsPerMeterY(qRound(xDensity->value() * par * 100.0 / 2.54));
} }
// Non-standard attribute Imf::Array<Imf::Rgba> pixels;
if (auto xmp = header.findTypedAttribute<Imf::StringAttribute>("xmp")) { pixels.resizeErase(width);
image.setText(QStringLiteral("XML:com.adobe.xmp"), QString::fromStdString(xmp->value()));
}
/* TODO: OpenEXR 3.2 metadata
*
* New Optional Standard Attributes:
* - Support automated editorial workflow:
* reelName, imageCounter, ascFramingDecisionList
*
* - Support forensics (“which other shots used that camera and lens before the camera firmware was updated?”):
* cameraMake, cameraModel, cameraSerialNumber, cameraFirmware, cameraUuid, cameraLabel, lensMake, lensModel,
* lensSerialNumber, lensFirmware, cameraColorBalance
*
* -Support pickup shots (reproduce critical camera settings):
* shutterAngle, cameraCCTSetting, cameraTintSetting
*
* - Support metadata-driven match move:
* sensorCenterOffset, sensorOverallDimensions, sensorPhotositePitch, sensorAcquisitionRectanglenominalFocalLength,
* effectiveFocalLength, pinholeFocalLength, entrancePupilOffset, tStop(complementing existing 'aperture')
*/
}
/*!
* \brief readColorSpace
* Reads EXR chromaticities from the \a header and set its as color profile in the \a image.
*/
static void readColorSpace(const Imf::Header &header, QImage &image)
{
// final color operations
#ifndef EXR_USE_LEGACY_CONVERSIONS
QColorSpace cs;
if (auto chroma = header.findTypedAttribute<Imf::ChromaticitiesAttribute>("chromaticities")) {
auto &&v = chroma->value();
cs = QColorSpace(QPointF(v.white.x, v.white.y),
QPointF(v.red.x, v.red.y),
QPointF(v.green.x, v.green.y),
QPointF(v.blue.x, v.blue.y),
QColorSpace::TransferFunction::Linear);
}
if (!cs.isValid()) {
cs = QColorSpace(QColorSpace::SRgbLinear);
}
image.setColorSpace(cs);
#ifdef EXR_CONVERT_TO_SRGB
image.convertToColorSpace(QColorSpace(QColorSpace::SRgb));
#endif
#endif // !EXR_USE_LEGACY_CONVERSIONS
}
bool EXRHandler::read(QImage *outImage)
{
try {
auto d = device();
// set the image position after the first run.
if (!d->isSequential()) {
if (m_startPos < 0) {
m_startPos = d->pos();
} else {
d->seek(m_startPos);
}
}
K_IStream istr(d, QByteArray());
Imf::RgbaInputFile file(istr);
auto &&header = file.header();
// set the image to load
if (m_imageNumber > -1) {
auto views = viewList(header);
if (m_imageNumber < views.count()) {
file.setLayerName(views.at(m_imageNumber).toStdString());
}
}
// get image info
Imath::Box2i dw = file.dataWindow();
qint32 width = dw.max.x - dw.min.x + 1;
qint32 height = dw.max.y - dw.min.y + 1;
// limiting the maximum image size on a reasonable size (as done in other plugins)
if (width > EXR_MAX_IMAGE_WIDTH || height > EXR_MAX_IMAGE_HEIGHT) {
qWarning() << "The maximum image size is limited to" << EXR_MAX_IMAGE_WIDTH << "x" << EXR_MAX_IMAGE_HEIGHT << "px";
return false;
}
// creating the image
QImage image = imageAlloc(width, height, imageFormat(file));
if (image.isNull()) {
qWarning() << "Failed to allocate image, invalid size?" << QSize(width, height);
return false;
}
Imf::Array2D<Imf::Rgba> pixels;
pixels.resizeErase(EXR_LINES_PER_BLOCK, width);
bool isRgba = image.hasAlphaChannel();
// somehow copy pixels into image // somehow copy pixels into image
for (int y = 0, n = 0; y < height; y += n) { for (int y = 0; y < height; ++y) {
auto my = dw.min.y + y; auto my = dw.min.y + y;
if (my > dw.max.y) { // paranoia check if (my <= dw.max.y) { // paranoia check
break; file.setFrameBuffer(&pixels[0] - dw.min.x - qint64(my) * width, 1, width);
} file.readPixels(my, my);
file.setFrameBuffer(&pixels[0][0] - dw.min.x - qint64(my) * width, 1, width);
file.readPixels(my, std::min(my + EXR_LINES_PER_BLOCK - 1, dw.max.y));
for (n = 0; n < std::min(EXR_LINES_PER_BLOCK, height - y); ++n) {
#if defined(EXR_USE_LEGACY_CONVERSIONS) #if defined(EXR_USE_LEGACY_CONVERSIONS)
Q_UNUSED(isRgba) auto scanLine = reinterpret_cast<QRgb *>(image.scanLine(y));
auto scanLine = reinterpret_cast<QRgb *>(image.scanLine(y + n));
for (int x = 0; x < width; ++x) { for (int x = 0; x < width; ++x) {
*(scanLine + x) = RgbaToQrgba(pixels[n][x]); *(scanLine + x) = RgbaToQrgba(pixels[x]);
} }
#elif defined(EXR_USE_QT6_FLOAT_IMAGE) #elif defined(EXR_USE_QT6_FLOAT_IMAGE)
auto scanLine = reinterpret_cast<qfloat16 *>(image.scanLine(y + n)); auto scanLine = reinterpret_cast<qfloat16 *>(image.scanLine(y));
for (int x = 0; x < width; ++x) { for (int x = 0; x < width; ++x) {
auto xcs = x * 4; auto xcs = x * 4;
*(scanLine + xcs) = qfloat16(qBound(0.f, float(pixels[n][x].r), 1.f)); *(scanLine + xcs) = qfloat16(qBound(0.f, float(pixels[x].r), 1.f));
*(scanLine + xcs + 1) = qfloat16(qBound(0.f, float(pixels[n][x].g), 1.f)); *(scanLine + xcs + 1) = qfloat16(qBound(0.f, float(pixels[x].g), 1.f));
*(scanLine + xcs + 2) = qfloat16(qBound(0.f, float(pixels[n][x].b), 1.f)); *(scanLine + xcs + 2) = qfloat16(qBound(0.f, float(pixels[x].b), 1.f));
*(scanLine + xcs + 3) = qfloat16(isRgba ? qBound(0.f, float(pixels[n][x].a), 1.f) : 1.f); *(scanLine + xcs + 3) = qfloat16(isRgba ? qBound(0.f, float(pixels[x].a), 1.f) : 1.f);
} }
#else #else
auto scanLine = reinterpret_cast<QRgba64 *>(image.scanLine(y + n)); auto scanLine = reinterpret_cast<QRgba64 *>(image.scanLine(y));
for (int x = 0; x < width; ++x) { for (int x = 0; x < width; ++x) {
*(scanLine + x) = QRgba64::fromRgba64(quint16(qBound(0.f, float(pixels[n][x].r) * 65535.f + 0.5f, 65535.f)), *(scanLine + x) = QRgba64::fromRgba64(quint16(qBound(0.f, float(pixels[x].r) * 65535.f + 0.5f, 65535.f)),
quint16(qBound(0.f, float(pixels[n][x].g) * 65535.f + 0.5f, 65535.f)), quint16(qBound(0.f, float(pixels[x].g) * 65535.f + 0.5f, 65535.f)),
quint16(qBound(0.f, float(pixels[n][x].b) * 65535.f + 0.5f, 65535.f)), quint16(qBound(0.f, float(pixels[x].b) * 65535.f + 0.5f, 65535.f)),
isRgba ? quint16(qBound(0.f, float(pixels[n][x].a) * 65535.f + 0.5f, 65535.f)) : quint16(65535)); isRgba ? quint16(qBound(0.f, float(pixels[x].a) * 65535.f + 0.5f, 65535.f)) : quint16(65535));
} }
#endif #endif
} }
} }
// set some useful metadata
readMetadata(header, image);
// final color operations // final color operations
readColorSpace(header, image); #ifndef EXR_USE_LEGACY_CONVERSIONS
image.setColorSpace(QColorSpace(QColorSpace::SRgbLinear));
#ifndef EXR_ALLOW_LINEAR_COLORSPACE
image.convertToColorSpace(QColorSpace(QColorSpace::SRgb));
#endif // !EXR_ALLOW_LINEAR_COLORSPACE
#endif // !EXR_USE_LEGACY_CONVERSIONS
*outImage = image; *outImage = std::move(image);
return true; return true;
} catch (const std::exception &) { } catch (const std::exception &exc) {
// qDebug() << exc.what();
return false; return false;
} }
} }
/*!
* \brief makePreview
* Creates a preview of maximum 256 x 256 pixels from the \a image.
*/
bool makePreview(const QImage &image, Imf::Array2D<Imf::PreviewRgba> &pixels)
{
auto w = image.width();
auto h = image.height();
QImage preview;
if (w > h) {
preview = image.scaledToWidth(256).convertToFormat(QImage::Format_ARGB32);
} else {
preview = image.scaledToHeight(256).convertToFormat(QImage::Format_ARGB32);
}
if (preview.isNull()) {
return false;
}
w = preview.width();
h = preview.height();
pixels.resizeErase(h, w);
preview.convertToColorSpace(QColorSpace(QColorSpace::SRgb));
for (int y = 0; y < h; ++y) {
auto scanLine = reinterpret_cast<const QRgb *>(preview.constScanLine(y));
for (int x = 0; x < w; ++x) {
auto &&out = pixels[y][x];
out.r = qRed(*(scanLine + x));
out.g = qGreen(*(scanLine + x));
out.b = qBlue(*(scanLine + x));
out.a = qAlpha(*(scanLine + x));
}
}
return true;
}
/*!
* \brief setMetadata
* Reades the metadata from \a image and set its as attributes in the \a header.
*/
static void setMetadata(const QImage &image, Imf::Header &header)
{
auto dateTime = QDateTime::currentDateTime();
for (auto &&key : image.textKeys()) {
auto text = image.text(key);
if (!key.compare(QStringLiteral("Comment"), Qt::CaseInsensitive)) {
header.insert("comments", Imf::StringAttribute(text.toStdString()));
}
if (!key.compare(QStringLiteral("Owner"), Qt::CaseInsensitive)) {
header.insert("owner", Imf::StringAttribute(text.toStdString()));
}
// clang-format off
if (!key.compare(QStringLiteral("Latitude"), Qt::CaseInsensitive) ||
!key.compare(QStringLiteral("Longitude"), Qt::CaseInsensitive) ||
!key.compare(QStringLiteral("Altitude"), Qt::CaseInsensitive)) {
// clang-format on
auto ok = false;
auto value = QLocale::c().toFloat(text, &ok);
if (ok) {
header.insert(qPrintable(key.toLower()), Imf::FloatAttribute(value));
}
}
if (!key.compare(QStringLiteral("CreationDate"), Qt::CaseInsensitive)) {
auto dt = QDateTime::fromString(text, Qt::ISODate);
if (dt.isValid()) {
dateTime = dt;
}
}
#ifndef EXR_DISABLE_XMP_ATTRIBUTE // warning: Non-standard attribute!
if (!key.compare(QStringLiteral("XML:com.adobe.xmp"), Qt::CaseInsensitive)) {
header.insert("xmp", Imf::StringAttribute(text.toStdString()));
}
#endif
}
if (dateTime.isValid()) {
header.insert("capDate", Imf::StringAttribute(dateTime.toString(QStringLiteral("yyyy:MM:dd HH:mm:ss")).toStdString()));
header.insert("utcOffset", Imf::FloatAttribute(dateTime.offsetFromUtc()));
}
if (image.dotsPerMeterX() && image.dotsPerMeterY()) {
header.insert("xDensity", Imf::FloatAttribute(image.dotsPerMeterX() * 2.54f / 100.f));
header.insert("pixelAspectRatio", Imf::FloatAttribute(float(image.dotsPerMeterX()) / float(image.dotsPerMeterY())));
}
// set default chroma (default constructor ITU-R BT.709-3 -> sRGB)
// The image is converted to Linear sRGB so, the chroma is the default EXR value.
// If a file doesnt have a chromaticities attribute, display software should assume that the
// files primaries and the white point match Rec. ITU-R BT.709-3.
// header.insert("chromaticities", Imf::ChromaticitiesAttribute(Imf::Chromaticities()));
// TODO: EXR 3.2 attributes (see readMetadata())
}
bool EXRHandler::write(const QImage &image)
{
try {
// create EXR header
qint32 width = image.width();
qint32 height = image.height();
// limiting the maximum image size on a reasonable size (as done in other plugins)
if (width > EXR_MAX_IMAGE_WIDTH || height > EXR_MAX_IMAGE_HEIGHT) {
qWarning() << "The maximum image size is limited to" << EXR_MAX_IMAGE_WIDTH << "x" << EXR_MAX_IMAGE_HEIGHT << "px";
return false;
}
Imf::Header header(width, height);
// set compression scheme (forcing PIZ as default)
header.compression() = Imf::Compression::PIZ_COMPRESSION;
if (m_compressionRatio >= qint32(Imf::Compression::NO_COMPRESSION) && m_compressionRatio < qint32(Imf::Compression::NUM_COMPRESSION_METHODS)) {
header.compression() = Imf::Compression(m_compressionRatio);
}
// set the DCT quality (used by DCT compressions only)
if (m_quality > -1 && m_quality <= 100) {
header.dwaCompressionLevel() = float(m_quality);
}
// make ZIP compression fast (used by ZIP compressions)
header.zipCompressionLevel() = 1;
// set preview (don't set it for small images)
if (width > 1024 || height > 1024) {
Imf::Array2D<Imf::PreviewRgba> previewPixels;
if (makePreview(image, previewPixels)) {
header.setPreviewImage(Imf::PreviewImage(previewPixels.width(), previewPixels.height(), &previewPixels[0][0]));
}
}
// set metadata (EXR attributes)
setMetadata(image, header);
// write the EXR
K_OStream ostr(device(), QByteArray());
auto channelsType = image.hasAlphaChannel() ? Imf::RgbaChannels::WRITE_RGBA : Imf::RgbaChannels::WRITE_RGB;
if (image.format() == QImage::Format_Mono ||
image.format() == QImage::Format_MonoLSB ||
image.format() == QImage::Format_Grayscale16 ||
image.format() == QImage::Format_Grayscale8) {
channelsType = Imf::RgbaChannels::WRITE_Y;
}
Imf::RgbaOutputFile file(ostr, header, channelsType);
Imf::Array2D<Imf::Rgba> pixels;
pixels.resizeErase(EXR_LINES_PER_BLOCK, width);
// convert the image and write into the stream
#if defined(EXR_USE_QT6_FLOAT_IMAGE)
auto convFormat = image.hasAlphaChannel() ? QImage::Format_RGBA16FPx4 : QImage::Format_RGBX16FPx4;
#else
auto convFormat = image.hasAlphaChannel() ? QImage::Format_RGBA64 : QImage::Format_RGBX64;
#endif
ScanLineConverter slc(convFormat);
slc.setDefaultSourceColorSpace(QColorSpace(QColorSpace::SRgb));
slc.setTargetColorSpace(QColorSpace(QColorSpace::SRgbLinear));
for (int y = 0, n = 0; y < height; y += n) {
for (n = 0; n < std::min(EXR_LINES_PER_BLOCK, height - y); ++n) {
#if defined(EXR_USE_QT6_FLOAT_IMAGE)
auto scanLine = reinterpret_cast<const qfloat16 *>(slc.convertedScanLine(image, y + n));
if (scanLine == nullptr) {
return false;
}
for (int x = 0; x < width; ++x) {
auto xcs = x * 4;
pixels[n][x].r = float(*(scanLine + xcs));
pixels[n][x].g = float(*(scanLine + xcs + 1));
pixels[n][x].b = float(*(scanLine + xcs + 2));
pixels[n][x].a = float(*(scanLine + xcs + 3));
}
#else
auto scanLine = reinterpret_cast<const QRgba64 *>(slc.convertedScanLine(image, y + n));
if (scanLine == nullptr) {
return false;
}
for (int x = 0; x < width; ++x) {
pixels[n][x].r = float((scanLine + x)->red() / 65535.f);
pixels[n][x].g = float((scanLine + x)->green() / 65535.f);
pixels[n][x].b = float((scanLine + x)->blue() / 65535.f);
pixels[n][x].a = float((scanLine + x)->alpha() / 65535.f);
}
#endif
}
file.setFrameBuffer(&pixels[0][0] - qint64(y) * width, 1, width);
file.writePixels(n);
}
} catch (const std::exception &) {
return false;
}
return true;
}
void EXRHandler::setOption(ImageOption option, const QVariant &value)
{
if (option == QImageIOHandler::CompressionRatio) {
auto ok = false;
auto cr = value.toInt(&ok);
if (ok) {
m_compressionRatio = cr;
}
}
if (option == QImageIOHandler::Quality) {
auto ok = false;
auto q = value.toInt(&ok);
if (ok) {
m_quality = q;
}
}
}
bool EXRHandler::supportsOption(ImageOption option) const
{
if (option == QImageIOHandler::Size) {
return true;
}
if (option == QImageIOHandler::ImageFormat) {
return true;
}
if (option == QImageIOHandler::CompressionRatio) {
return true;
}
if (option == QImageIOHandler::Quality) {
return true;
}
return false;
}
QVariant EXRHandler::option(ImageOption option) const
{
QVariant v;
if (option == QImageIOHandler::Size) {
if (auto d = device()) {
// transactions works on both random and sequential devices
d->startTransaction();
try {
K_IStream istr(d, QByteArray());
Imf::RgbaInputFile file(istr);
if (m_imageNumber > -1) { // set the image to read
auto views = viewList(file.header());
if (m_imageNumber < views.count()) {
file.setLayerName(views.at(m_imageNumber).toStdString());
}
}
Imath::Box2i dw = file.dataWindow();
v = QVariant(QSize(dw.max.x - dw.min.x + 1, dw.max.y - dw.min.y + 1));
} catch (const std::exception &) {
// broken file or unsupported version
}
d->rollbackTransaction();
}
}
if (option == QImageIOHandler::ImageFormat) {
if (auto d = device()) {
// transactions works on both random and sequential devices
d->startTransaction();
try {
K_IStream istr(d, QByteArray());
Imf::RgbaInputFile file(istr);
v = QVariant::fromValue(imageFormat(file));
} catch (const std::exception &) {
// broken file or unsupported version
}
d->rollbackTransaction();
}
}
if (option == QImageIOHandler::CompressionRatio) {
v = QVariant(m_compressionRatio);
}
if (option == QImageIOHandler::Quality) {
v = QVariant(m_quality);
}
return v;
}
bool EXRHandler::jumpToNextImage()
{
return jumpToImage(m_imageNumber + 1);
}
bool EXRHandler::jumpToImage(int imageNumber)
{
if (imageNumber < 0 || imageNumber >= imageCount()) {
return false;
}
m_imageNumber = imageNumber;
return true;
}
int EXRHandler::imageCount() const
{
// NOTE: image count is cached for performance reason
auto &&count = m_imageCount;
if (count > 0) {
return count;
}
count = QImageIOHandler::imageCount();
auto d = device();
d->startTransaction();
try {
K_IStream istr(d, QByteArray());
Imf::RgbaInputFile file(istr);
auto views = viewList(file.header());
if (!views.isEmpty()) {
count = views.size();
}
} catch (const std::exception &) {
// do nothing
}
d->rollbackTransaction();
return count;
}
int EXRHandler::currentImageNumber() const
{
return m_imageNumber;
}
bool EXRHandler::canRead(QIODevice *device) bool EXRHandler::canRead(QIODevice *device)
{ {
if (!device) { if (!device) {
@@ -828,7 +273,7 @@ bool EXRHandler::canRead(QIODevice *device)
QImageIOPlugin::Capabilities EXRPlugin::capabilities(QIODevice *device, const QByteArray &format) const QImageIOPlugin::Capabilities EXRPlugin::capabilities(QIODevice *device, const QByteArray &format) const
{ {
if (format == "exr") { if (format == "exr") {
return Capabilities(CanRead | CanWrite); return Capabilities(CanRead);
} }
if (!format.isEmpty()) { if (!format.isEmpty()) {
return {}; return {};
@@ -841,9 +286,6 @@ QImageIOPlugin::Capabilities EXRPlugin::capabilities(QIODevice *device, const QB
if (device->isReadable() && EXRHandler::canRead(device)) { if (device->isReadable() && EXRHandler::canRead(device)) {
cap |= CanRead; cap |= CanRead;
} }
if (device->isWritable()) {
cap |= CanWrite;
}
return cap; return cap;
} }

View File

@@ -1,8 +1,8 @@
/* /*
The high dynamic range EXR format support for QImage. QImageIO Routines to read (and perhaps in the future, write) images
in the high definition EXR format.
SPDX-FileCopyrightText: 2003 Brad Hards <bradh@frogmouth.net> SPDX-FileCopyrightText: 2003 Brad Hards <bradh@frogmouth.net>
SPDX-FileCopyrightText: 2023 Mirco Miranda <mircomir@outlook.com>
SPDX-License-Identifier: LGPL-2.0-or-later SPDX-License-Identifier: LGPL-2.0-or-later
*/ */
@@ -12,44 +12,6 @@
#include <QImageIOPlugin> #include <QImageIOPlugin>
/*!
* \brief The EXRHandler class
* The handler uses the OpenEXR reference implementation of the EXR file format.
*
* The purpose of EXR format is to accurately and efficiently represent high-dynamic-range scene-linear
* image data and associated metadata.
*
* Both reading and writing of EXR files is supported. When saving, the image is converted to 16-bit
* and sRGB Linear color space (if not already so). If no color space is set in the image, sRGB is assumed.
* When the handler is compiled with the default compile options, the loaded image is a 16-bit image
* with linear color space.
* Multiview images are also supported (read only) via imageCount(), jumpToImage(), etc....
*
* The following QImageIOHandler options are supported:
* - ImageFormat: The image's data format returned by the handler.
* - Size: The size of the image.
* - CompressionRatio: The compression ratio of the image data (see OpenEXR compression schemes).
* - Quality: The quality level of the image (see OpenEXR compression level of lossy codecs).
*
* The following metadata are set/get via QImage::setText()/QImage::text() in both read/write (if any):
* - Latitude, Longitude, Altitude: Geographic coordinates (Float converted to string).
* - CreationDate: Date the image was captured/created (QDateTime converted to string using Qt::ISODate).
* - Comment: Additional image information in human-readable form, for example a verbal description of the image (QString).
* - Owner: Name of the owner of the image (QString).
*
* In addition, information about image resolution is preserved and the preview is written for images larger
* than 1024px.
*
* The following compile options are supported (defines):
* - EXR_MAX_IMAGE_WIDTH: Maximum image width supported (read/write, default: 300000 px).
* - EXR_MAX_IMAGE_HEIGHT: Maximum image height supported (read/write, default: 300000 px).
* - EXR_LINES_PER_BLOCK: The number of scanlines buffered on both read and write operations.\n
* The higher the value, the greater the parallelization but the RAM consumption increases (default: 128)
* - EXR_USE_LEGACY_CONVERSIONS: The result image is an 8-bit RGB(A) converted without icc profiles (read, default: undefined).
* - EXR_CONVERT_TO_SRGB: The resulting image is convertef in the sRGB color space (read, default: undefined).
* - EXR_DISABLE_XMP_ATTRIBUTE: Disable the stores of XMP values in a non-standard attribute named "xmp".\n
* The QImage metadata used is "XML:com.adobe.xmp" (write, default: undefined).
*/
class EXRHandler : public QImageIOHandler class EXRHandler : public QImageIOHandler
{ {
public: public:
@@ -57,65 +19,8 @@ public:
bool canRead() const override; bool canRead() const override;
bool read(QImage *outImage) override; bool read(QImage *outImage) override;
bool write(const 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); static bool canRead(QIODevice *device);
private:
/*!
* \brief m_compressionRatio
* Value set by QImageWriter::setCompression().
*
* The compression scheme is the same as defined by OpenEXR library:
* - 0: no compression
* - 1: run length encoding
* - 2: zlib compression, one scan line at a time
* - 3: zlib compression, in blocks of 16 scan lines
* - 4: piz-based wavelet compression (default)
* - 5: lossy 24-bit float compression
* - 6: lossy 4-by-4 pixel block compression, fixed compression rate
* - 7: lossy 4-by-4 pixel block compression, fields are compressed more
* - 8: lossy DCT based compression, in blocks of 32 scanlines. More efficient for partial buffer access.
* - 9: lossy DCT based compression, in blocks of 256 scanlines. More efficient space wise and faster to decode full frames than DWAA_COMPRESSION.
*/
qint32 m_compressionRatio;
/*!
* \brief m_quality
* Value set by QImageWriter::setQuality().
*
* The quality is used on DCT compression schemes only with a
* supposed value between 0 and 100 (default: 45).
*/
qint32 m_quality;
/*!
* \brief m_imageNumber
* Value set by QImageReader::jumpToImage() or QImageReader::jumpToNextImage().
* The number of view selected in a multiview image.
*/
qint32 m_imageNumber;
/*!
* \brief m_imageCount
* The total number of views (cache value)
*/
mutable qint32 m_imageCount;
/*!
* \brief m_startPos
* The initial device position to allow multi image load (cache value).
*/
qint64 m_startPos;
}; };
class EXRPlugin : public QImageIOPlugin class EXRPlugin : public QImageIOPlugin

View File

@@ -283,7 +283,7 @@ bool HDRHandler::read(QImage *outImage)
// By setting the linear color space, programs that support profiles display HDR files as in GIMP and Photoshop. // By setting the linear color space, programs that support profiles display HDR files as in GIMP and Photoshop.
img.setColorSpace(QColorSpace(QColorSpace::SRgbLinear)); img.setColorSpace(QColorSpace(QColorSpace::SRgbLinear));
*outImage = img; *outImage = std::move(img);
return true; return true;
} }

View File

@@ -22,7 +22,6 @@ size_t HEIFHandler::m_initialized_count = 0;
bool HEIFHandler::m_plugins_queried = false; bool HEIFHandler::m_plugins_queried = false;
bool HEIFHandler::m_heif_decoder_available = false; bool HEIFHandler::m_heif_decoder_available = false;
bool HEIFHandler::m_heif_encoder_available = false; bool HEIFHandler::m_heif_encoder_available = false;
bool HEIFHandler::m_hej2_decoder_available = false;
extern "C" { extern "C" {
static struct heif_error heifhandler_write_callback(struct heif_context * /* ctx */, const void *data, size_t size, void *userdata) static struct heif_error heifhandler_write_callback(struct heif_context * /* ctx */, const void *data, size_t size, void *userdata)
@@ -60,25 +59,12 @@ HEIFHandler::HEIFHandler()
bool HEIFHandler::canRead() const bool HEIFHandler::canRead() const
{ {
if (m_parseState == ParseHeicNotParsed) { if (m_parseState == ParseHeicNotParsed && !canRead(device())) {
QIODevice *dev = device();
if (dev) {
const QByteArray header = dev->peek(28);
if (HEIFHandler::isSupportedBMFFType(header)) {
setFormat("heif");
return true;
}
if (HEIFHandler::isSupportedHEJ2(header)) {
setFormat("hej2");
return true;
}
}
return false; return false;
} }
if (m_parseState != ParseHeicError) { if (m_parseState != ParseHeicError) {
setFormat("heif");
return true; return true;
} }
return false; return false;
@@ -314,6 +300,17 @@ bool HEIFHandler::write_helper(const QImage &image)
return true; return true;
} }
bool HEIFHandler::canRead(QIODevice *device)
{
if (!device) {
qWarning("HEIFHandler::canRead() called with no device");
return false;
}
const QByteArray header = device->peek(28);
return HEIFHandler::isSupportedBMFFType(header);
}
bool HEIFHandler::isSupportedBMFFType(const QByteArray &header) bool HEIFHandler::isSupportedBMFFType(const QByteArray &header)
{ {
if (header.size() < 28) { if (header.size() < 28) {
@@ -353,22 +350,6 @@ bool HEIFHandler::isSupportedBMFFType(const QByteArray &header)
return false; return false;
} }
bool HEIFHandler::isSupportedHEJ2(const QByteArray &header)
{
if (header.size() < 28) {
return false;
}
const char *buffer = header.constData();
if (qstrncmp(buffer + 4, "ftyp", 4) == 0) {
if (qstrncmp(buffer + 8, "j2ki", 4) == 0) {
return true;
}
}
return false;
}
QVariant HEIFHandler::option(ImageOption option) const QVariant HEIFHandler::option(ImageOption option) const
{ {
if (option == Quality) { if (option == Quality) {
@@ -444,7 +425,7 @@ bool HEIFHandler::ensureDecoder()
} }
const QByteArray buffer = device()->readAll(); const QByteArray buffer = device()->readAll();
if (!HEIFHandler::isSupportedBMFFType(buffer) && !HEIFHandler::isSupportedHEJ2(buffer)) { if (!HEIFHandler::isSupportedBMFFType(buffer)) {
m_parseState = ParseHeicError; m_parseState = ParseHeicError;
return false; return false;
} }
@@ -833,9 +814,6 @@ bool HEIFHandler::isHeifDecoderAvailable()
} }
#endif #endif
#if LIBHEIF_HAVE_VERSION(1, 13, 0)
m_hej2_decoder_available = heif_have_decoder_for_format(heif_compression_JPEG2000);
#endif
m_heif_encoder_available = heif_have_encoder_for_format(heif_compression_HEVC); m_heif_encoder_available = heif_have_encoder_for_format(heif_compression_HEVC);
m_heif_decoder_available = heif_have_decoder_for_format(heif_compression_HEVC); m_heif_decoder_available = heif_have_decoder_for_format(heif_compression_HEVC);
m_plugins_queried = true; m_plugins_queried = true;
@@ -861,9 +839,6 @@ bool HEIFHandler::isHeifEncoderAvailable()
} }
#endif #endif
#if LIBHEIF_HAVE_VERSION(1, 13, 0)
m_hej2_decoder_available = heif_have_decoder_for_format(heif_compression_JPEG2000);
#endif
m_heif_decoder_available = heif_have_decoder_for_format(heif_compression_HEVC); m_heif_decoder_available = heif_have_decoder_for_format(heif_compression_HEVC);
m_heif_encoder_available = heif_have_encoder_for_format(heif_compression_HEVC); m_heif_encoder_available = heif_have_encoder_for_format(heif_compression_HEVC);
m_plugins_queried = true; m_plugins_queried = true;
@@ -878,34 +853,6 @@ bool HEIFHandler::isHeifEncoderAvailable()
return m_heif_encoder_available; return m_heif_encoder_available;
} }
bool HEIFHandler::isHej2DecoderAvailable()
{
QMutexLocker locker(&getHEIFHandlerMutex());
if (!m_plugins_queried) {
#if LIBHEIF_HAVE_VERSION(1, 13, 0)
if (m_initialized_count == 0) {
heif_init(nullptr);
}
#endif
m_heif_encoder_available = heif_have_encoder_for_format(heif_compression_HEVC);
m_heif_decoder_available = heif_have_decoder_for_format(heif_compression_HEVC);
#if LIBHEIF_HAVE_VERSION(1, 13, 0)
m_hej2_decoder_available = heif_have_decoder_for_format(heif_compression_JPEG2000);
#endif
m_plugins_queried = true;
#if LIBHEIF_HAVE_VERSION(1, 13, 0)
if (m_initialized_count == 0) {
heif_deinit();
}
#endif
}
return m_hej2_decoder_available;
}
void HEIFHandler::startHeifLib() void HEIFHandler::startHeifLib()
{ {
#if LIBHEIF_HAVE_VERSION(1, 13, 0) #if LIBHEIF_HAVE_VERSION(1, 13, 0)
@@ -954,15 +901,6 @@ QImageIOPlugin::Capabilities HEIFPlugin::capabilities(QIODevice *device, const Q
} }
return format_cap; return format_cap;
} }
if (format == "hej2") {
Capabilities format_cap;
if (HEIFHandler::isHej2DecoderAvailable()) {
format_cap |= CanRead;
}
return format_cap;
}
if (!format.isEmpty()) { if (!format.isEmpty()) {
return {}; return {};
} }
@@ -971,18 +909,10 @@ QImageIOPlugin::Capabilities HEIFPlugin::capabilities(QIODevice *device, const Q
} }
Capabilities cap; Capabilities cap;
if (device->isReadable()) { if (device->isReadable() && HEIFHandler::canRead(device) && HEIFHandler::isHeifDecoderAvailable()) {
const QByteArray header = device->peek(28);
if (HEIFHandler::isSupportedBMFFType(header) && HEIFHandler::isHeifDecoderAvailable()) {
cap |= CanRead; cap |= CanRead;
} }
if (HEIFHandler::isSupportedHEJ2(header) && HEIFHandler::isHej2DecoderAvailable()) {
cap |= CanRead;
}
}
if (device->isWritable() && HEIFHandler::isHeifEncoderAvailable()) { if (device->isWritable() && HEIFHandler::isHeifEncoderAvailable()) {
cap |= CanWrite; cap |= CanWrite;
} }

View File

@@ -1,4 +1,4 @@
{ {
"Keys": [ "heif", "heic", "hej2" ], "Keys": [ "heif", "heic" ],
"MimeTypes": [ "image/heif", "image/heif", "image/hej2k" ] "MimeTypes": [ "image/heif", "image/heif" ]
} }

View File

@@ -24,18 +24,17 @@ public:
bool read(QImage *image) override; bool read(QImage *image) override;
bool write(const QImage &image) override; bool write(const QImage &image) override;
static bool canRead(QIODevice *device);
QVariant option(ImageOption option) const override; QVariant option(ImageOption option) const override;
void setOption(ImageOption option, const QVariant &value) override; void setOption(ImageOption option, const QVariant &value) override;
bool supportsOption(ImageOption option) const override; bool supportsOption(ImageOption option) const override;
static bool isHeifDecoderAvailable(); static bool isHeifDecoderAvailable();
static bool isHeifEncoderAvailable(); static bool isHeifEncoderAvailable();
static bool isHej2DecoderAvailable();
static bool isSupportedBMFFType(const QByteArray &header);
static bool isSupportedHEJ2(const QByteArray &header);
private: private:
static bool isSupportedBMFFType(const QByteArray &header);
bool ensureParsed() const; bool ensureParsed() const;
bool ensureDecoder(); bool ensureDecoder();
@@ -58,7 +57,6 @@ private:
static bool m_plugins_queried; static bool m_plugins_queried;
static bool m_heif_decoder_available; static bool m_heif_decoder_available;
static bool m_heif_encoder_available; static bool m_heif_encoder_available;
static bool m_hej2_decoder_available;
static QMutex &getHEIFHandlerMutex(); static QMutex &getHEIFHandlerMutex();
}; };

View File

@@ -14,8 +14,8 @@
#include <QImage> #include <QImage>
#include <QImageIOHandler> #include <QImageIOHandler>
#include <QImageIOPlugin> #include <QImageIOPlugin>
#include <QList>
#include <QVariant> #include <QVariant>
#include <QVector>
#include <jxl/decode.h> #include <jxl/decode.h>
@@ -71,7 +71,7 @@ private:
void *m_runner; void *m_runner;
JxlBasicInfo m_basicinfo; JxlBasicInfo m_basicinfo;
QList<int> m_framedelays; QVector<int> m_framedelays;
int m_next_image_delay; int m_next_image_delay;
QImage m_current_image; QImage m_current_image;

View File

@@ -383,8 +383,7 @@ static bool readImage8(QImage &img, QDataStream &s, const PCXHEADER &header)
while (flag != 12 && s.status() == QDataStream::Ok) { while (flag != 12 && s.status() == QDataStream::Ok) {
s >> flag; s >> flag;
} }
} } else {
else {
device->seek(device->size() - 769); device->seek(device->size() - 769);
s >> flag; s >> flag;
} }
@@ -684,7 +683,7 @@ bool PCXHandler::read(QImage *outImage)
img.setDotsPerMeterX(qRound(header.HDpi / 25.4 * 1000)); img.setDotsPerMeterX(qRound(header.HDpi / 25.4 * 1000));
img.setDotsPerMeterY(qRound(header.YDpi / 25.4 * 1000)); img.setDotsPerMeterY(qRound(header.YDpi / 25.4 * 1000));
*outImage = img; *outImage = std::move(img);
return true; return true;
} }

View File

@@ -255,7 +255,7 @@ bool SoftimagePICHandler::read(QImage *image)
} }
} }
*image = img; *image = std::move(img);
m_state = Ready; m_state = Ready;
return true; return true;

View File

@@ -126,7 +126,7 @@ struct PSDDuotoneOptions {
*/ */
struct PSDColorModeDataSection { struct PSDColorModeDataSection {
PSDDuotoneOptions duotone; PSDDuotoneOptions duotone;
QList<QRgb> palette; QVector<QRgb> palette;
}; };
using PSDImageResourceSection = QHash<quint16, PSDImageResourceBlock>; using PSDImageResourceSection = QHash<quint16, PSDImageResourceBlock>;
@@ -458,7 +458,7 @@ PSDColorModeDataSection readColorModeDataSection(QDataStream &s, bool *ok = null
} }
else { // read the palette (768 bytes) else { // read the palette (768 bytes)
auto&& palette = cms.palette; auto&& palette = cms.palette;
QList<quint8> vect(size); QVector<quint8> vect(size);
for (auto&& v : vect) for (auto&& v : vect)
s >> v; s >> v;
for (qsizetype i = 0, n = vect.size()/3; i < n; ++i) for (qsizetype i = 0, n = vect.size()/3; i < n; ++i)
@@ -631,7 +631,7 @@ static bool IsValid(const PSDHeader &header)
qDebug() << "PSD header: invalid color mode" << header.color_mode; qDebug() << "PSD header: invalid color mode" << header.color_mode;
return false; return false;
} }
// Specs tells: "Supported range is 1 to 56" but when the alpha channel is present the limit is 57: // Specs tells: "Supported range is 1 to 56" but the limit is 57:
// Photoshop does not make you add more (see also 53alphas.psd test case). // Photoshop does not make you add more (see also 53alphas.psd test case).
if (header.channel_count < 1 || header.channel_count > 57) { if (header.channel_count < 1 || header.channel_count > 57) {
qDebug() << "PSD header: invalid number of channels" << header.channel_count; qDebug() << "PSD header: invalid number of channels" << header.channel_count;
@@ -1109,7 +1109,7 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
return false; return false;
} }
QList<quint32> strides(header.height * header.channel_count, raw_count); QVector<quint32> strides(header.height * header.channel_count, raw_count);
// Read the compressed stride sizes // Read the compressed stride sizes
if (compression) { if (compression) {
for (auto&& v : strides) { for (auto&& v : strides) {
@@ -1124,7 +1124,7 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
} }
// calculate the absolute file positions of each stride (required when a colorspace conversion should be done) // calculate the absolute file positions of each stride (required when a colorspace conversion should be done)
auto device = stream.device(); auto device = stream.device();
QList<quint64> stridePositions(strides.size()); QVector<quint64> stridePositions(strides.size());
if (!stridePositions.isEmpty()) { if (!stridePositions.isEmpty()) {
stridePositions[0] = device->pos(); stridePositions[0] = device->pos();
} }
@@ -1326,7 +1326,7 @@ bool PSDHandler::read(QImage *image)
return false; return false;
} }
*image = img; *image = std::move(img);
return true; return true;
} }

View File

@@ -221,7 +221,7 @@ static bool LoadRAS(QDataStream &s, const RasHeader &ras, QImage &img)
// Read palette if needed. // Read palette if needed.
if (ras.ColorMapType == RAS_COLOR_MAP_TYPE_RGB) { if (ras.ColorMapType == RAS_COLOR_MAP_TYPE_RGB) {
QList<quint8> palette(ras.ColorMapLength); QVector<quint8> palette(ras.ColorMapLength);
for (quint32 i = 0; i < ras.ColorMapLength; ++i) { for (quint32 i = 0; i < ras.ColorMapLength; ++i) {
s >> palette[i]; s >> palette[i];
} }
@@ -402,7 +402,7 @@ bool RASHandler::read(QImage *outImage)
return false; return false;
} }
*outImage = img; *outImage = std::move(img);
return true; return true;
} }

View File

@@ -444,9 +444,7 @@ void setParams(QImageIOHandler *handler, LibRaw *rawProcessor)
auto &&rawparams = rawProcessor->imgdata.rawparams; auto &&rawparams = rawProcessor->imgdata.rawparams;
#endif #endif
// Select one raw image from input file (0 - first, ...) // Select one raw image from input file (0 - first, ...)
if (handler->currentImageNumber() > -1) {
rawparams.shot_select = handler->currentImageNumber(); rawparams.shot_select = handler->currentImageNumber();
}
// *** Set processing parameters // *** Set processing parameters
@@ -724,7 +722,6 @@ RAWHandler::RAWHandler()
: m_imageNumber(0) : m_imageNumber(0)
, m_imageCount(0) , m_imageCount(0)
, m_quality(-1) , m_quality(-1)
, m_startPos(-1)
{ {
} }
@@ -741,15 +738,6 @@ bool RAWHandler::read(QImage *image)
{ {
auto dev = device(); auto dev = device();
// set the image position after the first run.
if (!dev->isSequential()) {
if (m_startPos < 0) {
m_startPos = dev->pos();
} else {
dev->seek(m_startPos);
}
}
// Check image file format. // Check image file format.
if (dev->atEnd()) { if (dev->atEnd()) {
return false; return false;
@@ -760,7 +748,7 @@ bool RAWHandler::read(QImage *image)
return false; return false;
} }
*image = img; *image = std::move(img);
return true; return true;
} }
@@ -831,7 +819,7 @@ bool RAWHandler::jumpToNextImage()
bool RAWHandler::jumpToImage(int imageNumber) bool RAWHandler::jumpToImage(int imageNumber)
{ {
if (imageNumber < 0 || imageNumber >= imageCount()) { if (imageNumber >= imageCount()) {
return false; return false;
} }
m_imageNumber = imageNumber; m_imageNumber = imageNumber;

View File

@@ -74,12 +74,6 @@ private:
* When the quality is -1, default quality is used. * When the quality is -1, default quality is used.
*/ */
qint32 m_quality; qint32 m_quality;
/*!
* \brief m_startPos
* The initial device position to allow multi image load (cache value).
*/
qint64 m_startPos;
}; };
class RAWPlugin : public QImageIOPlugin class RAWPlugin : public QImageIOPlugin

View File

@@ -22,13 +22,13 @@
#include "rgb_p.h" #include "rgb_p.h"
#include "util_p.h" #include "util_p.h"
#include <QList>
#include <QMap> #include <QMap>
#include <QVector>
#include <QDebug> #include <QDebug>
#include <QImage> #include <QImage>
class RLEData : public QList<uchar> class RLEData : public QVector<uchar>
{ {
public: public:
RLEData() RLEData()
@@ -61,7 +61,7 @@ public:
{ {
} }
uint insert(const uchar *d, uint l); uint insert(const uchar *d, uint l);
QList<const RLEData *> vector(); QVector<const RLEData *> vector();
void setBaseOffset(uint o) void setBaseOffset(uint o)
{ {
_offset = o; _offset = o;
@@ -107,7 +107,7 @@ private:
QByteArray _data; QByteArray _data;
QByteArray::Iterator _pos; QByteArray::Iterator _pos;
RLEMap _rlemap; RLEMap _rlemap;
QList<const RLEData *> _rlevector; QVector<const RLEData *> _rlevector;
uint _numrows; uint _numrows;
bool readData(QImage &); bool readData(QImage &);
@@ -421,9 +421,9 @@ uint RLEMap::insert(const uchar *d, uint l)
return QMap<RLEData, uint>::insert(data, _counter++).value(); return QMap<RLEData, uint>::insert(data, _counter++).value();
} }
QList<const RLEData *> RLEMap::vector() QVector<const RLEData *> RLEMap::vector()
{ {
QList<const RLEData *> v(size()); QVector<const RLEData *> v(size());
for (Iterator it = begin(); it != end(); ++it) { for (Iterator it = begin(); it != end(); ++it) {
v.replace(it.value(), &it.key()); v.replace(it.value(), &it.key());
} }
@@ -678,9 +678,9 @@ bool SGIImage::writeImage(const QImage &image)
} }
if (hasAlpha && img.format() != QImage::Format_ARGB32) { if (hasAlpha && img.format() != QImage::Format_ARGB32) {
img = img.convertToFormat(QImage::Format_ARGB32); img.convertTo(QImage::Format_ARGB32);
} else if (!hasAlpha && img.format() != QImage::Format_RGB32) { } else if (!hasAlpha && img.format() != QImage::Format_RGB32) {
img = img.convertToFormat(QImage::Format_RGB32); img.convertTo(QImage::Format_RGB32);
} }
if (img.isNull()) { if (img.isNull()) {
// qDebug() << "can't convert image to depth 32"; // qDebug() << "can't convert image to depth 32";

View File

@@ -15,15 +15,13 @@ ScanLineConverter::ScanLineConverter(const QImage::Format &targetFormat)
ScanLineConverter::ScanLineConverter(const ScanLineConverter &other) ScanLineConverter::ScanLineConverter(const ScanLineConverter &other)
: _targetFormat(other._targetFormat) : _targetFormat(other._targetFormat)
, _colorSpace(other._colorSpace) , _colorSpace(other._colorSpace)
, _defaultColorSpace(other._defaultColorSpace)
{ {
} }
ScanLineConverter &ScanLineConverter::operator=(const ScanLineConverter &other) ScanLineConverter &ScanLineConverter::operator=(const ScanLineConverter &other)
{ {
_targetFormat = other._targetFormat; this->_targetFormat = other._targetFormat;
_colorSpace = other._colorSpace; this->_colorSpace = other._colorSpace;
_defaultColorSpace = other._defaultColorSpace;
return (*this); return (*this);
} }
@@ -42,51 +40,24 @@ QColorSpace ScanLineConverter::targetColorSpace() const
return _colorSpace; return _colorSpace;
} }
void ScanLineConverter::setDefaultSourceColorSpace(const QColorSpace &colorSpace)
{
_defaultColorSpace = colorSpace;
}
QColorSpace ScanLineConverter::defaultSourceColorSpace() const
{
return _defaultColorSpace;
}
const uchar *ScanLineConverter::convertedScanLine(const QImage &image, qint32 y) const uchar *ScanLineConverter::convertedScanLine(const QImage &image, qint32 y)
{ {
auto colorSpaceConversion = isColorSpaceConversionNeeded(image); auto colorSpaceConversion = isColorSpaceConversionNeeded(image, _colorSpace);
if (image.format() == _targetFormat && !colorSpaceConversion) { if (image.format() == _targetFormat && !colorSpaceConversion) {
return image.constScanLine(y); return image.constScanLine(y);
} }
if (image.width() != _tmpBuffer.width() || image.format() != _tmpBuffer.format()) { if (image.width() != _tmpBuffer.width() || image.format() != _tmpBuffer.format()) {
_tmpBuffer = QImage(image.width(), 1, image.format()); _tmpBuffer = QImage(image.width(), 1, image.format());
_tmpBuffer.setColorTable(image.colorTable());
} }
if (_tmpBuffer.isNull()) { if (_tmpBuffer.isNull()) {
return nullptr; return nullptr;
} }
std::memcpy(_tmpBuffer.bits(), image.constScanLine(y), std::min(_tmpBuffer.bytesPerLine(), image.bytesPerLine())); std::memcpy(_tmpBuffer.bits(), image.constScanLine(y), std::min(_tmpBuffer.bytesPerLine(), image.bytesPerLine()));
auto tmp = _tmpBuffer;
if (colorSpaceConversion) { if (colorSpaceConversion) {
auto cs = image.colorSpace(); _tmpBuffer.setColorSpace(image.colorSpace());
if (!cs.isValid()) { _tmpBuffer.convertToColorSpace(_colorSpace);
cs = _defaultColorSpace;
} }
if (tmp.depth() < 24) { _convBuffer = _tmpBuffer.convertToFormat(_targetFormat);
tmp.convertTo(tmp.hasAlphaChannel() ? QImage::Format_ARGB32 : QImage::Format_RGB32);
}
tmp.setColorSpace(cs);
tmp.convertToColorSpace(_colorSpace);
}
/*
* Work Around for wrong RGBA64 -> 16FPx4/32FPx4 conversion on Intel architecture.
* Luckily convertTo() works fine with 16FPx4 images so I can use it instead convertToFormat().
* See also: https://bugreports.qt.io/browse/QTBUG-120614
*/
tmp.convertTo(_targetFormat);
_convBuffer = tmp;
if (_convBuffer.isNull()) { if (_convBuffer.isNull()) {
return nullptr; return nullptr;
} }
@@ -101,12 +72,12 @@ qsizetype ScanLineConverter::bytesPerLine() const
return _convBuffer.bytesPerLine(); return _convBuffer.bytesPerLine();
} }
bool ScanLineConverter::isColorSpaceConversionNeeded(const QImage &image, const QColorSpace &targetColorSpace, const QColorSpace &defaultColorSpace) bool ScanLineConverter::isColorSpaceConversionNeeded(const QImage &image, const QColorSpace &targetColorSpace) const
{ {
auto sourceColorSpace = image.colorSpace(); if (image.depth() < 24) { // RGB 8 bit or grater only
if (!sourceColorSpace.isValid()) { return false;
sourceColorSpace = defaultColorSpace;
} }
auto sourceColorSpace = image.colorSpace();
if (!sourceColorSpace.isValid() || !targetColorSpace.isValid()) { if (!sourceColorSpace.isValid() || !targetColorSpace.isValid()) {
return false; return false;
} }

View File

@@ -42,14 +42,6 @@ public:
void setTargetColorSpace(const QColorSpace &colorSpace); void setTargetColorSpace(const QColorSpace &colorSpace);
QColorSpace targetColorSpace() const; QColorSpace targetColorSpace() const;
/*!
* \brief setDefaultSourceColorSpace
* Set the source color space to use when the image does not have a color space.
* \param colorSpace
*/
void setDefaultSourceColorSpace(const QColorSpace &colorSpace);
QColorSpace defaultSourceColorSpace() const;
/*! /*!
* \brief convertedScanLine * \brief convertedScanLine
* Convert the scanline \a y. * Convert the scanline \a y.
@@ -70,24 +62,14 @@ public:
* \note Only 24 bit or grater images. * \note Only 24 bit or grater images.
* \param image The source image. * \param image The source image.
* \param targetColorSpace The target color space. * \param targetColorSpace The target color space.
* \param defaultColorSpace The default color space to use it image does not contains one.
* \return True if the conversion should be done otherwise false. * \return True if the conversion should be done otherwise false.
*/ */
static bool isColorSpaceConversionNeeded(const QImage &image, const QColorSpace &targetColorSpace, const QColorSpace &defaultColorSpace = QColorSpace()); bool isColorSpaceConversionNeeded(const QImage &image, const QColorSpace &targetColorSpace) const;
/*!
* \brief isColorSpaceConversionNeeded
*/
inline bool isColorSpaceConversionNeeded(const QImage &image) const
{
return isColorSpaceConversionNeeded(image, _colorSpace, _defaultColorSpace);
}
private: private:
// data // data
QImage::Format _targetFormat; QImage::Format _targetFormat;
QColorSpace _colorSpace; QColorSpace _colorSpace;
QColorSpace _defaultColorSpace;
// internal buffers // internal buffers
QImage _tmpBuffer; QImage _tmpBuffer;

View File

@@ -88,6 +88,10 @@ static QDataStream &operator>>(QDataStream &s, TgaHeader &head)
s >> head.height; s >> head.height;
s >> head.pixel_size; s >> head.pixel_size;
s >> head.flags; s >> head.flags;
/*qDebug() << "id_length: " << head.id_length << " - colormap_type: " << head.colormap_type << " - image_type: " << head.image_type;
qDebug() << "colormap_index: " << head.colormap_index << " - colormap_length: " << head.colormap_length << " - colormap_size: " << head.colormap_size;
qDebug() << "x_origin: " << head.x_origin << " - y_origin: " << head.y_origin << " - width:" << head.width << " - height:" << head.height << " - pixelsize:
" << head.pixel_size << " - flags: " << head.flags;*/
return s; return s;
} }
@@ -113,10 +117,6 @@ static bool IsSupported(const TgaHeader &head)
if (head.pixel_size != 8 && head.pixel_size != 16 && head.pixel_size != 24 && head.pixel_size != 32) { if (head.pixel_size != 8 && head.pixel_size != 16 && head.pixel_size != 24 && head.pixel_size != 32) {
return false; return false;
} }
// If the colormap_type field is set to zero, indicating that no color map exists, then colormap_size, colormap_index and colormap_length should be set to zero.
if (head.colormap_type == 0 && (head.colormap_size != 0 || head.colormap_index != 0 || head.colormap_length != 0)) {
return false;
}
return true; return true;
} }
@@ -170,57 +170,10 @@ struct TgaHeaderInfo {
} }
}; };
static QImage::Format imageFormat(const TgaHeader &head)
{
auto format = QImage::Format_Invalid;
if (IsSupported(head)) {
// Bits 0-3 are the numbers of alpha bits (can be zero!)
const int numAlphaBits = head.flags & 0xf;
// However alpha exists only in the 32 bit format.
if ((head.pixel_size == 32) && (head.flags & 0xf)) {
if (numAlphaBits <= 8) {
format = QImage::Format_ARGB32;
}
}
else {
format = QImage::Format_RGB32;
}
}
return format;
}
/*!
* \brief peekHeader
* Reads the header but does not change the position in the device.
*/
static bool peekHeader(QIODevice *device, TgaHeader &header)
{
qint64 oldPos = device->pos();
QByteArray head = device->read(TgaHeader::SIZE);
int readBytes = head.size();
if (device->isSequential()) {
for (int pos = readBytes - 1; pos >= 0; --pos) {
device->ungetChar(head[pos]);
}
} else {
device->seek(oldPos);
}
if (readBytes < TgaHeader::SIZE) {
return false;
}
QDataStream stream(head);
stream.setByteOrder(QDataStream::LittleEndian);
stream >> header;
return true;
}
static bool LoadTGA(QDataStream &s, const TgaHeader &tga, QImage &img) static bool LoadTGA(QDataStream &s, const TgaHeader &tga, QImage &img)
{ {
img = imageAlloc(tga.width, tga.height, imageFormat(tga)); // Create image.
img = imageAlloc(tga.width, tga.height, QImage::Format_RGB32);
if (img.isNull()) { if (img.isNull()) {
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(tga.width, tga.height); qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(tga.width, tga.height);
return false; return false;
@@ -228,7 +181,21 @@ static bool LoadTGA(QDataStream &s, const TgaHeader &tga, QImage &img)
TgaHeaderInfo info(tga); TgaHeaderInfo info(tga);
// Bits 0-3 are the numbers of alpha bits (can be zero!)
const int numAlphaBits = tga.flags & 0xf; 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);
if (img.isNull()) {
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(tga.width, tga.height);
return false;
}
if (numAlphaBits > 8) {
return false;
}
}
uint pixel_size = (tga.pixel_size / 8); uint pixel_size = (tga.pixel_size / 8);
qint64 size = qint64(tga.width) * qint64(tga.height) * pixel_size; qint64 size = qint64(tga.width) * qint64(tga.height) * pixel_size;
@@ -424,28 +391,26 @@ bool TGAHandler::read(QImage *outImage)
{ {
// qDebug() << "Loading TGA file!"; // qDebug() << "Loading TGA file!";
auto d = device(); QDataStream s(device());
TgaHeader tga;
if (!peekHeader(d, tga) || !IsSupported(tga)) {
// qDebug() << "This TGA file is not valid.";
return false;
}
if (d->isSequential()) {
d->read(TgaHeader::SIZE + tga.id_length);
} else {
d->seek(TgaHeader::SIZE + tga.id_length);
}
QDataStream s(d);
s.setByteOrder(QDataStream::LittleEndian); s.setByteOrder(QDataStream::LittleEndian);
// Read image header.
TgaHeader tga;
s >> tga;
s.device()->seek(TgaHeader::SIZE + tga.id_length);
// Check image file format. // Check image file format.
if (s.atEnd()) { if (s.atEnd()) {
// qDebug() << "This TGA file is not valid."; // qDebug() << "This TGA file is not valid.";
return false; return false;
} }
// Check supported file types.
if (!IsSupported(tga)) {
// qDebug() << "This TGA file is not supported.";
return false;
}
QImage img; QImage img;
bool result = LoadTGA(s, tga, img); bool result = LoadTGA(s, tga, img);
@@ -454,7 +419,7 @@ bool TGAHandler::read(QImage *outImage)
return false; return false;
} }
*outImage = img; *outImage = std::move(img);
return true; return true;
} }
@@ -503,42 +468,6 @@ bool TGAHandler::write(const QImage &image)
return true; return true;
} }
bool TGAHandler::supportsOption(ImageOption option) const
{
if (option == QImageIOHandler::Size) {
return true;
}
if (option == QImageIOHandler::ImageFormat) {
return true;
}
return false;
}
QVariant TGAHandler::option(ImageOption option) const
{
QVariant v;
if (option == QImageIOHandler::Size) {
if (auto d = device()) {
TgaHeader header;
if (peekHeader(d, header) && IsSupported(header)) {
v = QVariant::fromValue(QSize(header.width, header.height));
}
}
}
if (option == QImageIOHandler::ImageFormat) {
if (auto d = device()) {
TgaHeader header;
if (peekHeader(d, header) && IsSupported(header)) {
v = QVariant::fromValue(imageFormat(header));
}
}
}
return v;
}
bool TGAHandler::canRead(QIODevice *device) bool TGAHandler::canRead(QIODevice *device)
{ {
if (!device) { if (!device) {
@@ -562,12 +491,10 @@ bool TGAHandler::canRead(QIODevice *device)
return false; return false;
} }
QDataStream stream(head);
stream.setByteOrder(QDataStream::LittleEndian);
TgaHeader tga; TgaHeader tga;
if (!peekHeader(device, tga)) { stream >> tga;
qWarning("TGAHandler::canRead() error while reading the header");
return false;
}
return IsSupported(tga); return IsSupported(tga);
} }

View File

@@ -19,9 +19,6 @@ public:
bool read(QImage *image) override; bool read(QImage *image) override;
bool write(const 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); static bool canRead(QIODevice *device);
}; };

View File

@@ -13,7 +13,7 @@
#include <QImage> #include <QImage>
#include <QImageIOHandler> #include <QImageIOHandler>
// QList uses some extra space for stuff, hence the 32 here suggested by Thiago Macieira // 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; 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() // On Qt 6 to make the plugins fail to allocate if the image size is greater than QImageReader::allocationLimit()

View File

@@ -14,12 +14,18 @@
#include <QIODevice> #include <QIODevice>
#include <QImage> #include <QImage>
#include <QImageReader> #include <QImageReader>
#include <QList>
#include <QLoggingCategory> #include <QLoggingCategory>
#include <QPainter> #include <QPainter>
#include <QStack> #include <QStack>
#include <QVector>
#include <QtEndian> #include <QtEndian>
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
// To test the plugin with OSS FUZZ (in 2023) you need to make it compatible with Qt 5.
// The plugin has been made compatible with all Qt (5 and 6) versions supported by the KDE project.
#define XCF_QT5_SUPPORT
#endif
#ifndef XCF_QT5_SUPPORT #ifndef XCF_QT5_SUPPORT
// Float images are not supported by Qt 5 and can be disabled in QT 6 to reduce memory usage. // Float images are not supported by Qt 5 and can be disabled in QT 6 to reduce memory usage.
// Unfortunately enabling/disabling this define results in slightly different images, so leave the default if possible. // Unfortunately enabling/disabling this define results in slightly different images, so leave the default if possible.
@@ -110,7 +116,7 @@ struct RandomTable {
* parallel processing on a tile-by-tile basis. Here, though, * parallel processing on a tile-by-tile basis. Here, though,
* we just read them in en-masse and store them in a matrix. * we just read them in en-masse and store them in a matrix.
*/ */
typedef QList<QList<QImage>> Tiles; typedef QVector<QVector<QImage>> Tiles;
class XCFImageFormat class XCFImageFormat
{ {
@@ -492,7 +498,7 @@ public:
qint32 tattoo; //!< (unique identifier?) qint32 tattoo; //!< (unique identifier?)
quint32 unit; //!< Units of The GIMP (inch, mm, pica, etc...) quint32 unit; //!< Units of The GIMP (inch, mm, pica, etc...)
qint32 num_colors = 0; //!< number of colors in an indexed image qint32 num_colors = 0; //!< number of colors in an indexed image
QList<QRgb> palette; //!< indexed image color palette QVector<QRgb> palette; //!< indexed image color palette
int num_layers; //!< number of layers int num_layers; //!< number of layers
Layer layer; //!< most recently read layer Layer layer; //!< most recently read layer
@@ -545,7 +551,7 @@ private:
//! This table is used as a shared grayscale ramp to be set on grayscale //! This table is used as a shared grayscale ramp to be set on grayscale
//! images. This is because Qt does not differentiate between indexed and //! images. This is because Qt does not differentiate between indexed and
//! grayscale images. //! grayscale images.
static QList<QRgb> grayTable; static QVector<QRgb> grayTable;
//! This table provides the add_pixel saturation values (i.e. 250 + 250 = 255). //! This table provides the add_pixel saturation values (i.e. 250 + 250 = 255).
// static int add_lut[256][256]; - this is so lame waste of 256k of memory // static int add_lut[256][256]; - this is so lame waste of 256k of memory
@@ -646,7 +652,7 @@ bool XCFImageFormat::random_table_initialized;
constexpr RandomTable XCFImageFormat::randomTable; constexpr RandomTable XCFImageFormat::randomTable;
QList<QRgb> XCFImageFormat::grayTable; QVector<QRgb> XCFImageFormat::grayTable;
bool XCFImageFormat::modeAffectsSourceAlpha(const quint32 type) bool XCFImageFormat::modeAffectsSourceAlpha(const quint32 type)
{ {
@@ -960,11 +966,7 @@ bool XCFImageFormat::loadImageProperties(QDataStream &xcf_io, XCFImage &xcf_imag
case PROP_PARASITES: case PROP_PARASITES:
while (!property.atEnd()) { while (!property.atEnd()) {
char *tag; char *tag;
#if QT_VERSION < QT_VERSION_CHECK(6, 7, 0)
quint32 size; quint32 size;
#else
qint64 size;
#endif
property.readBytes(tag, size); property.readBytes(tag, size);
@@ -997,7 +999,7 @@ bool XCFImageFormat::loadImageProperties(QDataStream &xcf_io, XCFImage &xcf_imag
return false; return false;
} }
xcf_image.palette = QList<QRgb>(); xcf_image.palette = QVector<QRgb>();
xcf_image.palette.reserve(xcf_image.num_colors); xcf_image.palette.reserve(xcf_image.num_colors);
for (int i = 0; i < xcf_image.num_colors; i++) { for (int i = 0; i < xcf_image.num_colors; i++) {
@@ -2017,7 +2019,7 @@ bool XCFImageFormat::loadLevel(QDataStream &xcf_io, Layer &layer, qint32 bpp, co
const uint blockSize = TILE_WIDTH * TILE_HEIGHT * bpp * 1.5; const uint blockSize = TILE_WIDTH * TILE_HEIGHT * bpp * 1.5;
QList<uchar> buffer; QVector<uchar> buffer;
if (needConvert) { if (needConvert) {
buffer.resize(blockSize * (bpp == 2 ? 2 : 1)); buffer.resize(blockSize * (bpp == 2 ? 2 : 1));
} }

View File

@@ -103,7 +103,7 @@ int main(int argc, char **argv)
QTextStream(stderr) << "Unknown QImage data format " << parser.value(qimgformat) << '\n'; QTextStream(stderr) << "Unknown QImage data format " << parser.value(qimgformat) << '\n';
return 4; return 4;
} }
img = img.convertToFormat(qformat); img.convertTo(qformat);
} }
qint64 written = output.write(reinterpret_cast<const char *>(img.bits()), img.sizeInBytes()); qint64 written = output.write(reinterpret_cast<const char *>(img.bits()), img.sizeInBytes());
if (written != img.sizeInBytes()) { if (written != img.sizeInBytes()) {