Compare commits

...

11 Commits

12 changed files with 129 additions and 71 deletions

View File

@ -5,7 +5,7 @@ project(KImageFormats)
set (CMAKE_CXX_STANDARD 14) set (CMAKE_CXX_STANDARD 14)
include(FeatureSummary) 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") set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://commits.kde.org/extra-cmake-modules")
feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES) feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES)
@ -19,7 +19,7 @@ include(KDECMakeSettings)
include(CheckIncludeFiles) 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(Qt5Gui ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE)
find_package(KF5Archive) find_package(KF5Archive)
@ -50,8 +50,8 @@ set_package_properties(OpenEXR PROPERTIES
add_definitions(-DQT_NO_FOREACH) 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 # 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 # https://codereview.qt-project.org/c/qt/qtbase/+/279215
add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x050c00) add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x050d00)
add_definitions(-DKF_DISABLE_DEPRECATED_BEFORE_AND_AT=0x054900) add_definitions(-DKF_DISABLE_DEPRECATED_BEFORE_AND_AT=0x054B00)
add_subdirectory(src) add_subdirectory(src)
if (BUILD_TESTING) if (BUILD_TESTING)
add_subdirectory(autotests) add_subdirectory(autotests)

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 983 B

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 827 B

Binary file not shown.

View File

@ -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) 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 height = im1.height();
const int width = im1.width(); const int width = im1.width();
for (int i = 0; i < height; ++i) { for (int i = 0; i < height; ++i) {
const uchar *line1 = im1.scanLine(i); const Trait *line1 = reinterpret_cast<const Trait*>(im1.scanLine(i));
const uchar *line2 = im2.scanLine(i); const Trait *line2 = reinterpret_cast<const Trait*>(im2.scanLine(i));
for (int j = 0; j < width; ++j) { for (int j = 0; j < width; ++j) {
if (line1[j] > line2[j]) { if (line1[j] > line2[j]) {
if (line1[j] - line2[j] > fuzziness) if (line1[j] - line2[j] > fuzziness)
@ -57,6 +60,30 @@ static bool fuzzyeq(const QImage &im1, const QImage &im2, uchar fuzziness)
return true; 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) int main(int argc, char ** argv)
{ {
QCoreApplication app(argc, argv); QCoreApplication app(argc, argv);
@ -168,19 +195,23 @@ int main(int argc, char ** argv)
<< expImage.height() << "\n"; << expImage.height() << "\n";
++failed; ++failed;
} else { } 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() QTextStream(stdout) << "INFO : " << fi.fileName()
<< ": converting " << fi.fileName() << ": converting " << fi.fileName()
<< " from " << formatToString(inputImage.format()) << " from " << formatToString(inputImage.format())
<< " to ARGB32\n"; << " to " << formatToString(cmpFormat) << '\n';
inputImage = inputImage.convertToFormat(QImage::Format_ARGB32); inputImage = inputImage.convertToFormat(cmpFormat);
} }
if (expImage.format() != QImage::Format_ARGB32) { if (expImage.format() != cmpFormat) {
QTextStream(stdout) << "INFO : " << fi.fileName() QTextStream(stdout) << "INFO : " << fi.fileName()
<< ": converting " << expfilename << ": converting " << expfilename
<< " from " << formatToString(expImage.format()) << " from " << formatToString(expImage.format())
<< " to ARGB32\n"; << " to " << formatToString(cmpFormat) << '\n';
expImage = expImage.convertToFormat(QImage::Format_ARGB32); expImage = expImage.convertToFormat(cmpFormat);
} }
if (fuzzyeq(inputImage, expImage, fuzziness)) { if (fuzzyeq(inputImage, expImage, fuzziness)) {
QTextStream(stdout) << "PASS : " << fi.fileName() << "\n"; QTextStream(stdout) << "PASS : " << fi.fileName() << "\n";

View File

@ -87,7 +87,7 @@ static bool IsSupported(const PSDHeader &header)
if (header.channel_count > 16) { if (header.channel_count > 16) {
return false; return false;
} }
if (header.depth != 8) { if (header.depth != 8 && header.depth != 16) {
return false; return false;
} }
if (header.color_mode != CM_RGB) { if (header.color_mode != CM_RGB) {
@ -104,11 +104,13 @@ static void skip_section(QDataStream &s)
s.skipRawData(section_length); s.skipRawData(section_length);
} }
static quint8 readPixel(QDataStream &stream) { template <class Trait>
quint8 pixel; static Trait readPixel(QDataStream &stream) {
Trait pixel;
stream >> pixel; stream >> pixel;
return pixel; return pixel;
} }
static QRgb updateRed(QRgb oldPixel, quint8 redPixel) { static QRgb updateRed(QRgb oldPixel, quint8 redPixel) {
return qRgba(redPixel, qGreen(oldPixel), qBlue(oldPixel), qAlpha(oldPixel)); 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; 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. // Clear the image.
if (channel_num >= 4) { if (channel_num >= 4) {
// Enable alpha. // Enable alpha.
fmt = QImage::Format_ARGB32; fmt = header.depth == 8 ? QImage::Format_ARGB32
: QImage::Format_RGBA64;
// Ignore the other channels. // Ignore the other channels.
channel_num = 4; channel_num = 4;
} }
img = QImage(header.width, header.height, fmt); img = QImage(header.width, header.height, fmt);
if (img.isNull()) { if (img.isNull()) {
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width, header.height); 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)); img.fill(qRgb(0,0,0));
const quint32 pixel_count = header.height * header.width; 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 // 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(); qWarning() << "Invalid pixel count!" << pixel_count << "bytes available:" << img.sizeInBytes();
return false; return false;
} }
@ -186,6 +192,14 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
updateAlpha 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) { if (compression) {
// Skip row lengths. // Skip row lengths.
int skip_count = header.height * header.channel_count * sizeof(quint16); 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++) { for (unsigned short channel = 0; channel < channel_num; channel++) {
bool success = decodeRLEData(RLEVariant::PackBits, stream, bool success = false;
image_data, pixel_count, if (header.depth == 8) {
&readPixel, updaters[channel]); 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) { if (!success) {
qDebug() << "decodeRLEData on channel" << channel << "failed"; qDebug() << "decodeRLEData on channel" << channel << "failed";
return false; return false;
@ -204,8 +227,15 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
} }
} else { } else {
for (unsigned short channel = 0; channel < channel_num; channel++) { for (unsigned short channel = 0; channel < channel_num; channel++) {
for (unsigned i = 0; i < pixel_count; ++i) { if (header.depth == 8) {
image_data[i] = updaters[channel](image_data[i], readPixel(stream)); 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 // make sure we didn't try to read past the end of the stream
if (stream.status() != QDataStream::Ok) { if (stream.status() != QDataStream::Ok) {

View File

@ -24,6 +24,11 @@ enum class RLEVariant {
* of size 2, 130 of size 3, up to 255 of size 128. * of size 2, 130 of size 3, up to 255 of size 128.
*/ */
PackBits, PackBits,
/**
* Same as PackBits, but treat unpacked data as
* 16-bit integers.
*/
PackBits16,
/** /**
* PIC-style RLE * PIC-style RLE
* *
@ -67,6 +72,8 @@ static inline bool decodeRLEData(RLEVariant variant,
Func2 updateItem) Func2 updateItem)
{ {
unsigned offset = 0; // in dest 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) { while (offset < length) {
unsigned remaining = length - offset; unsigned remaining = length - offset;
quint8 count1; quint8 count1;
@ -85,7 +92,7 @@ static inline bool decodeRLEData(RLEVariant variant,
// 2 to 128 repetitions // 2 to 128 repetitions
length = count1 - 127u; length = count1 - 127u;
} }
} else if (variant == RLEVariant::PackBits) { } else if (variant == RLEVariant::PackBits || variant == RLEVariant::PackBits16) {
if (count1 == 128u) { if (count1 == 128u) {
// Ignore value 128 // Ignore value 128
continue; continue;
@ -102,7 +109,18 @@ static inline bool decodeRLEData(RLEVariant variant,
} }
auto datum = readData(stream); auto datum = readData(stream);
for (unsigned i = offset; i < offset + length; ++i) { 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; offset += length;
} else { } else {
@ -114,7 +132,18 @@ static inline bool decodeRLEData(RLEVariant variant,
} }
for (unsigned i = offset; i < offset + length; ++i) { for (unsigned i = offset; i < offset + length; ++i) {
auto datum = readData(stream); 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; offset += length;
} }

View File

@ -7,56 +7,24 @@
#ifndef FORMAT_ENUM_H #ifndef FORMAT_ENUM_H
#define FORMAT_ENUM_H #define FORMAT_ENUM_H
#include <QMetaEnum>
#include <QImage> #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) QImage::Format formatFromString(const QString &str)
{ {
for (int i = 0; i < qimage_format_enum_names_count; ++i) { const QMetaEnum metaEnum = QMetaEnum::fromType<QImage::Format>();
if (str.compare(QLatin1String(qimage_format_enum_names[i]), Qt::CaseInsensitive) == 0) { const QString enumString = QStringLiteral("Format_") + str;
return (QImage::Format)(i);
} bool ok;
} const int res = metaEnum.keyToValue(enumString.toLatin1().constData(), &ok);
return QImage::Format_Invalid;
return ok ? static_cast<QImage::Format>(res) : QImage::Format_Invalid;
} }
QString formatToString(QImage::Format format) QString formatToString(QImage::Format format)
{ {
int index = int(format); const QMetaEnum metaEnum = QMetaEnum::fromType<QImage::Format>();
if (index > 0 && index < qimage_format_enum_names_count) { return QString::fromLatin1(metaEnum.valueToKey(format)).remove(QStringLiteral("Format_"));
return QLatin1String(qimage_format_enum_names[index]);
}
return QLatin1String("<unknown:") +
QString::number(index) +
QLatin1String(">");
} }
#endif #endif

View File

@ -67,8 +67,8 @@ int main(int argc, char **argv)
QTextStream out(stdout); QTextStream out(stdout);
out << "QImage formats:\n"; out << "QImage formats:\n";
// skip QImage::Format_Invalid // skip QImage::Format_Invalid
for (int i = 1; i < qimage_format_enum_names_count; ++i) { for (int i = 1; i < QImage::NImageFormats; ++i) {
out << " " << qimage_format_enum_names[i] << '\n'; out << " " << formatToString(static_cast<QImage::Format>(i)) << '\n';
} }
return 0; return 0;
} }