Compare commits
12 Commits
v5.103.0-r
...
v5.107.0
Author | SHA1 | Date | |
---|---|---|---|
55227815d5 | |||
64d51ed610 | |||
2ca57c9c59 | |||
f7fd14d418 | |||
c9aa1ff629 | |||
91d3bd5227 | |||
bb66367bc8 | |||
14770318a3 | |||
9b1fafe29b | |||
fa673b5df8 | |||
e96b43aef5 | |||
64f3303ef0 |
@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.16)
|
||||
project(KImageFormats)
|
||||
|
||||
include(FeatureSummary)
|
||||
find_package(ECM 5.103.0 NO_MODULE)
|
||||
find_package(ECM 5.107.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)
|
||||
|
||||
|
BIN
autotests/read/pcx/ccbug_463951.pcx
Normal file
BIN
autotests/read/pcx/ccbug_463951.png
Normal file
After Width: | Height: | Size: 8.5 KiB |
BIN
autotests/read/pcx/mono.pcx
Normal file
BIN
autotests/read/pcx/mono.png
Normal file
After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 983 B After Width: | Height: | Size: 4.0 KiB |
Before Width: | Height: | Size: 94 KiB |
BIN
autotests/read/psd/birthday.tif
Normal file
Before Width: | Height: | Size: 191 KiB After Width: | Height: | Size: 189 KiB |
Before Width: | Height: | Size: 115 KiB After Width: | Height: | Size: 114 KiB |
@ -159,17 +159,22 @@ 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)) {
|
||||
if (!fi.suffix().compare("png", Qt::CaseInsensitive) || !fi.suffix().compare("tif", Qt::CaseInsensitive)) {
|
||||
continue;
|
||||
}
|
||||
int suffixPos = fi.filePath().count() - suffix.count();
|
||||
QString inputfile = fi.filePath();
|
||||
QString expfile = fi.filePath().replace(suffixPos, suffix.count(), QStringLiteral("png"));
|
||||
QString fmt = QStringLiteral("png");
|
||||
QString expfile = fi.filePath().replace(suffixPos, suffix.count(), fmt);
|
||||
if (!QFile::exists(expfile)) { // try with tiff
|
||||
fmt = QStringLiteral("tif");
|
||||
expfile = fi.filePath().replace(suffixPos, suffix.count(), fmt);
|
||||
}
|
||||
QString expfilename = QFileInfo(expfile).fileName();
|
||||
|
||||
std::unique_ptr<QIODevice> inputDevice(seq ? new SequentialFile(inputfile) : new QFile(inputfile));
|
||||
QImageReader inputReader(inputDevice.get(), format);
|
||||
QImageReader expReader(expfile, "png");
|
||||
QImageReader expReader(expfile, fmt.toLatin1());
|
||||
|
||||
QImage inputImage;
|
||||
QImage expImage;
|
||||
|
@ -230,7 +230,7 @@ PCXHEADER::PCXHEADER()
|
||||
s >> *this;
|
||||
}
|
||||
|
||||
static void readLine(QDataStream &s, QByteArray &buf, const PCXHEADER &header)
|
||||
static bool readLine(QDataStream &s, QByteArray &buf, const PCXHEADER &header)
|
||||
{
|
||||
quint32 i = 0;
|
||||
quint32 size = buf.size();
|
||||
@ -257,9 +257,11 @@ static void readLine(QDataStream &s, QByteArray &buf, const PCXHEADER &header)
|
||||
buf[i++] = byte;
|
||||
}
|
||||
}
|
||||
|
||||
return (s.status() == QDataStream::Ok);
|
||||
}
|
||||
|
||||
static void readImage1(QImage &img, QDataStream &s, const PCXHEADER &header)
|
||||
static bool readImage1(QImage &img, QDataStream &s, const PCXHEADER &header)
|
||||
{
|
||||
QByteArray buf(header.BytesPerLine, 0);
|
||||
|
||||
@ -268,16 +270,18 @@ static void readImage1(QImage &img, QDataStream &s, const PCXHEADER &header)
|
||||
|
||||
if (img.isNull()) {
|
||||
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int y = 0; y < header.height(); ++y) {
|
||||
if (s.atEnd()) {
|
||||
img = QImage();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!readLine(s, buf, header)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
readLine(s, buf, header);
|
||||
uchar *p = img.scanLine(y);
|
||||
unsigned int bpl = qMin((quint16)((header.width() + 7) / 8), header.BytesPerLine);
|
||||
for (unsigned int x = 0; x < bpl; ++x) {
|
||||
@ -288,9 +292,11 @@ static void readImage1(QImage &img, QDataStream &s, const PCXHEADER &header)
|
||||
// Set the color palette
|
||||
img.setColor(0, qRgb(0, 0, 0));
|
||||
img.setColor(1, qRgb(255, 255, 255));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void readImage4(QImage &img, QDataStream &s, const PCXHEADER &header)
|
||||
static bool readImage4(QImage &img, QDataStream &s, const PCXHEADER &header)
|
||||
{
|
||||
QByteArray buf(header.BytesPerLine * 4, 0);
|
||||
QByteArray pixbuf(header.width(), 0);
|
||||
@ -299,17 +305,18 @@ static void readImage4(QImage &img, QDataStream &s, const PCXHEADER &header)
|
||||
img.setColorCount(16);
|
||||
if (img.isNull()) {
|
||||
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int y = 0; y < header.height(); ++y) {
|
||||
if (s.atEnd()) {
|
||||
img = QImage();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
pixbuf.fill(0);
|
||||
readLine(s, buf, header);
|
||||
if (!readLine(s, buf, header)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
quint32 offset = i * header.BytesPerLine;
|
||||
@ -333,9 +340,11 @@ static void readImage4(QImage &img, QDataStream &s, const PCXHEADER &header)
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
img.setColor(i, header.ColorMap.color(i));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void readImage8(QImage &img, QDataStream &s, const PCXHEADER &header)
|
||||
static bool readImage8(QImage &img, QDataStream &s, const PCXHEADER &header)
|
||||
{
|
||||
QByteArray buf(header.BytesPerLine, 0);
|
||||
|
||||
@ -344,21 +353,21 @@ static void readImage8(QImage &img, QDataStream &s, const PCXHEADER &header)
|
||||
|
||||
if (img.isNull()) {
|
||||
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int y = 0; y < header.height(); ++y) {
|
||||
if (s.atEnd()) {
|
||||
img = QImage();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
readLine(s, buf, header);
|
||||
if (!readLine(s, buf, header)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uchar *p = img.scanLine(y);
|
||||
|
||||
if (!p) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned int bpl = qMin(header.BytesPerLine, (quint16)header.width());
|
||||
@ -367,10 +376,21 @@ static void readImage8(QImage &img, QDataStream &s, const PCXHEADER &header)
|
||||
}
|
||||
}
|
||||
|
||||
quint8 flag;
|
||||
// by specification, the extended palette starts at file.size() - 769
|
||||
quint8 flag = 0;
|
||||
if (auto device = s.device()) {
|
||||
if (device->isSequential()) {
|
||||
while (flag != 12 && s.status() == QDataStream::Ok) {
|
||||
s >> flag;
|
||||
// qDebug() << "Palette Flag: " << flag;
|
||||
}
|
||||
}
|
||||
else {
|
||||
device->seek(device->size() - 769);
|
||||
s >> flag;
|
||||
}
|
||||
}
|
||||
|
||||
// qDebug() << "Palette Flag: " << flag;
|
||||
if (flag == 12 && (header.Version == 5 || header.Version == 2)) {
|
||||
// Read the palette
|
||||
quint8 r;
|
||||
@ -381,9 +401,11 @@ static void readImage8(QImage &img, QDataStream &s, const PCXHEADER &header)
|
||||
img.setColor(i, qRgb(r, g, b));
|
||||
}
|
||||
}
|
||||
|
||||
return (s.status() == QDataStream::Ok);
|
||||
}
|
||||
|
||||
static void readImage24(QImage &img, QDataStream &s, const PCXHEADER &header)
|
||||
static bool readImage24(QImage &img, QDataStream &s, const PCXHEADER &header)
|
||||
{
|
||||
QByteArray r_buf(header.BytesPerLine, 0);
|
||||
QByteArray g_buf(header.BytesPerLine, 0);
|
||||
@ -393,27 +415,34 @@ static void readImage24(QImage &img, QDataStream &s, const PCXHEADER &header)
|
||||
|
||||
if (img.isNull()) {
|
||||
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int y = 0; y < header.height(); ++y) {
|
||||
if (s.atEnd()) {
|
||||
img = QImage();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
readLine(s, r_buf, header);
|
||||
readLine(s, g_buf, header);
|
||||
readLine(s, b_buf, header);
|
||||
if (!readLine(s, r_buf, header)) {
|
||||
return false;
|
||||
}
|
||||
if (!readLine(s, g_buf, header)) {
|
||||
return false;
|
||||
}
|
||||
if (!readLine(s, b_buf, header)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint *p = (uint *)img.scanLine(y);
|
||||
for (int x = 0; x < header.width(); ++x) {
|
||||
p[x] = qRgb(r_buf[x], g_buf[x], b_buf[x]);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void writeLine(QDataStream &s, QByteArray &buf)
|
||||
static bool writeLine(QDataStream &s, QByteArray &buf)
|
||||
{
|
||||
quint32 i = 0;
|
||||
quint32 size = buf.size();
|
||||
@ -439,15 +468,26 @@ static void writeLine(QDataStream &s, QByteArray &buf)
|
||||
|
||||
s << data;
|
||||
}
|
||||
return (s.status() == QDataStream::Ok);
|
||||
}
|
||||
|
||||
static void writeImage1(QImage &img, QDataStream &s, PCXHEADER &header)
|
||||
static bool writeImage1(QImage &img, QDataStream &s, PCXHEADER &header)
|
||||
{
|
||||
if (img.format() != QImage::Format_Mono) {
|
||||
img = img.convertToFormat(QImage::Format_Mono);
|
||||
}
|
||||
if (img.isNull() || img.colorCount() < 1) {
|
||||
return false;
|
||||
}
|
||||
auto rgb = img.color(0);
|
||||
auto minIsBlack = (qRed(rgb) + qGreen(rgb) + qBlue(rgb)) / 3 < 127;
|
||||
|
||||
header.Bpp = 1;
|
||||
header.NPlanes = 1;
|
||||
header.BytesPerLine = img.bytesPerLine();
|
||||
if (header.BytesPerLine == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
s << header;
|
||||
|
||||
@ -458,18 +498,24 @@ static void writeImage1(QImage &img, QDataStream &s, PCXHEADER &header)
|
||||
|
||||
// Invert as QImage uses reverse palette for monochrome images?
|
||||
for (int i = 0; i < header.BytesPerLine; ++i) {
|
||||
buf[i] = ~p[i];
|
||||
buf[i] = minIsBlack ? p[i] : ~p[i];
|
||||
}
|
||||
|
||||
writeLine(s, buf);
|
||||
if (!writeLine(s, buf)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void writeImage4(QImage &img, QDataStream &s, PCXHEADER &header)
|
||||
static bool writeImage4(QImage &img, QDataStream &s, PCXHEADER &header)
|
||||
{
|
||||
header.Bpp = 1;
|
||||
header.NPlanes = 4;
|
||||
header.BytesPerLine = header.width() / 8;
|
||||
if (header.BytesPerLine == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
header.ColorMap.setColor(i, img.color(i));
|
||||
@ -499,16 +545,22 @@ static void writeImage4(QImage &img, QDataStream &s, PCXHEADER &header)
|
||||
}
|
||||
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
writeLine(s, buf[i]);
|
||||
if (!writeLine(s, buf[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void writeImage8(QImage &img, QDataStream &s, PCXHEADER &header)
|
||||
static bool writeImage8(QImage &img, QDataStream &s, PCXHEADER &header)
|
||||
{
|
||||
header.Bpp = 8;
|
||||
header.NPlanes = 1;
|
||||
header.BytesPerLine = img.bytesPerLine();
|
||||
if (header.BytesPerLine == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
s << header;
|
||||
|
||||
@ -521,7 +573,9 @@ static void writeImage8(QImage &img, QDataStream &s, PCXHEADER &header)
|
||||
buf[i] = p[i];
|
||||
}
|
||||
|
||||
writeLine(s, buf);
|
||||
if (!writeLine(s, buf)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Write palette flag
|
||||
@ -532,13 +586,25 @@ static void writeImage8(QImage &img, QDataStream &s, PCXHEADER &header)
|
||||
for (int i = 0; i < 256; ++i) {
|
||||
s << RGB::from(img.color(i));
|
||||
}
|
||||
|
||||
return (s.status() == QDataStream::Ok);
|
||||
}
|
||||
|
||||
static void writeImage24(QImage &img, QDataStream &s, PCXHEADER &header)
|
||||
static bool writeImage24(QImage &img, QDataStream &s, PCXHEADER &header)
|
||||
{
|
||||
header.Bpp = 8;
|
||||
header.NPlanes = 3;
|
||||
header.BytesPerLine = header.width();
|
||||
if (header.BytesPerLine == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (img.format() != QImage::Format_ARGB32 && img.format() != QImage::Format_RGB32) {
|
||||
img = img.convertToFormat(QImage::Format_RGB32);
|
||||
}
|
||||
if (img.isNull()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
s << header;
|
||||
|
||||
@ -547,7 +613,7 @@ static void writeImage24(QImage &img, QDataStream &s, PCXHEADER &header)
|
||||
QByteArray b_buf(header.width(), 0);
|
||||
|
||||
for (int y = 0; y < header.height(); ++y) {
|
||||
uint *p = (uint *)img.scanLine(y);
|
||||
auto p = (QRgb*)img.scanLine(y);
|
||||
|
||||
for (int x = 0; x < header.width(); ++x) {
|
||||
QRgb rgb = *p++;
|
||||
@ -556,10 +622,18 @@ static void writeImage24(QImage &img, QDataStream &s, PCXHEADER &header)
|
||||
b_buf[x] = qBlue(rgb);
|
||||
}
|
||||
|
||||
writeLine(s, r_buf);
|
||||
writeLine(s, g_buf);
|
||||
writeLine(s, b_buf);
|
||||
if (!writeLine(s, r_buf)) {
|
||||
return false;
|
||||
}
|
||||
if (!writeLine(s, g_buf)) {
|
||||
return false;
|
||||
}
|
||||
if (!writeLine(s, b_buf)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
PCXHandler::PCXHandler()
|
||||
@ -588,46 +662,30 @@ bool PCXHandler::read(QImage *outImage)
|
||||
|
||||
s >> header;
|
||||
|
||||
if (header.Manufacturer != 10 || s.atEnd()) {
|
||||
if (header.Manufacturer != 10 || header.BytesPerLine == 0 || s.atEnd()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// int w = header.width();
|
||||
// int h = header.height();
|
||||
|
||||
// qDebug() << "Manufacturer: " << header.Manufacturer;
|
||||
// qDebug() << "Version: " << header.Version;
|
||||
// qDebug() << "Encoding: " << header.Encoding;
|
||||
// qDebug() << "Bpp: " << header.Bpp;
|
||||
// qDebug() << "Width: " << w;
|
||||
// qDebug() << "Height: " << h;
|
||||
// qDebug() << "Window: " << header.XMin << "," << header.XMax << ","
|
||||
// << header.YMin << "," << header.YMax << endl;
|
||||
// qDebug() << "BytesPerLine: " << header.BytesPerLine;
|
||||
// qDebug() << "NPlanes: " << header.NPlanes;
|
||||
|
||||
auto ok = false;
|
||||
QImage img;
|
||||
|
||||
if (header.Bpp == 1 && header.NPlanes == 1) {
|
||||
readImage1(img, s, header);
|
||||
ok = readImage1(img, s, header);
|
||||
} else if (header.Bpp == 1 && header.NPlanes == 4) {
|
||||
readImage4(img, s, header);
|
||||
ok = readImage4(img, s, header);
|
||||
} else if (header.Bpp == 8 && header.NPlanes == 1) {
|
||||
readImage8(img, s, header);
|
||||
ok = readImage8(img, s, header);
|
||||
} else if (header.Bpp == 8 && header.NPlanes == 3) {
|
||||
readImage24(img, s, header);
|
||||
ok = readImage24(img, s, header);
|
||||
}
|
||||
|
||||
// qDebug() << "Image Bytes: " << img.numBytes();
|
||||
// qDebug() << "Image Bytes Per Line: " << img.bytesPerLine();
|
||||
// qDebug() << "Image Depth: " << img.depth();
|
||||
if (img.isNull() || !ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!img.isNull()) {
|
||||
img.setDotsPerMeterX(qRound(header.HDpi / 25.4 * 1000));
|
||||
img.setDotsPerMeterY(qRound(header.YDpi / 25.4 * 1000));
|
||||
*outImage = img;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool PCXHandler::write(const QImage &image)
|
||||
@ -644,12 +702,6 @@ bool PCXHandler::write(const QImage &image)
|
||||
return false;
|
||||
}
|
||||
|
||||
// qDebug() << "Width: " << w;
|
||||
// qDebug() << "Height: " << h;
|
||||
// qDebug() << "Depth: " << img.depth();
|
||||
// qDebug() << "BytesPerLine: " << img.bytesPerLine();
|
||||
// qDebug() << "Color Count: " << img.colorCount();
|
||||
|
||||
PCXHEADER header;
|
||||
|
||||
header.Manufacturer = 10;
|
||||
@ -659,22 +711,23 @@ bool PCXHandler::write(const QImage &image)
|
||||
header.YMin = 0;
|
||||
header.XMax = w - 1;
|
||||
header.YMax = h - 1;
|
||||
header.HDpi = 300;
|
||||
header.YDpi = 300;
|
||||
header.HDpi = qRound(image.dotsPerMeterX() * 25.4 / 1000);
|
||||
header.YDpi = qRound(image.dotsPerMeterY() * 25.4 / 1000);
|
||||
header.Reserved = 0;
|
||||
header.PaletteInfo = 1;
|
||||
|
||||
auto ok = false;
|
||||
if (img.depth() == 1) {
|
||||
writeImage1(img, s, header);
|
||||
ok = writeImage1(img, s, header);
|
||||
} else if (img.depth() == 8 && img.colorCount() <= 16) {
|
||||
writeImage4(img, s, header);
|
||||
ok = writeImage4(img, s, header);
|
||||
} else if (img.depth() == 8) {
|
||||
writeImage8(img, s, header);
|
||||
} else if (img.depth() == 32) {
|
||||
writeImage24(img, s, header);
|
||||
ok = writeImage8(img, s, header);
|
||||
} else if (img.depth() >= 24) {
|
||||
ok = writeImage24(img, s, header);
|
||||
}
|
||||
|
||||
return true;
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool PCXHandler::canRead(QIODevice *device)
|
||||
|
@ -9,8 +9,8 @@
|
||||
*/
|
||||
|
||||
/*
|
||||
* This code is based on Thacher Ulrich PSD loading code released
|
||||
* into the public domain. See: http://tulrich.com/geekstuff/
|
||||
* The early version of this code was based on Thacher Ulrich PSD loading code
|
||||
* released into the public domain. See: http://tulrich.com/geekstuff/
|
||||
*/
|
||||
|
||||
/*
|
||||
@ -733,9 +733,9 @@ static QImage::Format imageFormat(const PSDHeader &header, bool alpha)
|
||||
switch(header.color_mode) {
|
||||
case CM_RGB:
|
||||
if (header.depth == 16 || header.depth == 32)
|
||||
format = header.channel_count < 4 || !alpha ? QImage::Format_RGBX64 : QImage::Format_RGBA64;
|
||||
format = header.channel_count < 4 || !alpha ? QImage::Format_RGBX64 : QImage::Format_RGBA64_Premultiplied;
|
||||
else
|
||||
format = header.channel_count < 4 || !alpha ? QImage::Format_RGB888 : QImage::Format_RGBA8888;
|
||||
format = header.channel_count < 4 || !alpha ? QImage::Format_RGB888 : QImage::Format_RGBA8888_Premultiplied;
|
||||
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
|
||||
@ -830,6 +830,43 @@ inline void planarToChunchyFloat(uchar *target, const char *source, qint32 width
|
||||
}
|
||||
}
|
||||
|
||||
enum class PremulConversion {
|
||||
PS2P, // Photoshop premul to qimage premul (required by RGB)
|
||||
PS2A, // Photoshop premul to unassociated alpha (required by RGB, CMYK and L* components of LAB)
|
||||
PSLab2A // Photoshop premul to unassociated alpha (required by a* and b* components of LAB)
|
||||
};
|
||||
|
||||
template<class T>
|
||||
inline void premulConversion(char *stride, qint32 width, qint32 ac, qint32 cn, const PremulConversion &conv)
|
||||
{
|
||||
auto s = reinterpret_cast<T *>(stride);
|
||||
auto max = qint64(std::numeric_limits<T>::max());
|
||||
|
||||
for (qint32 c = 0; c < ac; ++c) {
|
||||
if (conv == PremulConversion::PS2P) {
|
||||
for (qint32 x = 0; x < width; ++x) {
|
||||
auto xcn = x * cn;
|
||||
auto alpha = *(s + xcn + ac);
|
||||
*(s + xcn + c) = *(s + xcn + c) + alpha - max;
|
||||
}
|
||||
} else if (conv == PremulConversion::PS2A || (conv == PremulConversion::PSLab2A && c == 0)) {
|
||||
for (qint32 x = 0; x < width; ++x) {
|
||||
auto xcn = x * cn;
|
||||
auto alpha = *(s + xcn + ac);
|
||||
if (alpha > 0)
|
||||
*(s + xcn + c) = ((*(s + xcn + c) + alpha - max) * max + alpha / 2) / alpha;
|
||||
}
|
||||
} else if (conv == PremulConversion::PSLab2A) {
|
||||
for (qint32 x = 0; x < width; ++x) {
|
||||
auto xcn = x * cn;
|
||||
auto alpha = *(s + xcn + ac);
|
||||
if (alpha > 0)
|
||||
*(s + xcn + c) = ((*(s + xcn + c) + (alpha - max + 1) / 2) * max + alpha / 2) / alpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline void monoInvert(uchar *target, const char* source, qint32 bytes)
|
||||
{
|
||||
auto s = reinterpret_cast<const quint8*>(source);
|
||||
@ -839,6 +876,18 @@ inline void monoInvert(uchar *target, const char* source, qint32 bytes)
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline void rawChannelsCopy(uchar *target, qint32 targetChannels, const char *source, qint32 sourceChannels, qint32 width)
|
||||
{
|
||||
auto s = reinterpret_cast<const T *>(source);
|
||||
auto t = reinterpret_cast<T *>(target);
|
||||
for (qint32 c = 0, cs = std::min(targetChannels, sourceChannels); c < cs; ++c) {
|
||||
for (qint32 x = 0; x < width; ++x) {
|
||||
t[x * targetChannels + c] = s[x * sourceChannels + c];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline void cmykToRgb(uchar *target, qint32 targetChannels, const char *source, qint32 sourceChannels, qint32 width, bool alpha = false)
|
||||
{
|
||||
@ -1060,7 +1109,15 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
|
||||
QByteArray rawStride;
|
||||
rawStride.resize(raw_count);
|
||||
|
||||
if (header.color_mode == CM_CMYK || header.color_mode == CM_LABCOLOR || header.color_mode == CM_MULTICHANNEL) {
|
||||
// clang-format off
|
||||
// checks the need of color conversion (that requires random access to the image)
|
||||
auto randomAccess = (header.color_mode == CM_CMYK) ||
|
||||
(header.color_mode == CM_LABCOLOR) ||
|
||||
(header.color_mode == CM_MULTICHANNEL) ||
|
||||
(header.color_mode != CM_INDEXED && img.hasAlphaChannel());
|
||||
// clang-format on
|
||||
|
||||
if (randomAccess) {
|
||||
// In order to make a colorspace transformation, we need all channels of a scanline
|
||||
QByteArray psdScanline;
|
||||
psdScanline.resize(qsizetype(header.width * std::min(header.depth, quint16(16)) * header.channel_count + 7) / 8);
|
||||
@ -1080,31 +1137,56 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
|
||||
auto scanLine = reinterpret_cast<unsigned char*>(psdScanline.data());
|
||||
if (header.depth == 8) {
|
||||
planarToChunchy<quint8>(scanLine, rawStride.data(), header.width, c, header.channel_count);
|
||||
}
|
||||
else if (header.depth == 16) {
|
||||
} else if (header.depth == 16) {
|
||||
planarToChunchy<quint16>(scanLine, rawStride.data(), header.width, c, header.channel_count);
|
||||
}
|
||||
else if (header.depth == 32) { // Not currently used
|
||||
} else if (header.depth == 32) {
|
||||
planarToChunchyFloat<quint32>(scanLine, rawStride.data(), header.width, c, header.channel_count);
|
||||
}
|
||||
}
|
||||
|
||||
// Convert premultiplied data to unassociated data
|
||||
if (img.hasAlphaChannel()) {
|
||||
if (header.color_mode == CM_CMYK) {
|
||||
if (header.depth == 8)
|
||||
premulConversion<quint8>(psdScanline.data(), header.width, 4, header.channel_count, PremulConversion::PS2A);
|
||||
else if (header.depth == 16)
|
||||
premulConversion<quint16>(psdScanline.data(), header.width, 4, header.channel_count, PremulConversion::PS2A);
|
||||
}
|
||||
if (header.color_mode == CM_LABCOLOR) {
|
||||
if (header.depth == 8)
|
||||
premulConversion<quint8>(psdScanline.data(), header.width, 3, header.channel_count, PremulConversion::PSLab2A);
|
||||
else if (header.depth == 16)
|
||||
premulConversion<quint16>(psdScanline.data(), header.width, 3, header.channel_count, PremulConversion::PSLab2A);
|
||||
}
|
||||
if (header.color_mode == CM_RGB) {
|
||||
if (header.depth == 8)
|
||||
premulConversion<quint8>(psdScanline.data(), header.width, 3, header.channel_count, PremulConversion::PS2P);
|
||||
else if (header.depth == 16 || header.depth == 32)
|
||||
premulConversion<quint16>(psdScanline.data(), header.width, 3, header.channel_count, PremulConversion::PS2P);
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
else if (header.depth == 16)
|
||||
cmykToRgb<quint16>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha);
|
||||
}
|
||||
if (header.color_mode == CM_LABCOLOR) {
|
||||
if (header.depth == 8)
|
||||
labToRgb<quint8>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha);
|
||||
else
|
||||
else if (header.depth == 16)
|
||||
labToRgb<quint16>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha);
|
||||
}
|
||||
if (header.color_mode == CM_RGB) {
|
||||
if (header.depth == 8)
|
||||
rawChannelsCopy<quint8>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width);
|
||||
else if (header.depth == 16 || header.depth == 32)
|
||||
rawChannelsCopy<quint16>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width);
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// Linear read (no position jumps): optimized code usable only for the colorspaces supported by QImage
|
||||
for (qint32 c = 0; c < channel_num; ++c) {
|
||||
for (qint32 y = 0, h = header.height; y < h; ++y) {
|
||||
@ -1117,14 +1199,11 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
|
||||
auto scanLine = img.scanLine(y);
|
||||
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
|
||||
} 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 == 16) { // 16-bits integer images: Grayscale, RGB/RGBA
|
||||
} 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 == 32) { // 32-bits float images: Grayscale, RGB/RGBA (coverted to equivalent integer 16-bits)
|
||||
} else if (header.depth == 32) { // 32-bits float images: Grayscale, RGB/RGBA (coverted to equivalent integer 16-bits)
|
||||
planarToChunchyFloat<quint32>(scanLine, rawStride.data(), header.width, c, imgChannels);
|
||||
}
|
||||
}
|
||||
@ -1267,6 +1346,9 @@ bool PSDHandler::canRead(QIODevice *device)
|
||||
if (header.color_mode == CM_CMYK || header.color_mode == CM_LABCOLOR || header.color_mode == CM_MULTICHANNEL) {
|
||||
return false;
|
||||
}
|
||||
if (header.color_mode == CM_RGB && header.channel_count > 3) {
|
||||
return false; // supposing extra channel as alpha
|
||||
}
|
||||
}
|
||||
|
||||
return IsSupported(header);
|
||||
|
@ -672,11 +672,16 @@ bool SGIImage::writeImage(const QImage &image)
|
||||
_dim = 3, _zsize = 3;
|
||||
}
|
||||
|
||||
if (img.format() == QImage::Format_ARGB32) {
|
||||
auto hasAlpha = img.hasAlphaChannel();
|
||||
if (hasAlpha) {
|
||||
_dim = 3, _zsize++;
|
||||
}
|
||||
|
||||
if (hasAlpha && img.format() != QImage::Format_ARGB32) {
|
||||
img = img.convertToFormat(QImage::Format_ARGB32);
|
||||
} else if (!hasAlpha && img.format() != QImage::Format_RGB32) {
|
||||
img = img.convertToFormat(QImage::Format_RGB32);
|
||||
}
|
||||
if (img.isNull()) {
|
||||
// qDebug() << "can't convert image to depth 32";
|
||||
return false;
|
||||
@ -685,7 +690,7 @@ bool SGIImage::writeImage(const QImage &image)
|
||||
const int w = img.width();
|
||||
const int h = img.height();
|
||||
|
||||
if (w > 65536 || h > 65536) {
|
||||
if (w > 65535 || h > 65535) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -712,12 +717,6 @@ bool SGIImage::writeImage(const QImage &image)
|
||||
rle_size += _rlevector[i]->size();
|
||||
}
|
||||
|
||||
// qDebug() << "minimum intensity: " << _pixmin;
|
||||
// qDebug() << "maximum intensity: " << _pixmax;
|
||||
// qDebug() << "saved scanlines: " << _numrows - _rlemap.size();
|
||||
// qDebug() << "total savings: " << (verbatim_size - rle_size) << " bytes";
|
||||
// qDebug() << "compression: " << (rle_size * 100.0 / verbatim_size) << '%';
|
||||
|
||||
if (verbatim_size <= rle_size) {
|
||||
writeVerbatim(img);
|
||||
} else {
|
||||
|
@ -428,8 +428,17 @@ bool TGAHandler::write(const QImage &image)
|
||||
QDataStream s(device());
|
||||
s.setByteOrder(QDataStream::LittleEndian);
|
||||
|
||||
const QImage &img = image;
|
||||
const bool hasAlpha = (img.format() == QImage::Format_ARGB32);
|
||||
QImage img(image);
|
||||
const bool hasAlpha = img.hasAlphaChannel();
|
||||
if (hasAlpha && img.format() != QImage::Format_ARGB32) {
|
||||
img = img.convertToFormat(QImage::Format_ARGB32);
|
||||
} else if (!hasAlpha && img.format() != QImage::Format_RGB32) {
|
||||
img = img.convertToFormat(QImage::Format_RGB32);
|
||||
}
|
||||
if (img.isNull()) {
|
||||
qDebug() << "TGAHandler::write: image conversion to 32 bits failed!";
|
||||
return false;
|
||||
}
|
||||
static constexpr quint8 originTopLeft = TGA_ORIGIN_UPPER + TGA_ORIGIN_LEFT; // 0x20
|
||||
static constexpr quint8 alphaChannel8Bits = 0x08;
|
||||
|
||||
@ -444,8 +453,9 @@ bool TGAHandler::write(const QImage &image)
|
||||
s << quint8(hasAlpha ? originTopLeft + alphaChannel8Bits : originTopLeft); // top left image (0x20) + 8 bit alpha (0x8)
|
||||
|
||||
for (int y = 0; y < img.height(); y++) {
|
||||
auto ptr = reinterpret_cast<QRgb *>(img.scanLine(y));
|
||||
for (int x = 0; x < img.width(); x++) {
|
||||
const QRgb color = img.pixel(x, y);
|
||||
auto color = *(ptr + x);
|
||||
s << quint8(qBlue(color));
|
||||
s << quint8(qGreen(color));
|
||||
s << quint8(qRed(color));
|
||||
|