Compare commits
19 Commits
Author | SHA1 | Date | |
---|---|---|---|
bb17f7bf84 | |||
b849e48ef4 | |||
81b7263d73 | |||
63e21ee5f3 | |||
06f097046c | |||
950ed43623 | |||
863c424390 | |||
bd083ff354 | |||
99663607b2 | |||
7499e3b8d4 | |||
cb5ca7fc48 | |||
4f61e3912c | |||
b8a9c75c80 | |||
4995c9cd15 | |||
a54c5e876c | |||
6c1a7ad339 | |||
c721fa481b | |||
ea15fed399 | |||
c2fabef501 |
@ -1,5 +1,5 @@
|
||||
Dependencies:
|
||||
- 'on': ['Linux', 'FreeBSD', 'macOS', 'Windows']
|
||||
- 'on': ['Linux', 'FreeBSD', 'macOS', 'Windows', 'Android']
|
||||
'require':
|
||||
'frameworks/extra-cmake-modules': '@same'
|
||||
'frameworks/karchive' : '@same'
|
||||
|
@ -1,11 +1,11 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
set(KF_VERSION "6.2.0") # handled by release scripts
|
||||
set(KF_DEP_VERSION "6.2.0") # handled by release scripts
|
||||
set(KF_VERSION "6.4.0") # handled by release scripts
|
||||
set(KF_DEP_VERSION "6.4.0") # handled by release scripts
|
||||
project(KImageFormats VERSION ${KF_VERSION})
|
||||
|
||||
include(FeatureSummary)
|
||||
find_package(ECM 6.2.0 NO_MODULE)
|
||||
find_package(ECM 6.4.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)
|
||||
|
||||
@ -75,13 +75,18 @@ 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"
|
||||
)
|
||||
|
||||
option(KIMAGEFORMATS_JXR "Enable plugin for JPEG XR format" OFF)
|
||||
if(KIMAGEFORMATS_JXR)
|
||||
find_package(LibJXR)
|
||||
endif()
|
||||
add_feature_info(LibJXR LibJXR_FOUND "required for the QImage plugin for JPEG XR images")
|
||||
|
||||
ecm_set_disabled_deprecation_versions(
|
||||
QT 6.5
|
||||
KF 5.102
|
||||
|
@ -18,16 +18,19 @@ The following image formats have read-only support:
|
||||
- Gimp (xcf)
|
||||
- Krita (kra)
|
||||
- OpenRaster (ora)
|
||||
- Pixar raster (pxr)
|
||||
- Portable FloatMap (pfm)
|
||||
- Photoshop documents (psd, psb, pdd, psdt)
|
||||
- Radiance HDR (hdr)
|
||||
- Sun Raster (im1, im8, im24, im32, ras, sun)
|
||||
|
||||
The following image formats have read and write support:
|
||||
|
||||
- AV1 Image File Format (AVIF)
|
||||
- AV1 Image File Format (avif)
|
||||
- Encapsulated PostScript (eps)
|
||||
- High Efficiency Image File Format (heif). Can be enabled with the KIMAGEFORMATS_HEIF build option.
|
||||
- JPEG XL (jxl)
|
||||
- JPEG XR (jxr). Can be enabled with the KIMAGEFORMATS_JXR build option.
|
||||
- OpenEXR (exr)
|
||||
- Personal Computer Exchange (pcx)
|
||||
- Quite OK Image format (qoi)
|
||||
|
@ -11,7 +11,7 @@ macro(kimageformats_read_tests)
|
||||
endif()
|
||||
|
||||
if (NOT TARGET readtest)
|
||||
add_executable(readtest readtest.cpp)
|
||||
add_executable(readtest readtest.cpp templateimage.cpp)
|
||||
target_link_libraries(readtest Qt6::Gui)
|
||||
target_compile_definitions(readtest
|
||||
PRIVATE IMAGEDIR="${CMAKE_CURRENT_SOURCE_DIR}/read")
|
||||
@ -66,7 +66,9 @@ endmacro()
|
||||
kimageformats_read_tests(
|
||||
hdr
|
||||
pcx
|
||||
pfm
|
||||
psd
|
||||
pxr
|
||||
qoi
|
||||
ras
|
||||
rgb
|
||||
@ -114,6 +116,15 @@ if (LibJXL_FOUND AND LibJXLThreads_FOUND)
|
||||
)
|
||||
endif()
|
||||
|
||||
if (LibJXR_FOUND)
|
||||
kimageformats_read_tests(
|
||||
jxr
|
||||
)
|
||||
kimageformats_write_tests(
|
||||
jxr-nodatacheck
|
||||
)
|
||||
endif()
|
||||
|
||||
# Allow some fuzziness when reading this formats, to allow for
|
||||
# rounding errors (eg: in alpha blending).
|
||||
kimageformats_read_tests(FUZZ 1
|
||||
|
BIN
autotests/read/jxr/abydos_bgra32.jxr
Normal file
BIN
autotests/read/jxr/abydos_bgra32.png
Normal file
After Width: | Height: | Size: 206 KiB |
BIN
autotests/read/jxr/testcard_bgra8.jxr
Normal file
BIN
autotests/read/jxr/testcard_bgra8.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
autotests/read/jxr/testcard_cmyk8.jxr
Normal file
11
autotests/read/jxr/testcard_cmyk8.jxr.json
Normal file
@ -0,0 +1,11 @@
|
||||
[
|
||||
{
|
||||
"minQtVersion" : "6.8.0",
|
||||
"fileName" : "testcard_cmyk8.tif"
|
||||
},
|
||||
{
|
||||
"maxQtVersion" : "6.7.99",
|
||||
"unsupportedFormat" : true,
|
||||
"comment" : "Qt versions lower than 6.8 do not support CMYK format so this test should be skipped."
|
||||
}
|
||||
]
|
BIN
autotests/read/jxr/testcard_cmyk8.tif
Normal file
BIN
autotests/read/jxr/testcard_gray16.jxr
Normal file
BIN
autotests/read/jxr/testcard_gray16.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
autotests/read/jxr/testcard_gray8.jxr
Normal file
BIN
autotests/read/jxr/testcard_gray8.png
Normal file
After Width: | Height: | Size: 6.8 KiB |
BIN
autotests/read/jxr/testcard_mono.jxr
Normal file
BIN
autotests/read/jxr/testcard_mono.png
Normal file
After Width: | Height: | Size: 8.2 KiB |
BIN
autotests/read/jxr/testcard_rgb16.jxr
Normal file
BIN
autotests/read/jxr/testcard_rgb16.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
autotests/read/jxr/testcard_rgb8.jxr
Normal file
BIN
autotests/read/jxr/testcard_rgb8.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
autotests/read/jxr/testcard_rgba16.jxr
Normal file
BIN
autotests/read/jxr/testcard_rgba16.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
autotests/read/jxr/testcard_rgba8.jxr
Normal file
BIN
autotests/read/jxr/testcard_rgba8.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
autotests/read/pfm/testcard_gray.pfm
Normal file
BIN
autotests/read/pfm/testcard_gray.png
Normal file
After Width: | Height: | Size: 7.4 KiB |
BIN
autotests/read/pfm/testcard_rgb.pfm
Normal file
BIN
autotests/read/pfm/testcard_rgb.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
autotests/read/pfm/testcard_rgb_ps.pfm
Normal file
BIN
autotests/read/pfm/testcard_rgb_ps.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
autotests/read/psd/cmyk16_testcard.png
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
autotests/read/psd/cmyk16_testcard.psd
Normal file
11
autotests/read/psd/cmyk16_testcard.psd.json
Normal file
@ -0,0 +1,11 @@
|
||||
[
|
||||
{
|
||||
"minQtVersion" : "6.8.0",
|
||||
"fileName" : "cmyk16_testcard_qt6_8.tif"
|
||||
},
|
||||
{
|
||||
"minQtVersion" : "6.0.0",
|
||||
"maxQtVersion" : "6.7.99",
|
||||
"fileName" : "cmyk16_testcard.png"
|
||||
}
|
||||
]
|
BIN
autotests/read/psd/cmyk16_testcard_qt6_8.tif
Normal file
BIN
autotests/read/psd/cmyk8_testcard.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
autotests/read/psd/cmyk8_testcard.psd
Normal file
11
autotests/read/psd/cmyk8_testcard.psd.json
Normal file
@ -0,0 +1,11 @@
|
||||
[
|
||||
{
|
||||
"minQtVersion" : "6.8.0",
|
||||
"fileName" : "cmyk8_testcard_qt6_8.tif"
|
||||
},
|
||||
{
|
||||
"minQtVersion" : "6.0.0",
|
||||
"maxQtVersion" : "6.7.99",
|
||||
"fileName" : "cmyk8_testcard.png"
|
||||
}
|
||||
]
|
BIN
autotests/read/psd/cmyk8_testcard_qt6_8.tif
Normal file
11
autotests/read/psd/cmyka-16bits.psd.json
Normal file
@ -0,0 +1,11 @@
|
||||
[
|
||||
{
|
||||
"minQtVersion" : "6.8.0",
|
||||
"fileName" : "cmyka-16bits_qt6_8.png"
|
||||
},
|
||||
{
|
||||
"minQtVersion" : "6.0.0",
|
||||
"maxQtVersion" : "6.7.99",
|
||||
"fileName" : "cmyka-16bits.png"
|
||||
}
|
||||
]
|
BIN
autotests/read/psd/cmyka-16bits_qt6_8.png
Normal file
After Width: | Height: | Size: 111 KiB |
11
autotests/read/psd/cmyka-8bits.psd.json
Normal file
@ -0,0 +1,11 @@
|
||||
[
|
||||
{
|
||||
"minQtVersion" : "6.8.0",
|
||||
"fileName" : "cmyka-8bits_qt6_8.png"
|
||||
},
|
||||
{
|
||||
"minQtVersion" : "6.0.0",
|
||||
"maxQtVersion" : "6.7.99",
|
||||
"fileName" : "cmyka-8bits.png"
|
||||
}
|
||||
]
|
BIN
autotests/read/psd/cmyka-8bits_qt6_8.png
Normal file
After Width: | Height: | Size: 77 KiB |
11
autotests/read/psd/mch-16bits.psd.json
Normal file
@ -0,0 +1,11 @@
|
||||
[
|
||||
{
|
||||
"minQtVersion" : "6.8.0",
|
||||
"fileName" : "mch-16bits_qt_6_8.tif"
|
||||
},
|
||||
{
|
||||
"minQtVersion" : "6.0.0",
|
||||
"maxQtVersion" : "6.7.99",
|
||||
"fileName" : "mch-16bits.png"
|
||||
}
|
||||
]
|
BIN
autotests/read/psd/mch-16bits_qt_6_8.tif
Normal file
11
autotests/read/psd/mch-8bits.psd.json
Normal file
@ -0,0 +1,11 @@
|
||||
[
|
||||
{
|
||||
"minQtVersion" : "6.8.0",
|
||||
"fileName" : "mch-8bits_qt_6.8.tif"
|
||||
},
|
||||
{
|
||||
"minQtVersion" : "6.0.0",
|
||||
"maxQtVersion" : "6.7.99",
|
||||
"fileName" : "mch-8bits.png"
|
||||
}
|
||||
]
|
BIN
autotests/read/psd/mch-8bits_qt_6.8.tif
Normal file
BIN
autotests/read/pxr/testcard_gray.png
Normal file
After Width: | Height: | Size: 5.4 KiB |
BIN
autotests/read/pxr/testcard_gray.pxr
Normal file
BIN
autotests/read/pxr/testcard_rgb.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
autotests/read/pxr/testcard_rgb.pxr
Normal file
32
autotests/read/xcf/birthday16.xcf.json
Normal file
@ -0,0 +1,32 @@
|
||||
[
|
||||
{
|
||||
"minQtVersion" : "6.7.0",
|
||||
"fileName" : "birthday16.png",
|
||||
"seeAlso" : "https://bugreports.qt.io/browse/QTBUG-120614"
|
||||
},
|
||||
{
|
||||
"minQtVersion" : "6.0.0",
|
||||
"maxQtVersion" : "6.2.10",
|
||||
"fileName" : "birthday16_alphabug.png"
|
||||
},
|
||||
{
|
||||
"minQtVersion" : "6.3.0",
|
||||
"maxQtVersion" : "6.3.2",
|
||||
"fileName" : "birthday32_alphabug.png"
|
||||
},
|
||||
{
|
||||
"minQtVersion" : "6.4.0",
|
||||
"maxQtVersion" : "6.4.3",
|
||||
"fileName" : "birthday32_alphabug.png"
|
||||
},
|
||||
{
|
||||
"minQtVersion" : "6.5.0",
|
||||
"maxQtVersion" : "6.5.4",
|
||||
"fileName" : "birthday16_alphabug.png"
|
||||
},
|
||||
{
|
||||
"minQtVersion" : "6.6.0",
|
||||
"maxQtVersion" : "6.6.1",
|
||||
"fileName" : "birthday16_alphabug.png"
|
||||
}
|
||||
]
|
BIN
autotests/read/xcf/birthday16_alphabug.png
Normal file
After Width: | Height: | Size: 91 KiB |
32
autotests/read/xcf/birthday32.xcf.json
Normal file
@ -0,0 +1,32 @@
|
||||
[
|
||||
{
|
||||
"minQtVersion" : "6.7.0",
|
||||
"fileName" : "birthday32.png",
|
||||
"seeAlso" : "https://bugreports.qt.io/browse/QTBUG-120614"
|
||||
},
|
||||
{
|
||||
"minQtVersion" : "6.0.0",
|
||||
"maxQtVersion" : "6.2.10",
|
||||
"fileName" : "birthday32_alphabug.png"
|
||||
},
|
||||
{
|
||||
"minQtVersion" : "6.3.0",
|
||||
"maxQtVersion" : "6.3.2",
|
||||
"fileName" : "birthday32_alphabug.png"
|
||||
},
|
||||
{
|
||||
"minQtVersion" : "6.4.0",
|
||||
"maxQtVersion" : "6.4.3",
|
||||
"fileName" : "birthday32_alphabug.png"
|
||||
},
|
||||
{
|
||||
"minQtVersion" : "6.5.0",
|
||||
"maxQtVersion" : "6.5.4",
|
||||
"fileName" : "birthday32_alphabug.png"
|
||||
},
|
||||
{
|
||||
"minQtVersion" : "6.6.0",
|
||||
"maxQtVersion" : "6.6.1",
|
||||
"fileName" : "birthday32_alphabug.png"
|
||||
}
|
||||
]
|
BIN
autotests/read/xcf/birthday32_alphabug.png
Normal file
After Width: | Height: | Size: 91 KiB |
BIN
autotests/read/xcf/bug_476755_gray_layers.png
Normal file
After Width: | Height: | Size: 234 KiB |
BIN
autotests/read/xcf/bug_476755_gray_layers.xcf
Normal file
BIN
autotests/read/xcf/bug_476755_rgb_layers.png
Normal file
After Width: | Height: | Size: 234 KiB |
BIN
autotests/read/xcf/bug_476755_rgb_layers.xcf
Normal file
@ -16,6 +16,7 @@
|
||||
#include <QTextStream>
|
||||
|
||||
#include "../tests/format-enum.h"
|
||||
#include "templateimage.h"
|
||||
|
||||
#include "fuzzyeq.cpp"
|
||||
|
||||
@ -89,13 +90,114 @@ static QImage::Format preferredFormat(QImage::Format fmt)
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief The OptionTest class
|
||||
* Class for testing image options.
|
||||
* Supports the most common options:
|
||||
* - Size
|
||||
* - ImageFormat
|
||||
* - ImageTransformation (rotations)
|
||||
* \todo Add missing options if needed.
|
||||
*/
|
||||
class OptionTest
|
||||
{
|
||||
public:
|
||||
OptionTest()
|
||||
: m_size(QSize())
|
||||
, m_format(QImage::Format_Invalid)
|
||||
, m_transformations(QImageIOHandler::TransformationNone)
|
||||
{
|
||||
}
|
||||
|
||||
OptionTest(const OptionTest&) = default;
|
||||
OptionTest& operator =(const OptionTest&) = default;
|
||||
|
||||
/*!
|
||||
* \brief store
|
||||
* Stores the supported options of the reader.
|
||||
* \param reader
|
||||
* \return True on success, otherwise false.
|
||||
*/
|
||||
bool store(const QImageReader *reader = nullptr)
|
||||
{
|
||||
if (reader == nullptr) {
|
||||
return false;
|
||||
}
|
||||
bool ok = true;
|
||||
if (reader->supportsOption(QImageIOHandler::Size)) {
|
||||
m_size = reader->size();
|
||||
if (m_size.isEmpty())
|
||||
ok = false;
|
||||
}
|
||||
if (reader->supportsOption(QImageIOHandler::ImageFormat)) {
|
||||
m_format = reader->imageFormat();
|
||||
if (m_format == QImage::Format_Invalid)
|
||||
ok = false;
|
||||
}
|
||||
if (reader->supportsOption(QImageIOHandler::ImageTransformation)) {
|
||||
m_transformations = reader->transformation();
|
||||
if (m_transformations < 0 || m_transformations > 7)
|
||||
ok = false;
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
* \brief compare
|
||||
* Compare the stored values with the ones read from the image reader.
|
||||
* \param reader
|
||||
* \return True on success, otherwise false.
|
||||
*/
|
||||
bool compare(const QImageReader *reader)
|
||||
{
|
||||
if (reader == nullptr) {
|
||||
return false;
|
||||
}
|
||||
bool ok = true;
|
||||
if (reader->supportsOption(QImageIOHandler::Size)) {
|
||||
ok = ok && (m_size == reader->size());
|
||||
}
|
||||
if (reader->supportsOption(QImageIOHandler::ImageFormat)) {
|
||||
ok = ok && (m_format == reader->imageFormat());
|
||||
}
|
||||
if (reader->supportsOption(QImageIOHandler::ImageTransformation)) {
|
||||
ok = ok && (m_transformations == reader->transformation());
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief compare
|
||||
* Compare the image properties with the ones stored.
|
||||
* \param image
|
||||
* \return True on success, otherwise false.
|
||||
*/
|
||||
bool compare(const QImage& image)
|
||||
{
|
||||
bool ok = true;
|
||||
if (!m_size.isEmpty()) {
|
||||
ok = ok && (m_size == image.size());
|
||||
}
|
||||
if (m_format != QImage::Format_Invalid) {
|
||||
ok = ok && (m_format == image.format());
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
private:
|
||||
QSize m_size;
|
||||
QImage::Format m_format;
|
||||
QImageIOHandler::Transformations m_transformations;
|
||||
};
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
QCoreApplication app(argc, argv);
|
||||
QCoreApplication::removeLibraryPath(QStringLiteral(PLUGIN_DIR));
|
||||
QCoreApplication::addLibraryPath(QStringLiteral(PLUGIN_DIR));
|
||||
QCoreApplication::setApplicationName(QStringLiteral("readtest"));
|
||||
QCoreApplication::setApplicationVersion(QStringLiteral("1.1.0"));
|
||||
QCoreApplication::setApplicationVersion(QStringLiteral("1.2.0"));
|
||||
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription(QStringLiteral("Performs basic image conversion checking."));
|
||||
@ -159,22 +261,30 @@ int main(int argc, char **argv)
|
||||
QTextStream(stdout) << "* Run on RANDOM ACCESS device\n";
|
||||
}
|
||||
for (const QFileInfo &fi : lstImgDir) {
|
||||
if (!fi.suffix().compare("png", Qt::CaseInsensitive) || !fi.suffix().compare("tif", Qt::CaseInsensitive)) {
|
||||
TemplateImage timg(fi);
|
||||
if (timg.isTemplate()) {
|
||||
continue;
|
||||
}
|
||||
int suffixPos = fi.filePath().size() - suffix.size();
|
||||
QString inputfile = fi.filePath();
|
||||
QString fmt = QStringLiteral("png");
|
||||
QString expfile = fi.filePath().replace(suffixPos, suffix.size(), fmt);
|
||||
if (!QFile::exists(expfile)) { // try with tiff
|
||||
fmt = QStringLiteral("tif");
|
||||
expfile = fi.filePath().replace(suffixPos, suffix.size(), fmt);
|
||||
}
|
||||
QString expfilename = QFileInfo(expfile).fileName();
|
||||
|
||||
std::unique_ptr<QIODevice> inputDevice(seq ? new SequentialFile(inputfile) : new QFile(inputfile));
|
||||
bool skipTest = false;
|
||||
QFileInfo expFileInfo = timg.compareImage(skipTest);
|
||||
if (skipTest) {
|
||||
QTextStream(stdout) << "SKIP : " << fi.fileName() << ": image format not supported by current Qt version!\n";
|
||||
++skipped;
|
||||
continue;
|
||||
}
|
||||
if (!formatStrings.contains(expFileInfo.suffix(), Qt::CaseInsensitive)) {
|
||||
// Work Around for CCBUG: 468288
|
||||
QTextStream(stdout) << "SKIP : " << fi.fileName() << ": comparison image " << expFileInfo.fileName() << " cannot be loaded due to the lack of "
|
||||
<< expFileInfo.suffix().toUpper() << " plugin!\n";
|
||||
++skipped;
|
||||
continue;
|
||||
}
|
||||
QString expfilename = expFileInfo.fileName();
|
||||
|
||||
std::unique_ptr<QIODevice> inputDevice(seq ? new SequentialFile(fi.filePath()) : new QFile(fi.filePath()));
|
||||
QImageReader inputReader(inputDevice.get(), format);
|
||||
QImageReader expReader(expfile, fmt.toLatin1());
|
||||
QImageReader expReader(expFileInfo.filePath());
|
||||
|
||||
QImage inputImage;
|
||||
QImage expImage;
|
||||
@ -199,11 +309,32 @@ int main(int argc, char **argv)
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
OptionTest optionTest;
|
||||
if (!optionTest.store(&inputReader)) {
|
||||
QTextStream(stdout) << "FAIL : " << fi.fileName() << ": error while reading options\n";
|
||||
++failed;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!inputReader.read(&inputImage)) {
|
||||
QTextStream(stdout) << "FAIL : " << fi.fileName() << ": failed to load: " << inputReader.errorString() << "\n";
|
||||
++failed;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!optionTest.compare(&inputReader)) {
|
||||
QTextStream(stdout) << "FAIL : " << fi.fileName() << ": error while comparing options\n";
|
||||
++failed;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!optionTest.compare(inputImage)) {
|
||||
QTextStream(stdout) << "FAIL : " << fi.fileName() << ": error while comparing the image properties with options\n";
|
||||
++failed;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (expImage.width() != inputImage.width()) {
|
||||
QTextStream(stdout) << "FAIL : " << fi.fileName() << ": width was " << inputImage.width() << " but " << expfilename << " width was "
|
||||
<< expImage.width() << "\n";
|
||||
|
106
autotests/templateimage.cpp
Normal file
@ -0,0 +1,106 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2024 Mirco Miranda <mircomir@outlook.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||
*/
|
||||
|
||||
#include "templateimage.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QVersionNumber>
|
||||
|
||||
TemplateImage::TemplateImage(const QFileInfo &fi) :
|
||||
m_fi(fi)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool TemplateImage::isTemplate() const
|
||||
{
|
||||
auto list = suffixes();
|
||||
for (auto&& suffix : list) {
|
||||
if (!m_fi.suffix().compare(suffix, Qt::CaseInsensitive))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QFileInfo TemplateImage::compareImage(bool &skipTest) const
|
||||
{
|
||||
auto fi = jsonImage(skipTest);
|
||||
if (skipTest) {
|
||||
return {};
|
||||
}
|
||||
if (fi.exists()) {
|
||||
return fi;
|
||||
}
|
||||
return legacyImage();
|
||||
}
|
||||
|
||||
|
||||
QStringList TemplateImage::suffixes()
|
||||
{
|
||||
return QStringList({"png", "tif", "tiff", "json"});
|
||||
}
|
||||
|
||||
QFileInfo TemplateImage::legacyImage() const
|
||||
{
|
||||
auto list = suffixes();
|
||||
for (auto&& suffix : list) {
|
||||
auto fi = QFileInfo(QStringLiteral("%1/%2.%3").arg(m_fi.path(), m_fi.completeBaseName(), suffix));
|
||||
if (fi.exists()) {
|
||||
return fi;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
QFileInfo TemplateImage::jsonImage(bool &skipTest) const
|
||||
{
|
||||
auto fi = QFileInfo(QStringLiteral("%1.json").arg(m_fi.filePath()));
|
||||
if (!fi.exists()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
QFile f(fi.filePath());
|
||||
if (!f.open(QFile::ReadOnly)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
QJsonParseError err;
|
||||
auto doc = QJsonDocument::fromJson(f.readAll(), &err);
|
||||
if (err.error != QJsonParseError::NoError || !doc.isArray()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto currentQt = QVersionNumber::fromString(qVersion());
|
||||
auto arr = doc.array();
|
||||
for (auto val : arr) {
|
||||
if (!val.isObject())
|
||||
continue;
|
||||
auto obj = val.toObject();
|
||||
auto minQt = QVersionNumber::fromString(obj.value("minQtVersion").toString());
|
||||
auto maxQt = QVersionNumber::fromString(obj.value("maxQtVersion").toString());
|
||||
auto name = obj.value("fileName").toString();
|
||||
auto unsupportedFormat = obj.value("unsupportedFormat").toBool();
|
||||
|
||||
// filter
|
||||
if (name.isEmpty() && !unsupportedFormat)
|
||||
continue;
|
||||
if (!minQt.isNull() && currentQt < minQt)
|
||||
continue;
|
||||
if (!maxQt.isNull() && currentQt > maxQt)
|
||||
continue;
|
||||
if (unsupportedFormat) {
|
||||
skipTest = true;
|
||||
break;
|
||||
}
|
||||
return QFileInfo(QStringLiteral("%1/%2").arg(fi.path(), name));
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
74
autotests/templateimage.h
Normal file
@ -0,0 +1,74 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2024 Mirco Miranda <mircomir@outlook.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||
*/
|
||||
|
||||
#ifndef TEMPLATEIMAGE_H
|
||||
#define TEMPLATEIMAGE_H
|
||||
|
||||
#include <QFileInfo>
|
||||
|
||||
/*!
|
||||
* \brief The TemplateImage class
|
||||
* Given an image name, it decides the template image to compare it with.
|
||||
*/
|
||||
class TemplateImage
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* \brief TemplateImage
|
||||
* \param fi The image to test.
|
||||
*/
|
||||
TemplateImage(const QFileInfo& fi);
|
||||
|
||||
/*!
|
||||
* \brief TemplateImage
|
||||
* Default copy constructor.
|
||||
*/
|
||||
TemplateImage(const TemplateImage& other) = default;
|
||||
/*!
|
||||
* \brief operator =
|
||||
* Default copy operator
|
||||
*/
|
||||
TemplateImage& operator=(const TemplateImage& other) = default;
|
||||
|
||||
/*!
|
||||
* \brief isTemplate
|
||||
* \return True if the image is a template, false otherwise.
|
||||
* \sa suffixes
|
||||
*/
|
||||
bool isTemplate() const;
|
||||
|
||||
/*!
|
||||
* \brief compareImage
|
||||
* \param skipTest True if the test should be skipped (e.g. image format not supported by current Qt version).
|
||||
* \return The template image to use for the comparison.
|
||||
*/
|
||||
QFileInfo compareImage(bool &skipTest) const;
|
||||
|
||||
/*!
|
||||
* \brief suffixes
|
||||
* \return The list of suffixes considered templates.
|
||||
*/
|
||||
static QStringList suffixes();
|
||||
|
||||
private:
|
||||
/*!
|
||||
* \brief legacyImage
|
||||
* \return The template image calculated from the source image name.
|
||||
*/
|
||||
QFileInfo legacyImage() const;
|
||||
|
||||
/*!
|
||||
* \brief jsonImage
|
||||
* \param skipTest True if the test should be skipped (not supported).
|
||||
* \return The template image read from the corresponding JSON.
|
||||
*/
|
||||
QFileInfo jsonImage(bool &skipTest) const;
|
||||
|
||||
private:
|
||||
QFileInfo m_fi;
|
||||
};
|
||||
|
||||
#endif // TEMPLATEIMAGE_H
|
24
cmake/find-modules/FindLibJXR.cmake
Normal file
@ -0,0 +1,24 @@
|
||||
# - Find LibJXR
|
||||
# Find the JXR library
|
||||
# This module defines
|
||||
# LIBJXR_INCLUDE_DIRS, where to find jxrlib/JXRGlue.h
|
||||
# LIBJXR_LIBRARIES, the libraries needed to use JXR
|
||||
#
|
||||
# Based on cmake code found at https://github.com/microsoft/vcpkg/blob/master/ports/jxrlib/FindJXR.cmake
|
||||
|
||||
find_path(LIBJXR_INCLUDE_DIRS
|
||||
NAMES JXRGlue.h
|
||||
PATH_SUFFIXES jxrlib
|
||||
)
|
||||
mark_as_advanced(LIBJXR_INCLUDE_DIRS)
|
||||
|
||||
include(SelectLibraryConfigurations)
|
||||
|
||||
find_library(LIBJPEGXR_LIBRARY NAMES jpegxr)
|
||||
find_library(LIBJXRGLUE_LIBRARY NAMES jxrglue)
|
||||
|
||||
set(LIBJXR_LIBRARIES ${LIBJPEGXR_LIBRARY} ${LIBJXRGLUE_LIBRARY})
|
||||
mark_as_advanced(LIBJXR_LIBRARIES)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS(LibJXR DEFAULT_MSG LIBJXR_INCLUDE_DIRS LIBJXR_LIBRARIES)
|
@ -1,4 +1,3 @@
|
||||
maintainer: alexmerry
|
||||
description: Image format plugins for Qt
|
||||
tier: 2
|
||||
type: functional
|
||||
|
@ -83,7 +83,15 @@ kimageformats_add_plugin(kimg_pic SOURCES pic.cpp)
|
||||
|
||||
##################################
|
||||
|
||||
kimageformats_add_plugin(kimg_psd SOURCES psd.cpp)
|
||||
kimageformats_add_plugin(kimg_pfm SOURCES pfm.cpp)
|
||||
|
||||
##################################
|
||||
|
||||
kimageformats_add_plugin(kimg_psd SOURCES psd.cpp scanlineconverter.cpp)
|
||||
|
||||
##################################
|
||||
|
||||
kimageformats_add_plugin(kimg_pxr SOURCES pxr.cpp)
|
||||
|
||||
##################################
|
||||
|
||||
@ -115,6 +123,20 @@ endif()
|
||||
|
||||
##################################
|
||||
|
||||
if (LibJXR_FOUND)
|
||||
kimageformats_add_plugin(kimg_jxr SOURCES jxr.cpp)
|
||||
kde_enable_exceptions()
|
||||
target_include_directories(kimg_jxr PRIVATE ${LIBJXR_INCLUDE_DIRS})
|
||||
target_link_libraries(kimg_jxr PRIVATE jpegxr jxrglue)
|
||||
target_compile_definitions(kimg_jxr PRIVATE INITGUID)
|
||||
if (NOT CMAKE_SYSTEM_NAME STREQUAL "Windows")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-error=undef")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-error=undef")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
##################################
|
||||
|
||||
if (KF6Archive_FOUND)
|
||||
|
||||
kimageformats_add_plugin(kimg_kra SOURCES kra.cpp)
|
||||
|
@ -99,6 +99,11 @@
|
||||
#define EXR_USE_QT6_FLOAT_IMAGE // default uncommented
|
||||
#endif
|
||||
|
||||
// Qt 6.8 allow to create and use Gray profile, so we can load a Gray image as Grayscale format instead RGB one.
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
|
||||
#define EXR_GRAY_SUPPORT_ENABLED
|
||||
#endif
|
||||
|
||||
class K_IStream : public Imf::IStream
|
||||
{
|
||||
public:
|
||||
@ -248,10 +253,15 @@ bool EXRHandler::canRead() const
|
||||
static QImage::Format imageFormat(const Imf::RgbaInputFile &file)
|
||||
{
|
||||
auto isRgba = file.channels() & Imf::RgbaChannels::WRITE_A;
|
||||
#ifdef EXR_GRAY_SUPPORT_ENABLED
|
||||
auto isGray = file.channels() & Imf::RgbaChannels::WRITE_Y;
|
||||
#else
|
||||
auto isGray = false;
|
||||
#endif
|
||||
#if defined(EXR_USE_LEGACY_CONVERSIONS)
|
||||
return (isRgba ? QImage::Format_ARGB32 : QImage::Format_RGB32);
|
||||
#elif defined(EXR_USE_QT6_FLOAT_IMAGE)
|
||||
return (isRgba ? QImage::Format_RGBA16FPx4 : QImage::Format_RGBX16FPx4);
|
||||
return (isRgba ? QImage::Format_RGBA16FPx4 : isGray ? QImage::Format_Grayscale16 : QImage::Format_RGBX16FPx4);
|
||||
#else
|
||||
return (isRgba ? QImage::Format_RGBA64 : QImage::Format_RGBX64);
|
||||
#endif
|
||||
@ -290,23 +300,23 @@ static void readMetadata(const Imf::Header &header, QImage &image)
|
||||
{
|
||||
// set some useful metadata
|
||||
if (auto comments = header.findTypedAttribute<Imf::StringAttribute>("comments")) {
|
||||
image.setText(QStringLiteral("Comment"), QString::fromStdString(comments->value()));
|
||||
image.setText(QStringLiteral(META_KEY_COMMENT), QString::fromStdString(comments->value()));
|
||||
}
|
||||
|
||||
if (auto owner = header.findTypedAttribute<Imf::StringAttribute>("owner")) {
|
||||
image.setText(QStringLiteral("Owner"), QString::fromStdString(owner->value()));
|
||||
image.setText(QStringLiteral(META_KEY_OWNER), QString::fromStdString(owner->value()));
|
||||
}
|
||||
|
||||
if (auto lat = header.findTypedAttribute<Imf::FloatAttribute>("latitude")) {
|
||||
image.setText(QStringLiteral("Latitude"), QLocale::c().toString(lat->value()));
|
||||
image.setText(QStringLiteral(META_KEY_LATITUDE), QLocale::c().toString(lat->value()));
|
||||
}
|
||||
|
||||
if (auto lon = header.findTypedAttribute<Imf::FloatAttribute>("longitude")) {
|
||||
image.setText(QStringLiteral("Longitude"), QLocale::c().toString(lon->value()));
|
||||
image.setText(QStringLiteral(META_KEY_LONGITUDE), QLocale::c().toString(lon->value()));
|
||||
}
|
||||
|
||||
if (auto alt = header.findTypedAttribute<Imf::FloatAttribute>("altitude")) {
|
||||
image.setText(QStringLiteral("Altitude"), QLocale::c().toString(alt->value()));
|
||||
image.setText(QStringLiteral(META_KEY_ALTITUDE), QLocale::c().toString(alt->value()));
|
||||
}
|
||||
|
||||
if (auto capDate = header.findTypedAttribute<Imf::StringAttribute>("capDate")) {
|
||||
@ -317,7 +327,7 @@ static void readMetadata(const Imf::Header &header, QImage &image)
|
||||
auto dateTime = QDateTime::fromString(QString::fromStdString(capDate->value()), QStringLiteral("yyyy:MM:dd HH:mm:ss"));
|
||||
if (dateTime.isValid()) {
|
||||
dateTime.setTimeZone(QTimeZone::fromSecondsAheadOfUtc(off));
|
||||
image.setText(QStringLiteral("CreationDate"), dateTime.toString(Qt::ISODate));
|
||||
image.setText(QStringLiteral(META_KEY_CREATIONDATE), dateTime.toString(Qt::ISODate));
|
||||
}
|
||||
}
|
||||
|
||||
@ -332,7 +342,7 @@ static void readMetadata(const Imf::Header &header, QImage &image)
|
||||
|
||||
// Non-standard attribute
|
||||
if (auto xmp = header.findTypedAttribute<Imf::StringAttribute>("xmp")) {
|
||||
image.setText(QStringLiteral("XML:com.adobe.xmp"), QString::fromStdString(xmp->value()));
|
||||
image.setText(QStringLiteral(META_KEY_XMP_ADOBE), QString::fromStdString(xmp->value()));
|
||||
}
|
||||
|
||||
/* TODO: OpenEXR 3.2 metadata
|
||||
@ -373,7 +383,16 @@ static void readColorSpace(const Imf::Header &header, QImage &image)
|
||||
QColorSpace::TransferFunction::Linear);
|
||||
}
|
||||
if (!cs.isValid()) {
|
||||
#ifdef EXR_GRAY_SUPPORT_ENABLED
|
||||
if (image.format() == QImage::Format_Grayscale16 || image.format() == QImage::Format_Grayscale8) {
|
||||
cs = QColorSpace(QPointF(0.31271, 0.32902), QColorSpace::TransferFunction::Linear);
|
||||
cs.setDescription(QStringLiteral("Gray Linear build-in"));
|
||||
} else {
|
||||
cs = QColorSpace(QColorSpace::SRgbLinear);
|
||||
}
|
||||
#else
|
||||
cs = QColorSpace(QColorSpace::SRgbLinear);
|
||||
#endif
|
||||
}
|
||||
image.setColorSpace(cs);
|
||||
|
||||
@ -443,6 +462,13 @@ bool EXRHandler::read(QImage *outImage)
|
||||
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 (image.format() == QImage::Format_Grayscale16) { // grayscale image
|
||||
auto scanLine = reinterpret_cast<quint16 *>(image.scanLine(y + n));
|
||||
for (int x = 0; x < width; ++x) {
|
||||
*(scanLine + x) = quint16(qBound(0.f, float(pixels[n][x].r) * 65535.f + 0.5f, 65535.f));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
#if defined(EXR_USE_LEGACY_CONVERSIONS)
|
||||
Q_UNUSED(isRgba)
|
||||
auto scanLine = reinterpret_cast<QRgb *>(image.scanLine(y + n));
|
||||
@ -530,18 +556,18 @@ 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)) {
|
||||
if (!key.compare(QStringLiteral(META_KEY_COMMENT), Qt::CaseInsensitive)) {
|
||||
header.insert("comments", Imf::StringAttribute(text.toStdString()));
|
||||
}
|
||||
|
||||
if (!key.compare(QStringLiteral("Owner"), Qt::CaseInsensitive)) {
|
||||
if (!key.compare(QStringLiteral(META_KEY_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)) {
|
||||
if (!key.compare(QStringLiteral(META_KEY_LATITUDE), Qt::CaseInsensitive) ||
|
||||
!key.compare(QStringLiteral(META_KEY_LONGITUDE), Qt::CaseInsensitive) ||
|
||||
!key.compare(QStringLiteral(META_KEY_ALTITUDE), Qt::CaseInsensitive)) {
|
||||
// clang-format on
|
||||
auto ok = false;
|
||||
auto value = QLocale::c().toFloat(text, &ok);
|
||||
@ -550,7 +576,7 @@ static void setMetadata(const QImage &image, Imf::Header &header)
|
||||
}
|
||||
}
|
||||
|
||||
if (!key.compare(QStringLiteral("CreationDate"), Qt::CaseInsensitive)) {
|
||||
if (!key.compare(QStringLiteral(META_KEY_CREATIONDATE), Qt::CaseInsensitive)) {
|
||||
auto dt = QDateTime::fromString(text, Qt::ISODate);
|
||||
if (dt.isValid()) {
|
||||
dateTime = dt;
|
||||
@ -558,7 +584,7 @@ static void setMetadata(const QImage &image, Imf::Header &header)
|
||||
}
|
||||
|
||||
#ifndef EXR_DISABLE_XMP_ATTRIBUTE // warning: Non-standard attribute!
|
||||
if (!key.compare(QStringLiteral("XML:com.adobe.xmp"), Qt::CaseInsensitive)) {
|
||||
if (!key.compare(QStringLiteral(META_KEY_XMP_ADOBE), Qt::CaseInsensitive)) {
|
||||
header.insert("xmp", Imf::StringAttribute(text.toStdString()));
|
||||
}
|
||||
#endif
|
||||
@ -639,7 +665,16 @@ bool EXRHandler::write(const QImage &image)
|
||||
auto convFormat = image.hasAlphaChannel() ? QImage::Format_RGBA64 : QImage::Format_RGBX64;
|
||||
#endif
|
||||
ScanLineConverter slc(convFormat);
|
||||
#ifdef EXR_GRAY_SUPPORT_ENABLED
|
||||
if (channelsType == Imf::RgbaChannels::WRITE_Y) {
|
||||
slc.setDefaultSourceColorSpace(QColorSpace(QColorSpace(QColorSpace::SRgb).whitePoint(), QColorSpace::TransferFunction::SRgb)); // Creates a custom grayscale color space
|
||||
} else {
|
||||
slc.setDefaultSourceColorSpace(QColorSpace(QColorSpace::SRgb));
|
||||
}
|
||||
#else
|
||||
slc.setDefaultSourceColorSpace(QColorSpace(QColorSpace::SRgb));
|
||||
#endif
|
||||
|
||||
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) {
|
||||
@ -699,10 +734,12 @@ void EXRHandler::setOption(ImageOption option, const QVariant &value)
|
||||
bool EXRHandler::supportsOption(ImageOption option) const
|
||||
{
|
||||
if (option == QImageIOHandler::Size) {
|
||||
return true;
|
||||
if (auto d = device())
|
||||
return !d->isSequential();
|
||||
}
|
||||
if (option == QImageIOHandler::ImageFormat) {
|
||||
return true;
|
||||
if (auto d = device())
|
||||
return !d->isSequential();
|
||||
}
|
||||
if (option == QImageIOHandler::CompressionRatio) {
|
||||
return true;
|
||||
@ -721,6 +758,9 @@ QVariant EXRHandler::option(ImageOption option) const
|
||||
if (auto d = device()) {
|
||||
// transactions works on both random and sequential devices
|
||||
d->startTransaction();
|
||||
if (m_startPos > -1) {
|
||||
d->seek(m_startPos);
|
||||
}
|
||||
try {
|
||||
K_IStream istr(d, QByteArray());
|
||||
Imf::RgbaInputFile file(istr);
|
||||
@ -743,6 +783,9 @@ QVariant EXRHandler::option(ImageOption option) const
|
||||
if (auto d = device()) {
|
||||
// transactions works on both random and sequential devices
|
||||
d->startTransaction();
|
||||
if (m_startPos > -1) {
|
||||
d->seek(m_startPos);
|
||||
}
|
||||
try {
|
||||
K_IStream istr(d, QByteArray());
|
||||
Imf::RgbaInputFile file(istr);
|
||||
|
@ -269,13 +269,13 @@ bool HDRHandler::read(QImage *outImage)
|
||||
{
|
||||
QDataStream s(device());
|
||||
|
||||
QSize size = readHeaderSize(s.device());
|
||||
if (!size.isValid()) {
|
||||
m_imageSize = readHeaderSize(s.device());
|
||||
if (!m_imageSize.isValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QImage img;
|
||||
if (!LoadHDR(s, size.width(), size.height(), img)) {
|
||||
if (!LoadHDR(s, m_imageSize.width(), m_imageSize.height(), img)) {
|
||||
// qDebug() << "Error loading HDR file.";
|
||||
return false;
|
||||
}
|
||||
@ -303,7 +303,9 @@ QVariant HDRHandler::option(ImageOption option) const
|
||||
QVariant v;
|
||||
|
||||
if (option == QImageIOHandler::Size) {
|
||||
if (auto d = device()) {
|
||||
if (!m_imageSize.isEmpty()) {
|
||||
v = QVariant::fromValue(m_imageSize);
|
||||
} else if (auto d = device()) {
|
||||
// transactions works on both random and sequential devices
|
||||
d->startTransaction();
|
||||
auto size = readHeaderSize(d);
|
||||
|
@ -22,6 +22,13 @@ public:
|
||||
QVariant option(QImageIOHandler::ImageOption option) const override;
|
||||
|
||||
static bool canRead(QIODevice *device);
|
||||
|
||||
private:
|
||||
/*!
|
||||
* \brief m_imageSize
|
||||
* Image size cache used by option()
|
||||
*/
|
||||
QSize m_imageSize;
|
||||
};
|
||||
|
||||
class HDRPlugin : public QImageIOPlugin
|
||||
|
1141
src/imageformats/jxr.cpp
Normal file
4
src/imageformats/jxr.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"Keys": [ "jxr", "wdp", "hdp" ],
|
||||
"MimeTypes": [ "image/jxr", "image/vnd.ms-photo", "image/vnd.ms-photo" ]
|
||||
}
|
47
src/imageformats/jxr_p.h
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
This file is part of the KDE project
|
||||
SPDX-FileCopyrightText: 2024 Mirco Miranda <mircomir@outlook.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef KIMG_JXR_P_H
|
||||
#define KIMG_JXR_P_H
|
||||
|
||||
#include <QImageIOPlugin>
|
||||
#include <QSharedDataPointer>
|
||||
|
||||
class JXRHandlerPrivate;
|
||||
|
||||
class JXRHandler : public QImageIOHandler
|
||||
{
|
||||
public:
|
||||
JXRHandler();
|
||||
|
||||
bool canRead() const 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;
|
||||
|
||||
static bool canRead(QIODevice *device);
|
||||
|
||||
private:
|
||||
mutable QSharedDataPointer<JXRHandlerPrivate> d;
|
||||
|
||||
qint32 m_quality;
|
||||
};
|
||||
|
||||
class JXRPlugin : public QImageIOPlugin
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QImageIOHandlerFactoryInterface" FILE "jxr.json")
|
||||
|
||||
public:
|
||||
Capabilities capabilities(QIODevice *device, const QByteArray &format) const override;
|
||||
QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const override;
|
||||
};
|
||||
|
||||
#endif // KIMG_JXR_P_H
|
340
src/imageformats/pfm.cpp
Normal file
@ -0,0 +1,340 @@
|
||||
/*
|
||||
This file is part of the KDE project
|
||||
SPDX-FileCopyrightText: 2024 Mirco Miranda <mircomir@outlook.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
/*
|
||||
* See also: https://www.pauldebevec.com/Research/HDR/PFM/
|
||||
*/
|
||||
|
||||
#include "pfm_p.h"
|
||||
#include "util_p.h"
|
||||
|
||||
#include <QColorSpace>
|
||||
#include <QDataStream>
|
||||
#include <QIODevice>
|
||||
#include <QImage>
|
||||
#include <QLoggingCategory>
|
||||
|
||||
Q_DECLARE_LOGGING_CATEGORY(LOG_PFMPLUGIN)
|
||||
Q_LOGGING_CATEGORY(LOG_PFMPLUGIN, "kf.imageformats.plugins.pfm", QtWarningMsg)
|
||||
|
||||
class PFMHeader
|
||||
{
|
||||
private:
|
||||
/*!
|
||||
* \brief m_bw True if grayscale.
|
||||
*/
|
||||
bool m_bw;
|
||||
|
||||
/*!
|
||||
* \brief m_ps True if saved by Photoshop (Photoshop variant).
|
||||
*
|
||||
* When \a false the format of the header is the following (GIMP):
|
||||
* [type]
|
||||
* [xres] [yres]
|
||||
* [byte_order]
|
||||
*
|
||||
* When \a true the format of the header is the following (Photoshop):
|
||||
* [type]
|
||||
* [xres]
|
||||
* [yres]
|
||||
* [byte_order]
|
||||
*/
|
||||
bool m_ps;
|
||||
|
||||
/*!
|
||||
* \brief m_width The image width.
|
||||
*/
|
||||
qint32 m_width;
|
||||
|
||||
/*!
|
||||
* \brief m_height The image height.
|
||||
*/
|
||||
qint32 m_height;
|
||||
|
||||
/*!
|
||||
* \brief m_byteOrder The image byte orger.
|
||||
*/
|
||||
QDataStream::ByteOrder m_byteOrder;
|
||||
|
||||
public:
|
||||
PFMHeader() :
|
||||
m_bw(false),
|
||||
m_ps(false),
|
||||
m_width(0),
|
||||
m_height(0),
|
||||
m_byteOrder(QDataStream::BigEndian)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool isValid() const
|
||||
{
|
||||
return (m_width > 0 && m_height > 0);
|
||||
}
|
||||
|
||||
bool isBlackAndWhite() const
|
||||
{
|
||||
return m_bw;
|
||||
}
|
||||
|
||||
bool isPhotoshop() const
|
||||
{
|
||||
return m_ps;
|
||||
}
|
||||
|
||||
qint32 width() const
|
||||
{
|
||||
return m_width;
|
||||
}
|
||||
|
||||
qint32 height() const
|
||||
{
|
||||
return m_height;
|
||||
}
|
||||
|
||||
QSize size() const
|
||||
{
|
||||
return QSize(m_width, m_height);
|
||||
}
|
||||
|
||||
QDataStream::ByteOrder byteOrder() const
|
||||
{
|
||||
return m_byteOrder;
|
||||
}
|
||||
|
||||
QImage::Format format() const
|
||||
{
|
||||
if (isValid()) {
|
||||
return isBlackAndWhite() ? QImage::Format_Grayscale16 : QImage::Format_RGBX32FPx4;
|
||||
}
|
||||
return QImage::Format_Invalid;
|
||||
}
|
||||
|
||||
bool read(QIODevice *d)
|
||||
{
|
||||
auto pf = d->read(3);
|
||||
if (pf == QByteArray("PF\n")) {
|
||||
m_bw = false;
|
||||
} else if (pf == QByteArray("Pf\n")) {
|
||||
m_bw = true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
auto wh = QString::fromLatin1(d->readLine(128));
|
||||
auto list = wh.split(QStringLiteral(" "));
|
||||
if (list.size() == 1) {
|
||||
m_ps = true; // try for Photoshop version
|
||||
list << QString::fromLatin1(d->readLine(128));
|
||||
}
|
||||
if (list.size() != 2) {
|
||||
return false;
|
||||
}
|
||||
auto ok_o = false;
|
||||
auto ok_w = false;
|
||||
auto ok_h = false;
|
||||
auto o = QString::fromLatin1(d->readLine(128)).toDouble(&ok_o);
|
||||
auto w = list.first().toInt(&ok_w);
|
||||
auto h = list.last().toInt(&ok_h);
|
||||
if (!ok_o || !ok_w || !ok_h || o == 0) {
|
||||
return false;
|
||||
}
|
||||
m_width = w;
|
||||
m_height = h;
|
||||
m_byteOrder = o > 0 ? QDataStream::BigEndian : QDataStream::LittleEndian;
|
||||
return isValid();
|
||||
}
|
||||
|
||||
bool peek(QIODevice *d)
|
||||
{
|
||||
d->startTransaction();
|
||||
auto ok = read(d);
|
||||
d->rollbackTransaction();
|
||||
return ok;
|
||||
}
|
||||
} ;
|
||||
|
||||
class PFMHandlerPrivate
|
||||
{
|
||||
public:
|
||||
PFMHandlerPrivate() {}
|
||||
~PFMHandlerPrivate() {}
|
||||
|
||||
PFMHeader m_header;
|
||||
};
|
||||
|
||||
PFMHandler::PFMHandler()
|
||||
: QImageIOHandler()
|
||||
, d(new PFMHandlerPrivate)
|
||||
{
|
||||
}
|
||||
|
||||
bool PFMHandler::canRead() const
|
||||
{
|
||||
if (canRead(device())) {
|
||||
setFormat("pfm");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PFMHandler::canRead(QIODevice *device)
|
||||
{
|
||||
if (!device) {
|
||||
qCWarning(LOG_PFMPLUGIN) << "PFMHandler::canRead() called with no device";
|
||||
return false;
|
||||
}
|
||||
|
||||
PFMHeader h;
|
||||
if (!h.peek(device)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return h.isValid();
|
||||
}
|
||||
|
||||
bool PFMHandler::read(QImage *image)
|
||||
{
|
||||
auto&& header = d->m_header;
|
||||
if (!header.read(device())) {
|
||||
qCWarning(LOG_PFMPLUGIN) << "PFMHandler::read() invalid header";
|
||||
return false;
|
||||
}
|
||||
|
||||
QDataStream s(device());
|
||||
s.setFloatingPointPrecision(QDataStream::SinglePrecision);
|
||||
s.setByteOrder(header.byteOrder());
|
||||
|
||||
auto img = imageAlloc(header.size(), header.format());
|
||||
if (img.isNull()) {
|
||||
qCWarning(LOG_PFMPLUGIN) << "PFMHandler::read() error while allocating the image";
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto y = 0, h = img.height(); y < h; ++y) {
|
||||
float f;
|
||||
if (header.isBlackAndWhite()) {
|
||||
auto line = reinterpret_cast<quint16*>(img.scanLine(header.isPhotoshop() ? y : h - y - 1));
|
||||
for (auto x = 0, w = img.width(); x < w; ++x) {
|
||||
s >> f;
|
||||
// QColorSpace does not handle gray linear profile, so I have to convert to non-linear
|
||||
f = f < 0.0031308f ? (f * 12.92f) : (1.055 * std::pow(f, 1.0 / 2.4) - 0.055);
|
||||
line[x] = quint16(std::clamp(f, float(0), float(1)) * std::numeric_limits<quint16>::max() + float(0.5));
|
||||
|
||||
if (s.status() != QDataStream::Ok) {
|
||||
qCWarning(LOG_PFMPLUGIN) << "PFMHandler::read() detected corrupted data";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
auto line = reinterpret_cast<float*>(img.scanLine(header.isPhotoshop() ? y : h - y - 1));
|
||||
for (auto x = 0, n = img.width() * 4; x < n; x += 4) {
|
||||
s >> f;
|
||||
line[x] = std::clamp(f, float(0), float(1));
|
||||
s >> f;
|
||||
line[x + 1] = std::clamp(f, float(0), float(1));
|
||||
s >> f;
|
||||
line[x + 2] = std::clamp(f, float(0), float(1));
|
||||
line[x + 3] = float(1);
|
||||
|
||||
if (s.status() != QDataStream::Ok) {
|
||||
qCWarning(LOG_PFMPLUGIN) << "PFMHandler::read() detected corrupted data";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!header.isBlackAndWhite()) {
|
||||
img.setColorSpace(QColorSpace(QColorSpace::SRgbLinear));
|
||||
}
|
||||
|
||||
*image = img;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PFMHandler::supportsOption(ImageOption option) const
|
||||
{
|
||||
if (option == QImageIOHandler::Size) {
|
||||
return true;
|
||||
}
|
||||
if (option == QImageIOHandler::ImageFormat) {
|
||||
return true;
|
||||
}
|
||||
if (option == QImageIOHandler::Endianness) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QVariant PFMHandler::option(ImageOption option) const
|
||||
{
|
||||
QVariant v;
|
||||
|
||||
if (option == QImageIOHandler::Size) {
|
||||
auto&& h = d->m_header;
|
||||
if (h.isValid()) {
|
||||
v = QVariant::fromValue(h.size());
|
||||
} else if (auto dev = device()) {
|
||||
if (h.peek(dev)) {
|
||||
v = QVariant::fromValue(h.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (option == QImageIOHandler::ImageFormat) {
|
||||
auto&& h = d->m_header;
|
||||
if (h.isValid()) {
|
||||
v = QVariant::fromValue(h.format());
|
||||
} else if (auto dev = device()) {
|
||||
if (h.peek(dev)) {
|
||||
v = QVariant::fromValue(h.format());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (option == QImageIOHandler::Endianness) {
|
||||
auto&& h = d->m_header;
|
||||
if (h.isValid()) {
|
||||
v = QVariant::fromValue(h.byteOrder());
|
||||
} else if (auto dev = device()) {
|
||||
if (h.peek(dev)) {
|
||||
v = QVariant::fromValue(h.byteOrder());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
QImageIOPlugin::Capabilities PFMPlugin::capabilities(QIODevice *device, const QByteArray &format) const
|
||||
{
|
||||
if (format == "pfm") {
|
||||
return Capabilities(CanRead);
|
||||
}
|
||||
if (!format.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
if (!device->isOpen()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
Capabilities cap;
|
||||
if (device->isReadable() && PFMHandler::canRead(device)) {
|
||||
cap |= CanRead;
|
||||
}
|
||||
return cap;
|
||||
}
|
||||
|
||||
QImageIOHandler *PFMPlugin::create(QIODevice *device, const QByteArray &format) const
|
||||
{
|
||||
QImageIOHandler *handler = new PFMHandler;
|
||||
handler->setDevice(device);
|
||||
handler->setFormat(format);
|
||||
return handler;
|
||||
}
|
||||
|
||||
#include "moc_pfm_p.cpp"
|
4
src/imageformats/pfm.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"Keys": [ "pfm" ],
|
||||
"MimeTypes": [ "image/x-pfm" ]
|
||||
}
|
42
src/imageformats/pfm_p.h
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
This file is part of the KDE project
|
||||
SPDX-FileCopyrightText: 2024 Mirco Miranda <mircomir@outlook.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef KIMG_PFM_P_H
|
||||
#define KIMG_PFM_P_H
|
||||
|
||||
#include <QImageIOPlugin>
|
||||
#include <QScopedPointer>
|
||||
|
||||
class PFMHandlerPrivate;
|
||||
class PFMHandler : public QImageIOHandler
|
||||
{
|
||||
public:
|
||||
PFMHandler();
|
||||
|
||||
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);
|
||||
|
||||
private:
|
||||
const QScopedPointer<PFMHandlerPrivate> d;
|
||||
};
|
||||
|
||||
class PFMPlugin : public QImageIOPlugin
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QImageIOHandlerFactoryInterface" FILE "pfm.json")
|
||||
|
||||
public:
|
||||
Capabilities capabilities(QIODevice *device, const QByteArray &format) const override;
|
||||
QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const override;
|
||||
};
|
||||
|
||||
#endif // KIMG_PFM_P_H
|
@ -23,7 +23,7 @@
|
||||
* - Color spaces other than RGB/Grayscale cannot be read due to lack of QImage
|
||||
* support. Where possible, a conversion to RGB is done:
|
||||
* - CMYK images are converted using an approximated way that ignores the color
|
||||
* information (ICC profile).
|
||||
* information (ICC profile) with Qt less than 6.8.
|
||||
* - LAB images are converted to sRGB using literature formulas.
|
||||
* - MULICHANNEL images more than 3 channels are converted as CMYK images.
|
||||
* - DUOTONE images are considered as Grayscale images.
|
||||
@ -34,6 +34,7 @@
|
||||
|
||||
#include "fastmath_p.h"
|
||||
#include "psd_p.h"
|
||||
#include "scanlineconverter_p.h"
|
||||
#include "util_p.h"
|
||||
|
||||
#include <QDataStream>
|
||||
@ -60,9 +61,24 @@ typedef quint8 uchar;
|
||||
*/
|
||||
//#define PSD_FAST_LAB_CONVERSION
|
||||
|
||||
/*
|
||||
* Since Qt version 6.8, the 8-bit CMYK format is natively supported.
|
||||
* If you encounter problems with native CMYK support you can continue to force the plugin to convert
|
||||
* to RGB as in previous versions by defining PSD_NATIVE_CMYK_SUPPORT_DISABLED.
|
||||
*/
|
||||
//#define PSD_NATIVE_CMYK_SUPPORT_DISABLED
|
||||
|
||||
namespace // Private.
|
||||
{
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 8, 0) || defined(PSD_NATIVE_CMYK_SUPPORT_DISABLED)
|
||||
# define CMYK_FORMAT QImage::Format_Invalid
|
||||
#else
|
||||
# define CMYK_FORMAT QImage::Format_CMYK8888
|
||||
#endif
|
||||
|
||||
#define NATIVE_CMYK (CMYK_FORMAT != QImage::Format_Invalid)
|
||||
|
||||
enum Signature : quint32 {
|
||||
S_8BIM = 0x3842494D, // '8BIM'
|
||||
S_8B64 = 0x38423634, // '8B64'
|
||||
@ -96,6 +112,10 @@ enum LayerId : quint32 {
|
||||
};
|
||||
|
||||
struct PSDHeader {
|
||||
PSDHeader() {
|
||||
memset(this, 0, sizeof(PSDHeader));
|
||||
}
|
||||
|
||||
uint signature;
|
||||
ushort version;
|
||||
uchar reserved[6];
|
||||
@ -477,7 +497,7 @@ PSDColorModeDataSection readColorModeDataSection(QDataStream &s, bool *ok = null
|
||||
*/
|
||||
static bool setColorSpace(QImage& img, const PSDImageResourceSection& irs)
|
||||
{
|
||||
if (!irs.contains(IRI_ICCPROFILE))
|
||||
if (!irs.contains(IRI_ICCPROFILE) || img.isNull())
|
||||
return false;
|
||||
auto irb = irs.value(IRI_ICCPROFILE);
|
||||
auto cs = QColorSpace::fromIccProfile(irb.data);
|
||||
@ -505,7 +525,7 @@ static bool setXmpData(QImage& img, const PSDImageResourceSection& irs)
|
||||
// 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'm reusing the same key because a programs could search for it.
|
||||
img.setText(QStringLiteral("XML:com.adobe.xmp"), xmp);
|
||||
img.setText(QStringLiteral(META_KEY_XMP_ADOBE), xmp);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -742,7 +762,9 @@ static QImage::Format imageFormat(const PSDHeader &header, bool alpha)
|
||||
break;
|
||||
case CM_MULTICHANNEL: // Treat MCH as CMYK (number of channel check is done in IsSupported())
|
||||
case CM_CMYK: // Photoshop supports CMYK/MCH 8-bits and 16-bits only
|
||||
if (header.depth == 16)
|
||||
if (NATIVE_CMYK && header.channel_count == 4 && (header.depth == 16 || header.depth == 8))
|
||||
format = CMYK_FORMAT;
|
||||
else if (header.depth == 16)
|
||||
format = header.channel_count < 5 || !alpha ? QImage::Format_RGBX64 : QImage::Format_RGBA64;
|
||||
else if (header.depth == 8)
|
||||
format = header.channel_count < 5 || !alpha ? QImage::Format_RGB888 : QImage::Format_RGBA8888;
|
||||
@ -844,6 +866,18 @@ inline void planarToChunchy(uchar *target, const char *source, qint32 width, qin
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline void planarToChunchyCMYK(uchar *target, const char *source, qint32 width, qint32 c, qint32 cn)
|
||||
{
|
||||
auto s = reinterpret_cast<const T*>(source);
|
||||
auto t = reinterpret_cast<quint8*>(target);
|
||||
const T d = std::numeric_limits<T>::max() / std::numeric_limits<quint8>::max();
|
||||
for (qint32 x = 0; x < width; ++x) {
|
||||
t[x * cn + c] = quint8((std::numeric_limits<T>::max() - xchg(s[x])) / d);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template<class T>
|
||||
inline void planarToChunchyFloatToUInt16(uchar *target, const char *source, qint32 width, qint32 c, qint32 cn)
|
||||
{
|
||||
@ -903,6 +937,19 @@ inline void monoInvert(uchar *target, const char* source, qint32 bytes)
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline void rawChannelsCopyToCMYK(uchar *target, qint32 targetChannels, const char *source, qint32 sourceChannels, qint32 width)
|
||||
{
|
||||
auto s = reinterpret_cast<const T*>(source);
|
||||
auto t = reinterpret_cast<quint8*>(target);
|
||||
const T d = std::numeric_limits<T>::max() / std::numeric_limits<quint8>::max();
|
||||
for (qint32 c = 0, cs = std::min(targetChannels, sourceChannels); c < cs; ++c) {
|
||||
for (qint32 x = 0; x < width; ++x) {
|
||||
t[x * targetChannels + c] = (std::numeric_limits<T>::max() - s[x * sourceChannels + c]) / d;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline void rawChannelsCopy(uchar *target, qint32 targetChannels, const char *source, qint32 sourceChannels, qint32 width)
|
||||
{
|
||||
@ -915,6 +962,17 @@ inline void rawChannelsCopy(uchar *target, qint32 targetChannels, const char *so
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline void rawChannelCopy(uchar *target, qint32 targetChannels, qint32 targetChannel, const char *source, qint32 sourceChannels, qint32 sourceChannel, qint32 width)
|
||||
{
|
||||
auto s = reinterpret_cast<const T*>(source);
|
||||
auto t = reinterpret_cast<T*>(target);
|
||||
for (qint32 x = 0; x < width; ++x) {
|
||||
t[x * targetChannels + targetChannel] = s[x * sourceChannels + sourceChannel];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template<class T>
|
||||
inline void cmykToRgb(uchar *target, qint32 targetChannels, const char *source, qint32 sourceChannels, qint32 width, bool alpha = false)
|
||||
{
|
||||
@ -1103,6 +1161,7 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
|
||||
auto imgChannels = imageChannels(img.format());
|
||||
auto channel_num = std::min(qint32(header.channel_count), imgChannels);
|
||||
auto raw_count = qsizetype(header.width * header.depth + 7) / 8;
|
||||
auto native_cmyk = img.format() == CMYK_FORMAT;
|
||||
|
||||
if (header.height > kMaxQVectorSize / header.channel_count / sizeof(quint32)) {
|
||||
qWarning() << "LoadPSD() header height/channel_count too big" << header.height << header.channel_count;
|
||||
@ -1138,13 +1197,25 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
|
||||
|
||||
// clang-format off
|
||||
// checks the need of color conversion (that requires random access to the image)
|
||||
auto randomAccess = (header.color_mode == CM_CMYK) ||
|
||||
auto randomAccess = (header.color_mode == CM_CMYK && !native_cmyk) ||
|
||||
(header.color_mode == CM_MULTICHANNEL && !native_cmyk) ||
|
||||
(header.color_mode == CM_LABCOLOR) ||
|
||||
(header.color_mode == CM_MULTICHANNEL) ||
|
||||
(header.color_mode != CM_INDEXED && img.hasAlphaChannel());
|
||||
// clang-format on
|
||||
|
||||
if (randomAccess) {
|
||||
// CMYK with spots (e.g. CMYKA) ICC conversion to RGBA/RGBX
|
||||
QImage tmpCmyk;
|
||||
ScanLineConverter iccConv(img.format());
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) && !defined(PSD_NATIVE_CMYK_SUPPORT_DISABLED)
|
||||
if (header.color_mode == CM_CMYK && img.format() != QImage::Format_CMYK8888) {
|
||||
auto tmpi = QImage(header.width, 1, QImage::Format_CMYK8888);
|
||||
if (setColorSpace(tmpi, irs))
|
||||
tmpCmyk = tmpi;
|
||||
iccConv.setTargetColorSpace(QColorSpace(QColorSpace::SRgb));
|
||||
}
|
||||
#endif
|
||||
|
||||
// In order to make a colorspace transformation, we need all channels of a scanline
|
||||
QByteArray psdScanline;
|
||||
psdScanline.resize(qsizetype(header.width * header.depth * header.channel_count + 7) / 8);
|
||||
@ -1200,10 +1271,26 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
|
||||
|
||||
// Conversion to RGB
|
||||
if (header.color_mode == CM_CMYK || header.color_mode == CM_MULTICHANNEL) {
|
||||
if (header.depth == 8)
|
||||
cmykToRgb<quint8>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha);
|
||||
else if (header.depth == 16)
|
||||
cmykToRgb<quint16>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha);
|
||||
if (tmpCmyk.isNull()) {
|
||||
if (header.depth == 8)
|
||||
cmykToRgb<quint8>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha);
|
||||
else if (header.depth == 16)
|
||||
cmykToRgb<quint16>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha);
|
||||
}
|
||||
else if (header.depth == 8) {
|
||||
rawChannelsCopyToCMYK<quint8>(tmpCmyk.bits(), 4, psdScanline.data(), header.channel_count, header.width);
|
||||
if (auto rgbPtr = iccConv.convertedScanLine(tmpCmyk, 0))
|
||||
std::memcpy(img.scanLine(y), rgbPtr, img.bytesPerLine());
|
||||
if (imgChannels == 4 && header.channel_count >= 5)
|
||||
rawChannelCopy<quint8>(img.scanLine(y), imgChannels, 3, psdScanline.data(), header.channel_count, 4, header.width);
|
||||
}
|
||||
else if (header.depth == 16) {
|
||||
rawChannelsCopyToCMYK<quint16>(tmpCmyk.bits(), 4, psdScanline.data(), header.channel_count, header.width);
|
||||
if (auto rgbPtr = iccConv.convertedScanLine(tmpCmyk, 0))
|
||||
std::memcpy(img.scanLine(y), rgbPtr, img.bytesPerLine());
|
||||
if (imgChannels == 4 && header.channel_count >= 5)
|
||||
rawChannelCopy<quint16>(img.scanLine(y), imgChannels, 3, psdScanline.data(), header.channel_count, 4, header.width);
|
||||
}
|
||||
}
|
||||
if (header.color_mode == CM_LABCOLOR) {
|
||||
if (header.depth == 8)
|
||||
@ -1235,11 +1322,17 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
|
||||
if (header.depth == 1) { // Bitmap
|
||||
monoInvert(scanLine, rawStride.data(), std::min(rawStride.size(), img.bytesPerLine()));
|
||||
}
|
||||
else if (header.depth == 8) { // 8-bits images: Indexed, Grayscale, RGB/RGBA
|
||||
planarToChunchy<quint8>(scanLine, rawStride.data(), header.width, c, imgChannels);
|
||||
else if (header.depth == 8) { // 8-bits images: Indexed, Grayscale, RGB/RGBA, CMYK, MCH4
|
||||
if (native_cmyk)
|
||||
planarToChunchyCMYK<quint8>(scanLine, rawStride.data(), header.width, c, imgChannels);
|
||||
else
|
||||
planarToChunchy<quint8>(scanLine, rawStride.data(), header.width, c, imgChannels);
|
||||
}
|
||||
else if (header.depth == 16) { // 16-bits integer images: Grayscale, RGB/RGBA
|
||||
planarToChunchy<quint16>(scanLine, rawStride.data(), header.width, c, imgChannels);
|
||||
else if (header.depth == 16) { // 16-bits integer images: Grayscale, RGB/RGBA, CMYK, MCH4
|
||||
if (native_cmyk)
|
||||
planarToChunchyCMYK<quint16>(scanLine, rawStride.data(), header.width, c, imgChannels);
|
||||
else
|
||||
planarToChunchy<quint16>(scanLine, rawStride.data(), header.width, c, imgChannels);
|
||||
}
|
||||
else if (header.depth == 32 && header.color_mode == CM_RGB) { // 32-bits float images: RGB/RGBA
|
||||
planarToChunchy<float>(scanLine, rawStride.data(), header.width, c, imgChannels);
|
||||
@ -1251,7 +1344,6 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Resolution info
|
||||
if (!setResolution(img, irs)) {
|
||||
// qDebug() << "No resolution info found!";
|
||||
@ -1287,7 +1379,17 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
|
||||
|
||||
} // Private
|
||||
|
||||
class PSDHandlerPrivate
|
||||
{
|
||||
public:
|
||||
PSDHandlerPrivate() {}
|
||||
~PSDHandlerPrivate() {}
|
||||
PSDHeader m_header;
|
||||
};
|
||||
|
||||
PSDHandler::PSDHandler()
|
||||
: QImageIOHandler()
|
||||
, d(new PSDHandlerPrivate)
|
||||
{
|
||||
}
|
||||
|
||||
@ -1305,7 +1407,7 @@ bool PSDHandler::read(QImage *image)
|
||||
QDataStream s(device());
|
||||
s.setByteOrder(QDataStream::BigEndian);
|
||||
|
||||
PSDHeader header;
|
||||
auto&& header = d->m_header;
|
||||
s >> header;
|
||||
|
||||
// Check image file format.
|
||||
@ -1342,18 +1444,20 @@ QVariant PSDHandler::option(ImageOption option) const
|
||||
QVariant v;
|
||||
|
||||
if (option == QImageIOHandler::Size) {
|
||||
if (auto d = device()) {
|
||||
auto&& header = d->m_header;
|
||||
if (IsValid(header)) {
|
||||
v = QVariant::fromValue(QSize(header.width, header.height));
|
||||
}
|
||||
else if (auto dev = device()) {
|
||||
// transactions works on both random and sequential devices
|
||||
d->startTransaction();
|
||||
auto ba = d->read(sizeof(PSDHeader));
|
||||
d->rollbackTransaction();
|
||||
dev->startTransaction();
|
||||
auto ba = dev->read(sizeof(PSDHeader));
|
||||
dev->rollbackTransaction();
|
||||
|
||||
QDataStream s(ba);
|
||||
s.setByteOrder(QDataStream::BigEndian);
|
||||
|
||||
PSDHeader header;
|
||||
s >> header;
|
||||
|
||||
if (s.status() == QDataStream::Ok && IsValid(header))
|
||||
v = QVariant::fromValue(QSize(header.width, header.height));
|
||||
}
|
||||
@ -1384,7 +1488,11 @@ bool PSDHandler::canRead(QIODevice *device)
|
||||
}
|
||||
|
||||
if (device->isSequential()) {
|
||||
if (header.color_mode == CM_CMYK || header.color_mode == CM_LABCOLOR || header.color_mode == CM_MULTICHANNEL) {
|
||||
if (header.color_mode == CM_CMYK || header.color_mode == CM_MULTICHANNEL) {
|
||||
if (header.channel_count != 4 || !NATIVE_CMYK)
|
||||
return false;
|
||||
}
|
||||
if (header.color_mode == CM_LABCOLOR) {
|
||||
return false;
|
||||
}
|
||||
if (header.color_mode == CM_RGB && header.channel_count > 3) {
|
||||
|
@ -9,7 +9,9 @@
|
||||
#define KIMG_PSD_P_H
|
||||
|
||||
#include <QImageIOPlugin>
|
||||
#include <QScopedPointer>
|
||||
|
||||
class PSDHandlerPrivate;
|
||||
class PSDHandler : public QImageIOHandler
|
||||
{
|
||||
public:
|
||||
@ -22,6 +24,9 @@ public:
|
||||
QVariant option(QImageIOHandler::ImageOption option) const override;
|
||||
|
||||
static bool canRead(QIODevice *device);
|
||||
|
||||
private:
|
||||
const QScopedPointer<PSDHandlerPrivate> d;
|
||||
};
|
||||
|
||||
class PSDPlugin : public QImageIOPlugin
|
||||
|
282
src/imageformats/pxr.cpp
Normal file
@ -0,0 +1,282 @@
|
||||
/*
|
||||
This file is part of the KDE project
|
||||
SPDX-FileCopyrightText: 2024 Mirco Miranda <mircomir@outlook.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "pxr_p.h"
|
||||
#include "util_p.h"
|
||||
|
||||
#include <QIODevice>
|
||||
#include <QImage>
|
||||
#include <QLoggingCategory>
|
||||
|
||||
Q_DECLARE_LOGGING_CATEGORY(LOG_PXRPLUGIN)
|
||||
Q_LOGGING_CATEGORY(LOG_PXRPLUGIN, "kf.imageformats.plugins.pxr", QtWarningMsg)
|
||||
|
||||
class PXRHeader
|
||||
{
|
||||
private:
|
||||
QByteArray m_rawHeader;
|
||||
|
||||
quint16 ui16(quint8 c1, quint8 c2) const {
|
||||
return (quint16(c2) << 8) | quint16(c1);
|
||||
}
|
||||
|
||||
quint32 ui32(quint8 c1, quint8 c2, quint8 c3, quint8 c4) const {
|
||||
return (quint32(c4) << 24) | (quint32(c3) << 16) | (quint32(c2) << 8) | quint32(c1);
|
||||
}
|
||||
|
||||
public:
|
||||
PXRHeader()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool isValid() const
|
||||
{
|
||||
return (m_rawHeader.size() == 512 &&
|
||||
m_rawHeader.startsWith(QByteArray::fromRawData("\x80\xE8\x00\x00", 4)));
|
||||
}
|
||||
|
||||
bool isSupported() const
|
||||
{
|
||||
return format() != QImage::Format_Invalid;
|
||||
}
|
||||
|
||||
qint32 width() const
|
||||
{
|
||||
if (!isValid()) {
|
||||
return 0;
|
||||
}
|
||||
return qint32(ui16(m_rawHeader.at(418), m_rawHeader.at(419)));
|
||||
}
|
||||
|
||||
qint32 height() const
|
||||
{
|
||||
if (!isValid()) {
|
||||
return 0;
|
||||
}
|
||||
return qint32(ui16(m_rawHeader.at(416), m_rawHeader.at(417)));
|
||||
}
|
||||
|
||||
QSize size() const
|
||||
{
|
||||
return QSize(width(), height());
|
||||
}
|
||||
|
||||
qint32 channel() const
|
||||
{
|
||||
if (!isValid()) {
|
||||
return 0;
|
||||
}
|
||||
return qint32(ui16(m_rawHeader.at(424), m_rawHeader.at(425)));
|
||||
}
|
||||
|
||||
qint32 depth() const
|
||||
{
|
||||
if (!isValid()) {
|
||||
return 0;
|
||||
}
|
||||
return qint32(ui16(m_rawHeader.at(426), m_rawHeader.at(427)));
|
||||
}
|
||||
|
||||
// supposing the image offset (always 1024 on sample files)
|
||||
qint32 offset() const
|
||||
{
|
||||
if (!isValid()) {
|
||||
return 0;
|
||||
}
|
||||
return qint32(ui16(m_rawHeader.at(428), m_rawHeader.at(429)));
|
||||
}
|
||||
|
||||
QImage::Format format() const
|
||||
{
|
||||
if (channel() == 14 && depth() == 2) {
|
||||
return QImage::Format_RGB888;
|
||||
}
|
||||
if (channel() == 8 && depth() == 2) {
|
||||
return QImage::Format_Grayscale8;
|
||||
}
|
||||
return QImage::Format_Invalid;
|
||||
}
|
||||
|
||||
qsizetype strideSize() const
|
||||
{
|
||||
if (format() == QImage::Format_RGB888) {
|
||||
return width() * 3;
|
||||
}
|
||||
if (format() == QImage::Format_Grayscale8) {
|
||||
return width();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool read(QIODevice *d)
|
||||
{
|
||||
m_rawHeader = d->read(512);
|
||||
return isValid();
|
||||
}
|
||||
|
||||
bool peek(QIODevice *d)
|
||||
{
|
||||
d->startTransaction();
|
||||
auto ok = read(d);
|
||||
d->rollbackTransaction();
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool jumpToImageData(QIODevice *d) const
|
||||
{
|
||||
if (d->isSequential()) {
|
||||
if (auto sz = std::max(offset() - qint32(m_rawHeader.size()), 0)) {
|
||||
return d->read(sz).size() == sz;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return d->seek(offset());
|
||||
}
|
||||
};
|
||||
|
||||
class PXRHandlerPrivate
|
||||
{
|
||||
public:
|
||||
PXRHandlerPrivate() {}
|
||||
~PXRHandlerPrivate() {}
|
||||
|
||||
PXRHeader m_header;
|
||||
};
|
||||
|
||||
PXRHandler::PXRHandler()
|
||||
: QImageIOHandler()
|
||||
, d(new PXRHandlerPrivate)
|
||||
{
|
||||
}
|
||||
|
||||
bool PXRHandler::canRead() const
|
||||
{
|
||||
if (canRead(device())) {
|
||||
setFormat("pxr");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PXRHandler::canRead(QIODevice *device)
|
||||
{
|
||||
if (!device) {
|
||||
qCWarning(LOG_PXRPLUGIN) << "PXRHandler::canRead() called with no device";
|
||||
return false;
|
||||
}
|
||||
|
||||
PXRHeader h;
|
||||
if (!h.peek(device)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return h.isSupported();
|
||||
}
|
||||
|
||||
bool PXRHandler::read(QImage *image)
|
||||
{
|
||||
auto&& header = d->m_header;
|
||||
|
||||
if (!header.read(device())) {
|
||||
qCWarning(LOG_PXRPLUGIN) << "PXRHandler::read() invalid header";
|
||||
return false;
|
||||
}
|
||||
|
||||
auto img = imageAlloc(header.size(), header.format());
|
||||
if (img.isNull()) {
|
||||
qCWarning(LOG_PXRPLUGIN) << "PXRHandler::read() error while allocating the image";
|
||||
return false;
|
||||
}
|
||||
|
||||
auto d = device();
|
||||
if (!header.jumpToImageData(d)) {
|
||||
qCWarning(LOG_PXRPLUGIN) << "PXRHandler::read() error while seeking image data";
|
||||
return false;
|
||||
}
|
||||
|
||||
auto size = std::min(img.bytesPerLine(), header.strideSize());
|
||||
for (auto y = 0, h = img.height(); y < h; ++y) {
|
||||
auto line = reinterpret_cast<char*>(img.scanLine(y));
|
||||
if (d->read(line, size) != size) {
|
||||
qCWarning(LOG_PXRPLUGIN) << "PXRHandler::read() error while reading image scanline";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
*image = img;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PXRHandler::supportsOption(ImageOption option) const
|
||||
{
|
||||
if (option == QImageIOHandler::Size) {
|
||||
return true;
|
||||
}
|
||||
if (option == QImageIOHandler::ImageFormat) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QVariant PXRHandler::option(ImageOption option) const
|
||||
{
|
||||
QVariant v;
|
||||
|
||||
if (option == QImageIOHandler::Size) {
|
||||
auto&& h = d->m_header;
|
||||
if (h.isValid()) {
|
||||
v = QVariant::fromValue(h.size());
|
||||
} else if (auto d = device()) {
|
||||
if (h.peek(d)) {
|
||||
v = QVariant::fromValue(h.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (option == QImageIOHandler::ImageFormat) {
|
||||
auto&& h = d->m_header;
|
||||
if (h.isValid()) {
|
||||
v = QVariant::fromValue(h.format());
|
||||
} else if (auto d = device()) {
|
||||
if (h.peek(d)) {
|
||||
v = QVariant::fromValue(h.format());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
QImageIOPlugin::Capabilities PXRPlugin::capabilities(QIODevice *device, const QByteArray &format) const
|
||||
{
|
||||
if (format == "pxr") {
|
||||
return Capabilities(CanRead);
|
||||
}
|
||||
if (!format.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
if (!device->isOpen()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
Capabilities cap;
|
||||
if (device->isReadable() && PXRHandler::canRead(device)) {
|
||||
cap |= CanRead;
|
||||
}
|
||||
return cap;
|
||||
}
|
||||
|
||||
QImageIOHandler *PXRPlugin::create(QIODevice *device, const QByteArray &format) const
|
||||
{
|
||||
QImageIOHandler *handler = new PXRHandler;
|
||||
handler->setDevice(device);
|
||||
handler->setFormat(format);
|
||||
return handler;
|
||||
}
|
||||
|
||||
#include "moc_pxr_p.cpp"
|
4
src/imageformats/pxr.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"Keys": [ "pxr" ],
|
||||
"MimeTypes": [ "image/x-pxr" ]
|
||||
}
|
42
src/imageformats/pxr_p.h
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
This file is part of the KDE project
|
||||
SPDX-FileCopyrightText: 2024 Mirco Miranda <mircomir@outlook.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef KIMG_PXR_P_H
|
||||
#define KIMG_PXR_P_H
|
||||
|
||||
#include <QImageIOPlugin>
|
||||
#include <QScopedPointer>
|
||||
|
||||
class PXRHandlerPrivate;
|
||||
class PXRHandler : public QImageIOHandler
|
||||
{
|
||||
public:
|
||||
PXRHandler();
|
||||
|
||||
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);
|
||||
|
||||
private:
|
||||
const QScopedPointer<PXRHandlerPrivate> d;
|
||||
};
|
||||
|
||||
class PXRPlugin : public QImageIOPlugin
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QImageIOHandlerFactoryInterface" FILE "pxr.json")
|
||||
|
||||
public:
|
||||
Capabilities capabilities(QIODevice *device, const QByteArray &format) const override;
|
||||
QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const override;
|
||||
};
|
||||
|
||||
#endif // KIMG_PXR_P_H
|
@ -31,6 +31,18 @@ namespace // Private
|
||||
#define QOI_END_STREAM_PAD 8
|
||||
|
||||
struct QoiHeader {
|
||||
QoiHeader()
|
||||
: MagicNumber(0)
|
||||
, Width(0)
|
||||
, Height(0)
|
||||
, Channels(0)
|
||||
, Colorspace(2)
|
||||
{
|
||||
}
|
||||
|
||||
QoiHeader(const QoiHeader&) = default;
|
||||
QoiHeader& operator=(const QoiHeader&) = default;
|
||||
|
||||
quint32 MagicNumber;
|
||||
quint32 Width;
|
||||
quint32 Height;
|
||||
@ -297,7 +309,19 @@ static bool SaveQOI(QIODevice *device, const QoiHeader &qoi, const QImage &img)
|
||||
|
||||
} // namespace
|
||||
|
||||
class QOIHandlerPrivate
|
||||
{
|
||||
public:
|
||||
QOIHandlerPrivate() {}
|
||||
~QOIHandlerPrivate() {}
|
||||
|
||||
QoiHeader m_header;
|
||||
};
|
||||
|
||||
|
||||
QOIHandler::QOIHandler()
|
||||
: QImageIOHandler()
|
||||
, d(new QOIHandlerPrivate)
|
||||
{
|
||||
}
|
||||
|
||||
@ -328,7 +352,7 @@ bool QOIHandler::canRead(QIODevice *device)
|
||||
|
||||
QDataStream stream(head);
|
||||
stream.setByteOrder(QDataStream::BigEndian);
|
||||
QoiHeader qoi = {0, 0, 0, 0, 2};
|
||||
QoiHeader qoi;
|
||||
stream >> qoi;
|
||||
|
||||
return IsSupported(qoi);
|
||||
@ -340,7 +364,7 @@ bool QOIHandler::read(QImage *image)
|
||||
s.setByteOrder(QDataStream::BigEndian);
|
||||
|
||||
// Read image header
|
||||
QoiHeader qoi = {0, 0, 0, 0, 2};
|
||||
auto&& qoi = d->m_header;
|
||||
s >> qoi;
|
||||
|
||||
// Check if file is supported
|
||||
@ -402,7 +426,10 @@ QVariant QOIHandler::option(ImageOption option) const
|
||||
QVariant v;
|
||||
|
||||
if (option == QImageIOHandler::Size) {
|
||||
if (auto d = device()) {
|
||||
auto&& header = d->m_header;
|
||||
if (IsSupported(header)) {
|
||||
v = QVariant::fromValue(QSize(header.Width, header.Height));
|
||||
} else if (auto d = device()) {
|
||||
// transactions works on both random and sequential devices
|
||||
d->startTransaction();
|
||||
auto ba = d->read(sizeof(QoiHeader));
|
||||
@ -410,10 +437,7 @@ QVariant QOIHandler::option(ImageOption option) const
|
||||
|
||||
QDataStream s(ba);
|
||||
s.setByteOrder(QDataStream::BigEndian);
|
||||
|
||||
QoiHeader header = {0, 0, 0, 0, 2};
|
||||
s >> header;
|
||||
|
||||
if (s.status() == QDataStream::Ok && IsSupported(header)) {
|
||||
v = QVariant::fromValue(QSize(header.Width, header.Height));
|
||||
}
|
||||
@ -421,7 +445,10 @@ QVariant QOIHandler::option(ImageOption option) const
|
||||
}
|
||||
|
||||
if (option == QImageIOHandler::ImageFormat) {
|
||||
if (auto d = device()) {
|
||||
auto&& header = d->m_header;
|
||||
if (IsSupported(header)) {
|
||||
v = QVariant::fromValue(imageFormat(header));
|
||||
} else if (auto d = device()) {
|
||||
// transactions works on both random and sequential devices
|
||||
d->startTransaction();
|
||||
auto ba = d->read(sizeof(QoiHeader));
|
||||
@ -429,10 +456,7 @@ QVariant QOIHandler::option(ImageOption option) const
|
||||
|
||||
QDataStream s(ba);
|
||||
s.setByteOrder(QDataStream::BigEndian);
|
||||
|
||||
QoiHeader header = {0, 0, 0, 0, 2};
|
||||
s >> header;
|
||||
|
||||
if (s.status() == QDataStream::Ok && IsSupported(header)) {
|
||||
v = QVariant::fromValue(imageFormat(header));
|
||||
}
|
||||
|
@ -9,7 +9,9 @@
|
||||
#define KIMG_QOI_P_H
|
||||
|
||||
#include <QImageIOPlugin>
|
||||
#include <QScopedPointer>
|
||||
|
||||
class QOIHandlerPrivate;
|
||||
class QOIHandler : public QImageIOHandler
|
||||
{
|
||||
public:
|
||||
@ -23,6 +25,9 @@ public:
|
||||
QVariant option(QImageIOHandler::ImageOption option) const override;
|
||||
|
||||
static bool canRead(QIODevice *device);
|
||||
|
||||
private:
|
||||
const QScopedPointer<QOIHandlerPrivate> d;
|
||||
};
|
||||
|
||||
class QOIPlugin : public QImageIOPlugin
|
||||
|
@ -221,9 +221,16 @@ static bool LoadRAS(QDataStream &s, const RasHeader &ras, QImage &img)
|
||||
|
||||
// Read palette if needed.
|
||||
if (ras.ColorMapType == RAS_COLOR_MAP_TYPE_RGB) {
|
||||
// max 256 rgb elements palette is supported
|
||||
if (ras.ColorMapLength > 768) {
|
||||
return false;
|
||||
}
|
||||
QList<quint8> palette(ras.ColorMapLength);
|
||||
for (quint32 i = 0; i < ras.ColorMapLength; ++i) {
|
||||
s >> palette[i];
|
||||
if (s.status() != QDataStream::Ok) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
QList<QRgb> colorTable;
|
||||
for (quint32 i = 0, n = ras.ColorMapLength / 3; i < n; ++i) {
|
||||
@ -233,9 +240,6 @@ static bool LoadRAS(QDataStream &s, const RasHeader &ras, QImage &img)
|
||||
colorTable << qRgb(255, 255, 255);
|
||||
}
|
||||
img.setColorTable(colorTable);
|
||||
if (s.status() != QDataStream::Ok) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
LineDecoder dec(s.device(), ras);
|
||||
@ -332,7 +336,19 @@ static bool LoadRAS(QDataStream &s, const RasHeader &ras, QImage &img)
|
||||
}
|
||||
} // namespace
|
||||
|
||||
class RASHandlerPrivate
|
||||
{
|
||||
public:
|
||||
RASHandlerPrivate() {}
|
||||
~RASHandlerPrivate() {}
|
||||
|
||||
RasHeader m_header;
|
||||
};
|
||||
|
||||
|
||||
RASHandler::RASHandler()
|
||||
: QImageIOHandler()
|
||||
, d(new RASHandlerPrivate)
|
||||
{
|
||||
}
|
||||
|
||||
@ -380,7 +396,7 @@ bool RASHandler::read(QImage *outImage)
|
||||
s.setByteOrder(QDataStream::BigEndian);
|
||||
|
||||
// Read image header.
|
||||
RasHeader ras;
|
||||
auto&& ras = d->m_header;
|
||||
s >> ras;
|
||||
|
||||
if (ras.ColorMapLength > kMaxQVectorSize) {
|
||||
@ -422,18 +438,19 @@ QVariant RASHandler::option(ImageOption option) const
|
||||
QVariant v;
|
||||
|
||||
if (option == QImageIOHandler::Size) {
|
||||
if (auto d = device()) {
|
||||
auto&& header = d->m_header;
|
||||
if (IsSupported(header)) {
|
||||
v = QVariant::fromValue(QSize(header.Width, header.Height));
|
||||
}
|
||||
else if (auto dev = device()) {
|
||||
// transactions works on both random and sequential devices
|
||||
d->startTransaction();
|
||||
auto ba = d->read(RasHeader::SIZE);
|
||||
d->rollbackTransaction();
|
||||
dev->startTransaction();
|
||||
auto ba = dev->read(RasHeader::SIZE);
|
||||
dev->rollbackTransaction();
|
||||
|
||||
QDataStream s(ba);
|
||||
s.setByteOrder(QDataStream::BigEndian);
|
||||
|
||||
RasHeader header;
|
||||
s >> header;
|
||||
|
||||
if (s.status() == QDataStream::Ok && IsSupported(header)) {
|
||||
v = QVariant::fromValue(QSize(header.Width, header.Height));
|
||||
}
|
||||
@ -441,18 +458,19 @@ QVariant RASHandler::option(ImageOption option) const
|
||||
}
|
||||
|
||||
if (option == QImageIOHandler::ImageFormat) {
|
||||
if (auto d = device()) {
|
||||
auto&& header = d->m_header;
|
||||
if (IsSupported(header)) {
|
||||
v = QVariant::fromValue(imageFormat(header));
|
||||
}
|
||||
else if (auto dev = device()) {
|
||||
// transactions works on both random and sequential devices
|
||||
d->startTransaction();
|
||||
auto ba = d->read(RasHeader::SIZE);
|
||||
d->rollbackTransaction();
|
||||
dev->startTransaction();
|
||||
auto ba = dev->read(RasHeader::SIZE);
|
||||
dev->rollbackTransaction();
|
||||
|
||||
QDataStream s(ba);
|
||||
s.setByteOrder(QDataStream::BigEndian);
|
||||
|
||||
RasHeader header;
|
||||
s >> header;
|
||||
|
||||
if (s.status() == QDataStream::Ok && IsSupported(header)) {
|
||||
v = QVariant::fromValue(imageFormat(header));
|
||||
}
|
||||
|
@ -10,7 +10,9 @@
|
||||
#define KIMG_RAS_P_H
|
||||
|
||||
#include <QImageIOPlugin>
|
||||
#include <QScopedPointer>
|
||||
|
||||
class RASHandlerPrivate;
|
||||
class RASHandler : public QImageIOHandler
|
||||
{
|
||||
public:
|
||||
@ -23,6 +25,9 @@ public:
|
||||
QVariant option(QImageIOHandler::ImageOption option) const override;
|
||||
|
||||
static bool canRead(QIODevice *device);
|
||||
|
||||
private:
|
||||
const QScopedPointer<RASHandlerPrivate> d;
|
||||
};
|
||||
|
||||
class RASPlugin : public QImageIOPlugin
|
||||
|
@ -690,29 +690,29 @@ bool LoadRAW(QImageIOHandler *handler, QImage &img)
|
||||
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()));
|
||||
img.setText(QStringLiteral(META_KEY_XMP_ADOBE), updateXmpPacket(xmpPacket, rawProcessor.get()));
|
||||
|
||||
auto model = QString::fromUtf8(iparams.normalized_model);
|
||||
if (!model.isEmpty()) {
|
||||
img.setText(QStringLiteral("Model"), model);
|
||||
img.setText(QStringLiteral(META_KEY_MODEL), model);
|
||||
}
|
||||
auto manufacturer = QString::fromUtf8(iparams.normalized_make);
|
||||
if (!manufacturer.isEmpty()) {
|
||||
img.setText(QStringLiteral("Manufacturer"), manufacturer);
|
||||
img.setText(QStringLiteral(META_KEY_MANUFACTURER), manufacturer);
|
||||
}
|
||||
auto software = QString::fromUtf8(iparams.software);
|
||||
if (!software.isEmpty()) {
|
||||
img.setText(QStringLiteral("Software"), software);
|
||||
img.setText(QStringLiteral(META_KEY_SOFTWARE), software);
|
||||
}
|
||||
|
||||
auto &&iother = rawProcessor->imgdata.other;
|
||||
auto description = QString::fromUtf8(iother.desc);
|
||||
if (!description.isEmpty()) {
|
||||
img.setText(QStringLiteral("Description"), description);
|
||||
img.setText(QStringLiteral(META_KEY_DESCRIPTION), description);
|
||||
}
|
||||
auto artist = QString::fromUtf8(iother.artist);
|
||||
if (!artist.isEmpty()) {
|
||||
img.setText(QStringLiteral("Author"), artist);
|
||||
img.setText(QStringLiteral(META_KEY_AUTHOR), artist);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -360,11 +360,15 @@ bool SGIImage::readImage(QImage &img)
|
||||
}
|
||||
|
||||
_lengthtab = new quint32[_numrows];
|
||||
for (l = 0; l < _numrows; l++) {
|
||||
for (l = 0; !_stream.atEnd() && l < _numrows; l++) {
|
||||
_stream >> _lengthtab[l];
|
||||
}
|
||||
}
|
||||
|
||||
if (_stream.status() != QDataStream::Ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_data = _dev->readAll();
|
||||
|
||||
// sanity check
|
||||
|
@ -72,9 +72,18 @@ const uchar *ScanLineConverter::convertedScanLine(const QImage &image, qint32 y)
|
||||
if (!cs.isValid()) {
|
||||
cs = _defaultColorSpace;
|
||||
}
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
|
||||
if (tmp.depth() < 8 && cs.colorModel() == QColorSpace::ColorModel::Gray) {
|
||||
tmp.convertTo(QImage::Format_Grayscale8);
|
||||
}
|
||||
else if (tmp.depth() < 24 && cs.colorModel() == QColorSpace::ColorModel::Rgb) {
|
||||
tmp.convertTo(tmp.hasAlphaChannel() ? QImage::Format_ARGB32 : QImage::Format_RGB32);
|
||||
}
|
||||
#else
|
||||
if (tmp.depth() < 24) {
|
||||
tmp.convertTo(tmp.hasAlphaChannel() ? QImage::Format_ARGB32 : QImage::Format_RGB32);
|
||||
}
|
||||
#endif
|
||||
tmp.setColorSpace(cs);
|
||||
tmp.convertToColorSpace(_colorSpace);
|
||||
}
|
||||
|
@ -56,18 +56,18 @@ enum TGAType {
|
||||
|
||||
/** Tga Header. */
|
||||
struct TgaHeader {
|
||||
uchar id_length;
|
||||
uchar colormap_type;
|
||||
uchar image_type;
|
||||
ushort colormap_index;
|
||||
ushort colormap_length;
|
||||
uchar colormap_size;
|
||||
ushort x_origin;
|
||||
ushort y_origin;
|
||||
ushort width;
|
||||
ushort height;
|
||||
uchar pixel_size;
|
||||
uchar flags;
|
||||
uchar id_length = 0;
|
||||
uchar colormap_type = 0;
|
||||
uchar image_type = 0;
|
||||
ushort colormap_index = 0;
|
||||
ushort colormap_length = 0;
|
||||
uchar colormap_size = 0;
|
||||
ushort x_origin = 0;
|
||||
ushort y_origin = 0;
|
||||
ushort width = 0;
|
||||
ushort height = 0;
|
||||
uchar pixel_size = 0;
|
||||
uchar flags = 0;
|
||||
|
||||
enum {
|
||||
SIZE = 18,
|
||||
@ -407,7 +407,18 @@ static bool LoadTGA(QDataStream &s, const TgaHeader &tga, QImage &img)
|
||||
|
||||
} // namespace
|
||||
|
||||
class TGAHandlerPrivate
|
||||
{
|
||||
public:
|
||||
TGAHandlerPrivate() {}
|
||||
~TGAHandlerPrivate() {}
|
||||
|
||||
TgaHeader m_header;
|
||||
};
|
||||
|
||||
TGAHandler::TGAHandler()
|
||||
: QImageIOHandler()
|
||||
, d(new TGAHandlerPrivate)
|
||||
{
|
||||
}
|
||||
|
||||
@ -424,20 +435,20 @@ bool TGAHandler::read(QImage *outImage)
|
||||
{
|
||||
// qDebug() << "Loading TGA file!";
|
||||
|
||||
auto d = device();
|
||||
TgaHeader tga;
|
||||
if (!peekHeader(d, tga) || !IsSupported(tga)) {
|
||||
auto dev = device();
|
||||
auto&& tga = d->m_header;
|
||||
if (!peekHeader(dev, tga) || !IsSupported(tga)) {
|
||||
// qDebug() << "This TGA file is not valid.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (d->isSequential()) {
|
||||
d->read(TgaHeader::SIZE + tga.id_length);
|
||||
if (dev->isSequential()) {
|
||||
dev->read(TgaHeader::SIZE + tga.id_length);
|
||||
} else {
|
||||
d->seek(TgaHeader::SIZE + tga.id_length);
|
||||
dev->seek(TgaHeader::SIZE + tga.id_length);
|
||||
}
|
||||
|
||||
QDataStream s(d);
|
||||
QDataStream s(dev);
|
||||
s.setByteOrder(QDataStream::LittleEndian);
|
||||
|
||||
// Check image file format.
|
||||
@ -519,18 +530,22 @@ QVariant TGAHandler::option(ImageOption option) const
|
||||
QVariant v;
|
||||
|
||||
if (option == QImageIOHandler::Size) {
|
||||
if (auto d = device()) {
|
||||
TgaHeader header;
|
||||
if (peekHeader(d, header) && IsSupported(header)) {
|
||||
auto&& header = d->m_header;
|
||||
if (IsSupported(header)) {
|
||||
v = QVariant::fromValue(QSize(header.width, header.height));
|
||||
} else if (auto dev = device()) {
|
||||
if (peekHeader(dev, 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)) {
|
||||
auto&& header = d->m_header;
|
||||
if (IsSupported(header)) {
|
||||
v = QVariant::fromValue(imageFormat(header));
|
||||
} else if (auto dev = device()) {
|
||||
if (peekHeader(dev, header) && IsSupported(header)) {
|
||||
v = QVariant::fromValue(imageFormat(header));
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,9 @@
|
||||
#define KIMG_TGA_P_H
|
||||
|
||||
#include <QImageIOPlugin>
|
||||
#include <QScopedPointer>
|
||||
|
||||
class TGAHandlerPrivate;
|
||||
class TGAHandler : public QImageIOHandler
|
||||
{
|
||||
public:
|
||||
@ -23,6 +25,9 @@ public:
|
||||
QVariant option(QImageIOHandler::ImageOption option) const override;
|
||||
|
||||
static bool canRead(QIODevice *device);
|
||||
|
||||
private:
|
||||
const QScopedPointer<TGAHandlerPrivate> d;
|
||||
};
|
||||
|
||||
class TGAPlugin : public QImageIOPlugin
|
||||
|
@ -13,6 +13,26 @@
|
||||
#include <QImage>
|
||||
#include <QImageIOHandler>
|
||||
|
||||
// Image metadata keys to use in plugins (so they are consistent)
|
||||
#define META_KEY_ALTITUDE "Altitude"
|
||||
#define META_KEY_AUTHOR "Author"
|
||||
#define META_KEY_COMMENT "Comment"
|
||||
#define META_KEY_COPYRIGHT "Copyright"
|
||||
#define META_KEY_CREATIONDATE "CreationDate"
|
||||
#define META_KEY_DESCRIPTION "Description"
|
||||
#define META_KEY_DOCUMENTNAME "DocumentName"
|
||||
#define META_KEY_HOSTCOMPUTER "HostComputer"
|
||||
#define META_KEY_LATITUDE "Latitude"
|
||||
#define META_KEY_LONGITUDE "Longitude"
|
||||
#define META_KEY_HOSTCOMPUTER "HostComputer"
|
||||
#define META_KEY_MANUFACTURER "Manufacturer"
|
||||
#define META_KEY_MODEL "Model"
|
||||
#define META_KEY_OWNER "Owner"
|
||||
#define META_KEY_SOFTWARE "Software"
|
||||
#define META_KEY_TITLE "Title"
|
||||
#define META_KEY_XML_GIMP "XML:org.gimp.xml"
|
||||
#define META_KEY_XMP_ADOBE "XML:com.adobe.xmp"
|
||||
|
||||
// QList uses some extra space for stuff, hence the 32 here suggested by Thiago Macieira
|
||||
static constexpr int kMaxQVectorSize = std::numeric_limits<int>::max() - 32;
|
||||
|
||||
|
@ -1091,7 +1091,9 @@ bool XCFImageFormat::loadProperty(QDataStream &xcf_io, PropType &type, QByteArra
|
||||
size = 0;
|
||||
} else {
|
||||
xcf_io >> size;
|
||||
if (size > 256000) {
|
||||
if (size > 256000 * 4) {
|
||||
// NOTE: I didn't find any reference to maximum property dimensions in the specs, so I assume it's just a sanity check.
|
||||
qCDebug(XCFPLUGIN) << "XCF: loadProperty skips" << type << "due to size being too large";
|
||||
return false;
|
||||
}
|
||||
data = new char[size];
|
||||
@ -1594,7 +1596,7 @@ void XCFImageFormat::setImageParasites(const XCFImage &xcf_image, QImage &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));
|
||||
image.setText(QStringLiteral(META_KEY_COMMENT), QString::fromUtf8(value));
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -1605,7 +1607,7 @@ void XCFImageFormat::setImageParasites(const XCFImage &xcf_image, QImage &image)
|
||||
// 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));
|
||||
image.setText(QStringLiteral(META_KEY_XML_GIMP), QString::fromUtf8(value));
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -1621,7 +1623,7 @@ void XCFImageFormat::setImageParasites(const XCFImage &xcf_image, QImage &image)
|
||||
// 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));
|
||||
image.setText(QStringLiteral(META_KEY_XMP_ADOBE), QString::fromUtf8(value));
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
@ -2753,10 +2755,10 @@ void XCFImageFormat::copyLayerToImage(XCFImage &xcf_image)
|
||||
// For each tile...
|
||||
|
||||
for (uint j = 0; j < layer.nrows; j++) {
|
||||
uint y = j * TILE_HEIGHT;
|
||||
qint32 y = qint32(j * TILE_HEIGHT);
|
||||
|
||||
for (uint i = 0; i < layer.ncols; i++) {
|
||||
uint x = i * TILE_WIDTH;
|
||||
qint32 x = qint32(i * TILE_WIDTH);
|
||||
|
||||
// This seems the best place to apply the dissolve because it
|
||||
// depends on the global position of each tile's
|
||||
@ -3043,7 +3045,7 @@ void XCFImageFormat::mergeLayerIntoImage(XCFImage &xcf_image)
|
||||
merge = mergeRGBToRGB;
|
||||
break;
|
||||
case GRAY_GIMAGE:
|
||||
if (layer.opacity == OPAQUE_OPACITY) {
|
||||
if (layer.opacity == OPAQUE_OPACITY && xcf_image.image.depth() <= 8) {
|
||||
merge = mergeGrayToGray;
|
||||
} else {
|
||||
merge = mergeGrayToRGB;
|
||||
@ -3179,10 +3181,10 @@ void XCFImageFormat::mergeLayerIntoImage(XCFImage &xcf_image)
|
||||
qCDebug(XCFPLUGIN) << "Using QPainter for mode" << layer.mode;
|
||||
|
||||
for (uint j = 0; j < layer.nrows; j++) {
|
||||
uint y = j * TILE_HEIGHT;
|
||||
qint32 y = qint32(j * TILE_HEIGHT);
|
||||
|
||||
for (uint i = 0; i < layer.ncols; i++) {
|
||||
uint x = i * TILE_WIDTH;
|
||||
qint32 x = qint32(i * TILE_WIDTH);
|
||||
|
||||
QImage &tile = layer.image_tiles[j][i];
|
||||
if (x + layer.x_offset < MAX_IMAGE_WIDTH &&
|
||||
@ -3208,10 +3210,10 @@ void XCFImageFormat::mergeLayerIntoImage(XCFImage &xcf_image)
|
||||
#endif
|
||||
|
||||
for (uint j = 0; j < layer.nrows; j++) {
|
||||
uint y = j * TILE_HEIGHT;
|
||||
qint32 y = qint32(j * TILE_HEIGHT);
|
||||
|
||||
for (uint i = 0; i < layer.ncols; i++) {
|
||||
uint x = i * TILE_WIDTH;
|
||||
qint32 x = qint32(i * TILE_WIDTH);
|
||||
|
||||
// This seems the best place to apply the dissolve because it
|
||||
// depends on the global position of each tile's
|
||||
@ -3851,6 +3853,9 @@ bool XCFImageFormat::mergeGrayAToRGB(const Layer &layer, uint i, uint j, int k,
|
||||
}
|
||||
|
||||
switch (layer.mode) {
|
||||
case GIMP_LAYER_MODE_NORMAL:
|
||||
case GIMP_LAYER_MODE_NORMAL_LEGACY:
|
||||
break;
|
||||
case GIMP_LAYER_MODE_MULTIPLY:
|
||||
case GIMP_LAYER_MODE_MULTIPLY_LEGACY: {
|
||||
src = INT_MULT(src, dst);
|
||||
@ -4144,7 +4149,9 @@ bool XCFHandler::canRead() const
|
||||
bool XCFHandler::read(QImage *image)
|
||||
{
|
||||
XCFImageFormat xcfif;
|
||||
return xcfif.readXCF(device(), image);
|
||||
auto ok = xcfif.readXCF(device(), image);
|
||||
m_imageSize = image->size();
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool XCFHandler::write(const QImage &)
|
||||
@ -4164,6 +4171,9 @@ QVariant XCFHandler::option(ImageOption option) const
|
||||
QVariant v;
|
||||
|
||||
if (option == QImageIOHandler::Size) {
|
||||
if (!m_imageSize.isEmpty()) {
|
||||
return m_imageSize;
|
||||
}
|
||||
/*
|
||||
* The image structure always starts at offset 0 in the XCF file.
|
||||
* byte[9] "gimp xcf " File type identification
|
||||
@ -4176,7 +4186,7 @@ QVariant XCFHandler::option(ImageOption option) const
|
||||
* uint32 width Width of canvas
|
||||
* uint32 height Height of canvas
|
||||
*/
|
||||
if (auto d = device()) {
|
||||
else if (auto d = device()) {
|
||||
// transactions works on both random and sequential devices
|
||||
d->startTransaction();
|
||||
auto ba9 = d->read(9); // "gimp xcf "
|
||||
|
@ -24,6 +24,13 @@ public:
|
||||
QVariant option(QImageIOHandler::ImageOption option) const override;
|
||||
|
||||
static bool canRead(QIODevice *device);
|
||||
|
||||
private:
|
||||
/*!
|
||||
* \brief m_imageSize
|
||||
* Image size cache used by option()
|
||||
*/
|
||||
QSize m_imageSize;
|
||||
};
|
||||
|
||||
class XCFPlugin : public QImageIOPlugin
|
||||
|