mirror of
https://invent.kde.org/frameworks/kimageformats.git
synced 2025-07-14 19:04:17 -04:00
Compare commits
11 Commits
Author | SHA1 | Date | |
---|---|---|---|
a6ec69d276 | |||
02cbf3889f | |||
6cf05cf305 | |||
938b8126b5 | |||
d36c191351 | |||
1acb5a6177 | |||
f2ccbf1724 | |||
5825c83235 | |||
b742cb7cc7 | |||
2e6eeebdfc | |||
db0b5d571a |
@ -5,7 +5,7 @@ project(KImageFormats)
|
||||
set (CMAKE_CXX_STANDARD 14)
|
||||
|
||||
include(FeatureSummary)
|
||||
find_package(ECM 5.75.0 NO_MODULE)
|
||||
find_package(ECM 5.77.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)
|
||||
|
||||
@ -19,7 +19,7 @@ include(KDECMakeSettings)
|
||||
|
||||
include(CheckIncludeFiles)
|
||||
|
||||
set(REQUIRED_QT_VERSION 5.12.0)
|
||||
set(REQUIRED_QT_VERSION 5.13.0)
|
||||
find_package(Qt5Gui ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE)
|
||||
|
||||
find_package(KF5Archive)
|
||||
@ -50,8 +50,8 @@ set_package_properties(OpenEXR PROPERTIES
|
||||
add_definitions(-DQT_NO_FOREACH)
|
||||
# 050d00 (5.13) triggers a BIC in qimageiohandler.h, in Qt 5.13, so do not enable that until we can require 5.14
|
||||
# https://codereview.qt-project.org/c/qt/qtbase/+/279215
|
||||
add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x050c00)
|
||||
add_definitions(-DKF_DISABLE_DEPRECATED_BEFORE_AND_AT=0x054900)
|
||||
add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x050d00)
|
||||
add_definitions(-DKF_DISABLE_DEPRECATED_BEFORE_AND_AT=0x054B00)
|
||||
add_subdirectory(src)
|
||||
if (BUILD_TESTING)
|
||||
add_subdirectory(autotests)
|
||||
|
BIN
autotests/read/psd/16bit-rle.png
Normal file
BIN
autotests/read/psd/16bit-rle.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 78 KiB |
BIN
autotests/read/psd/16bit-rle.psd
Normal file
BIN
autotests/read/psd/16bit-rle.psd
Normal file
Binary file not shown.
BIN
autotests/read/psd/argb16-raw-affinityphoto-1.8.5.png
Normal file
BIN
autotests/read/psd/argb16-raw-affinityphoto-1.8.5.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 983 B |
BIN
autotests/read/psd/argb16-raw-affinityphoto-1.8.5.psd
Normal file
BIN
autotests/read/psd/argb16-raw-affinityphoto-1.8.5.psd
Normal file
Binary file not shown.
BIN
autotests/read/psd/rgb16-raw-affinityphoto-1.8.5.png
Normal file
BIN
autotests/read/psd/rgb16-raw-affinityphoto-1.8.5.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 827 B |
BIN
autotests/read/psd/rgb16-raw-affinityphoto-1.8.5.psd
Normal file
BIN
autotests/read/psd/rgb16-raw-affinityphoto-1.8.5.psd
Normal file
Binary file not shown.
@ -36,14 +36,17 @@ static void writeImageData(const char *name, const QString &filename, const QIma
|
||||
}
|
||||
}
|
||||
|
||||
// allow each byte to be different by up to 1, to allow for rounding errors
|
||||
template<class Trait>
|
||||
static bool fuzzyeq(const QImage &im1, const QImage &im2, uchar fuzziness)
|
||||
{
|
||||
Q_ASSERT(im1.format() == im2.format());
|
||||
Q_ASSERT(im1.depth() == 24 || im1.depth() == 32 || im1.depth() == 64);
|
||||
|
||||
const int height = im1.height();
|
||||
const int width = im1.width();
|
||||
for (int i = 0; i < height; ++i) {
|
||||
const uchar *line1 = im1.scanLine(i);
|
||||
const uchar *line2 = im2.scanLine(i);
|
||||
const Trait *line1 = reinterpret_cast<const Trait*>(im1.scanLine(i));
|
||||
const Trait *line2 = reinterpret_cast<const Trait*>(im2.scanLine(i));
|
||||
for (int j = 0; j < width; ++j) {
|
||||
if (line1[j] > line2[j]) {
|
||||
if (line1[j] - line2[j] > fuzziness)
|
||||
@ -57,6 +60,30 @@ static bool fuzzyeq(const QImage &im1, const QImage &im2, uchar fuzziness)
|
||||
return true;
|
||||
}
|
||||
|
||||
// allow each byte to be different by up to 1, to allow for rounding errors
|
||||
static bool fuzzyeq(const QImage &im1, const QImage &im2, uchar fuzziness)
|
||||
{
|
||||
return (im1.depth() == 64) ? fuzzyeq<quint16>(im1, im2, fuzziness)
|
||||
: fuzzyeq<quint8>(im1, im2, fuzziness);
|
||||
}
|
||||
|
||||
// Returns the original format if we support, or returns
|
||||
// format which we preferred to use for `fuzzyeq()`.
|
||||
// We do only support formats with 8-bits/16-bits pre pixel.
|
||||
// If that changed, don't forget to update `fuzzyeq()` too
|
||||
static QImage::Format preferredFormat(QImage::Format fmt)
|
||||
{
|
||||
switch (fmt) {
|
||||
case QImage::Format_RGB32:
|
||||
case QImage::Format_ARGB32:
|
||||
case QImage::Format_RGBX64:
|
||||
case QImage::Format_RGBA64:
|
||||
return fmt;
|
||||
default:
|
||||
return QImage::Format_ARGB32;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char ** argv)
|
||||
{
|
||||
QCoreApplication app(argc, argv);
|
||||
@ -168,19 +195,23 @@ int main(int argc, char ** argv)
|
||||
<< expImage.height() << "\n";
|
||||
++failed;
|
||||
} else {
|
||||
if (inputImage.format() != QImage::Format_ARGB32) {
|
||||
QImage::Format inputFormat = preferredFormat(inputImage.format());
|
||||
QImage::Format expFormat = preferredFormat(expImage.format());
|
||||
QImage::Format cmpFormat = inputFormat == expFormat ? inputFormat : QImage::Format_ARGB32;
|
||||
|
||||
if (inputImage.format() != cmpFormat) {
|
||||
QTextStream(stdout) << "INFO : " << fi.fileName()
|
||||
<< ": converting " << fi.fileName()
|
||||
<< " from " << formatToString(inputImage.format())
|
||||
<< " to ARGB32\n";
|
||||
inputImage = inputImage.convertToFormat(QImage::Format_ARGB32);
|
||||
<< " to " << formatToString(cmpFormat) << '\n';
|
||||
inputImage = inputImage.convertToFormat(cmpFormat);
|
||||
}
|
||||
if (expImage.format() != QImage::Format_ARGB32) {
|
||||
if (expImage.format() != cmpFormat) {
|
||||
QTextStream(stdout) << "INFO : " << fi.fileName()
|
||||
<< ": converting " << expfilename
|
||||
<< " from " << formatToString(expImage.format())
|
||||
<< " to ARGB32\n";
|
||||
expImage = expImage.convertToFormat(QImage::Format_ARGB32);
|
||||
<< " to " << formatToString(cmpFormat) << '\n';
|
||||
expImage = expImage.convertToFormat(cmpFormat);
|
||||
}
|
||||
if (fuzzyeq(inputImage, expImage, fuzziness)) {
|
||||
QTextStream(stdout) << "PASS : " << fi.fileName() << "\n";
|
||||
|
@ -87,7 +87,7 @@ static bool IsSupported(const PSDHeader &header)
|
||||
if (header.channel_count > 16) {
|
||||
return false;
|
||||
}
|
||||
if (header.depth != 8) {
|
||||
if (header.depth != 8 && header.depth != 16) {
|
||||
return false;
|
||||
}
|
||||
if (header.color_mode != CM_RGB) {
|
||||
@ -104,11 +104,13 @@ static void skip_section(QDataStream &s)
|
||||
s.skipRawData(section_length);
|
||||
}
|
||||
|
||||
static quint8 readPixel(QDataStream &stream) {
|
||||
quint8 pixel;
|
||||
template <class Trait>
|
||||
static Trait readPixel(QDataStream &stream) {
|
||||
Trait pixel;
|
||||
stream >> pixel;
|
||||
return pixel;
|
||||
}
|
||||
|
||||
static QRgb updateRed(QRgb oldPixel, quint8 redPixel) {
|
||||
return qRgba(redPixel, qGreen(oldPixel), qBlue(oldPixel), qAlpha(oldPixel));
|
||||
}
|
||||
@ -149,15 +151,18 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
|
||||
|
||||
quint32 channel_num = header.channel_count;
|
||||
|
||||
QImage::Format fmt = QImage::Format_RGB32;
|
||||
QImage::Format fmt = header.depth == 8 ? QImage::Format_RGB32
|
||||
: QImage::Format_RGBX64;
|
||||
// Clear the image.
|
||||
if (channel_num >= 4) {
|
||||
// Enable alpha.
|
||||
fmt = QImage::Format_ARGB32;
|
||||
fmt = header.depth == 8 ? QImage::Format_ARGB32
|
||||
: QImage::Format_RGBA64;
|
||||
|
||||
// Ignore the other channels.
|
||||
channel_num = 4;
|
||||
}
|
||||
|
||||
img = QImage(header.width, header.height, fmt);
|
||||
if (img.isNull()) {
|
||||
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width, header.height);
|
||||
@ -166,9 +171,10 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
|
||||
img.fill(qRgb(0,0,0));
|
||||
|
||||
const quint32 pixel_count = header.height * header.width;
|
||||
const quint32 channel_size = pixel_count * header.depth / 8;
|
||||
|
||||
// Verify this, as this is used to write into the memory of the QImage
|
||||
if (pixel_count > img.sizeInBytes() / sizeof(QRgb)) {
|
||||
if (pixel_count > img.sizeInBytes() / (header.depth == 8 ? sizeof(QRgb) : sizeof(QRgba64))) {
|
||||
qWarning() << "Invalid pixel count!" << pixel_count << "bytes available:" << img.sizeInBytes();
|
||||
return false;
|
||||
}
|
||||
@ -186,6 +192,14 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
|
||||
updateAlpha
|
||||
};
|
||||
|
||||
typedef QRgba64(*channelUpdater16)(QRgba64, quint16);
|
||||
static const channelUpdater16 updaters64[4] = {
|
||||
[](QRgba64 oldPixel, quint16 redPixel) {return qRgba64((oldPixel & ~(0xFFFFull << 0)) | (quint64( redPixel) << 0));},
|
||||
[](QRgba64 oldPixel, quint16 greenPixel){return qRgba64((oldPixel & ~(0xFFFFull << 16)) | (quint64(greenPixel) << 16));},
|
||||
[](QRgba64 oldPixel, quint16 bluePixel) {return qRgba64((oldPixel & ~(0xFFFFull << 32)) | (quint64( bluePixel) << 32));},
|
||||
[](QRgba64 oldPixel, quint16 alphaPixel){return qRgba64((oldPixel & ~(0xFFFFull << 48)) | (quint64(alphaPixel) << 48));}
|
||||
};
|
||||
|
||||
if (compression) {
|
||||
// Skip row lengths.
|
||||
int skip_count = header.height * header.channel_count * sizeof(quint16);
|
||||
@ -194,9 +208,18 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
|
||||
}
|
||||
|
||||
for (unsigned short channel = 0; channel < channel_num; channel++) {
|
||||
bool success = decodeRLEData(RLEVariant::PackBits, stream,
|
||||
image_data, pixel_count,
|
||||
&readPixel, updaters[channel]);
|
||||
bool success = false;
|
||||
if (header.depth == 8) {
|
||||
success = decodeRLEData(RLEVariant::PackBits, stream,
|
||||
image_data, channel_size,
|
||||
&readPixel<quint8>, updaters[channel]);
|
||||
} else if (header.depth == 16) {
|
||||
QRgba64 *image_data = reinterpret_cast<QRgba64*>(img.bits());
|
||||
success = decodeRLEData(RLEVariant::PackBits16, stream,
|
||||
image_data, channel_size,
|
||||
&readPixel<quint8>, updaters64[channel]);
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
qDebug() << "decodeRLEData on channel" << channel << "failed";
|
||||
return false;
|
||||
@ -204,8 +227,15 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
|
||||
}
|
||||
} else {
|
||||
for (unsigned short channel = 0; channel < channel_num; channel++) {
|
||||
for (unsigned i = 0; i < pixel_count; ++i) {
|
||||
image_data[i] = updaters[channel](image_data[i], readPixel(stream));
|
||||
if (header.depth == 8) {
|
||||
for (unsigned i = 0; i < pixel_count; ++i) {
|
||||
image_data[i] = updaters[channel](image_data[i], readPixel<quint8>(stream));
|
||||
}
|
||||
} else if (header.depth == 16) {
|
||||
QRgba64 *image_data = reinterpret_cast<QRgba64*>(img.bits());
|
||||
for (unsigned i = 0; i < pixel_count; ++i) {
|
||||
image_data[i] = updaters64[channel](image_data[i], readPixel<quint16>(stream));
|
||||
}
|
||||
}
|
||||
// make sure we didn't try to read past the end of the stream
|
||||
if (stream.status() != QDataStream::Ok) {
|
||||
|
@ -24,6 +24,11 @@ enum class RLEVariant {
|
||||
* of size 2, 130 of size 3, up to 255 of size 128.
|
||||
*/
|
||||
PackBits,
|
||||
/**
|
||||
* Same as PackBits, but treat unpacked data as
|
||||
* 16-bit integers.
|
||||
*/
|
||||
PackBits16,
|
||||
/**
|
||||
* PIC-style RLE
|
||||
*
|
||||
@ -67,6 +72,8 @@ static inline bool decodeRLEData(RLEVariant variant,
|
||||
Func2 updateItem)
|
||||
{
|
||||
unsigned offset = 0; // in dest
|
||||
bool is_msb = true; // only used for 16-bit PackBits, data is big-endian
|
||||
quint16 temp_data = 0;
|
||||
while (offset < length) {
|
||||
unsigned remaining = length - offset;
|
||||
quint8 count1;
|
||||
@ -85,7 +92,7 @@ static inline bool decodeRLEData(RLEVariant variant,
|
||||
// 2 to 128 repetitions
|
||||
length = count1 - 127u;
|
||||
}
|
||||
} else if (variant == RLEVariant::PackBits) {
|
||||
} else if (variant == RLEVariant::PackBits || variant == RLEVariant::PackBits16) {
|
||||
if (count1 == 128u) {
|
||||
// Ignore value 128
|
||||
continue;
|
||||
@ -102,7 +109,18 @@ static inline bool decodeRLEData(RLEVariant variant,
|
||||
}
|
||||
auto datum = readData(stream);
|
||||
for (unsigned i = offset; i < offset + length; ++i) {
|
||||
dest[i] = updateItem(dest[i], datum);
|
||||
if (variant == RLEVariant::PackBits16) {
|
||||
if (is_msb) {
|
||||
temp_data = datum << 8;
|
||||
is_msb = false;
|
||||
} else {
|
||||
temp_data |= datum;
|
||||
dest[i >> 1] = updateItem(dest[i >> 1], temp_data);
|
||||
is_msb = true;
|
||||
}
|
||||
} else {
|
||||
dest[i] = updateItem(dest[i], datum);
|
||||
}
|
||||
}
|
||||
offset += length;
|
||||
} else {
|
||||
@ -114,7 +132,18 @@ static inline bool decodeRLEData(RLEVariant variant,
|
||||
}
|
||||
for (unsigned i = offset; i < offset + length; ++i) {
|
||||
auto datum = readData(stream);
|
||||
dest[i] = updateItem(dest[i], datum);
|
||||
if (variant == RLEVariant::PackBits16) {
|
||||
if (is_msb) {
|
||||
temp_data = datum << 8;
|
||||
is_msb = false;
|
||||
} else {
|
||||
temp_data |= datum;
|
||||
dest[i >> 1] = updateItem(dest[i >> 1], temp_data);
|
||||
is_msb = true;
|
||||
}
|
||||
} else {
|
||||
dest[i] = updateItem(dest[i], datum);
|
||||
}
|
||||
}
|
||||
offset += length;
|
||||
}
|
||||
|
@ -7,56 +7,24 @@
|
||||
#ifndef FORMAT_ENUM_H
|
||||
#define FORMAT_ENUM_H
|
||||
|
||||
#include <QMetaEnum>
|
||||
#include <QImage>
|
||||
|
||||
// Generated from QImage::Format enum
|
||||
static const char * qimage_format_enum_names[] = {
|
||||
"Invalid",
|
||||
"Mono",
|
||||
"MonoLSB",
|
||||
"Indexed8",
|
||||
"RGB32",
|
||||
"ARGB32",
|
||||
"ARGB32_Premultiplied",
|
||||
"RGB16",
|
||||
"ARGB8565_Premultiplied",
|
||||
"RGB666",
|
||||
"ARGB6666_Premultiplied",
|
||||
"RGB555",
|
||||
"ARGB8555_Premultiplied",
|
||||
"RGB888",
|
||||
"RGB444",
|
||||
"ARGB4444_Premultiplied",
|
||||
"RGBX8888",
|
||||
"RGBA8888",
|
||||
"RGBA8888_Premultiplied"
|
||||
};
|
||||
// Never claim there are more than QImage::NImageFormats supported formats.
|
||||
// This is future-proofing against the above list being extended.
|
||||
static const int qimage_format_enum_names_count =
|
||||
(sizeof(qimage_format_enum_names) / sizeof(*qimage_format_enum_names) > int(QImage::NImageFormats))
|
||||
? int(QImage::NImageFormats)
|
||||
: (sizeof(qimage_format_enum_names) / sizeof(*qimage_format_enum_names));
|
||||
|
||||
QImage::Format formatFromString(const QString &str)
|
||||
{
|
||||
for (int i = 0; i < qimage_format_enum_names_count; ++i) {
|
||||
if (str.compare(QLatin1String(qimage_format_enum_names[i]), Qt::CaseInsensitive) == 0) {
|
||||
return (QImage::Format)(i);
|
||||
}
|
||||
}
|
||||
return QImage::Format_Invalid;
|
||||
const QMetaEnum metaEnum = QMetaEnum::fromType<QImage::Format>();
|
||||
const QString enumString = QStringLiteral("Format_") + str;
|
||||
|
||||
bool ok;
|
||||
const int res = metaEnum.keyToValue(enumString.toLatin1().constData(), &ok);
|
||||
|
||||
return ok ? static_cast<QImage::Format>(res) : QImage::Format_Invalid;
|
||||
}
|
||||
|
||||
QString formatToString(QImage::Format format)
|
||||
{
|
||||
int index = int(format);
|
||||
if (index > 0 && index < qimage_format_enum_names_count) {
|
||||
return QLatin1String(qimage_format_enum_names[index]);
|
||||
}
|
||||
return QLatin1String("<unknown:") +
|
||||
QString::number(index) +
|
||||
QLatin1String(">");
|
||||
const QMetaEnum metaEnum = QMetaEnum::fromType<QImage::Format>();
|
||||
return QString::fromLatin1(metaEnum.valueToKey(format)).remove(QStringLiteral("Format_"));
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -67,8 +67,8 @@ int main(int argc, char **argv)
|
||||
QTextStream out(stdout);
|
||||
out << "QImage formats:\n";
|
||||
// skip QImage::Format_Invalid
|
||||
for (int i = 1; i < qimage_format_enum_names_count; ++i) {
|
||||
out << " " << qimage_format_enum_names[i] << '\n';
|
||||
for (int i = 1; i < QImage::NImageFormats; ++i) {
|
||||
out << " " << formatToString(static_cast<QImage::Format>(i)) << '\n';
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
Reference in New Issue
Block a user