ppm/pgm image formats: fix reading 16bit and limited range

The color values of ppm and pgm images can be either 8 or 16 bits.
They can also be scaled to a smaller max value, and they can be
expressed either binary or ascii. For some of these permutations, Qt's
image handler lacked implementation or would decode the wrong color
value. This commit fixes that.

Task-number: QTBUG-18262
Task-number: QTBUG-35990
Change-Id: I7cf11c2366244f3a9b31c1a565a81e2658bc6a51
Reviewed-by: Lars Knoll <lars.knoll@qt.io>
bb10
Eirik Aavitsland 2017-02-23 09:39:07 +01:00
parent 593a707ba3
commit c4c8886a86
2 changed files with 121 additions and 14 deletions

View File

@ -45,6 +45,7 @@
#include <qvariant.h>
#include <qvector.h>
#include <ctype.h>
#include <qrgba64.h>
QT_BEGIN_NAMESPACE
@ -120,6 +121,11 @@ static bool read_pbm_header(QIODevice *device, char& type, int& w, int& h, int&
return true;
}
static inline QRgb scale_pbm_color(quint16 mx, quint16 rv, quint16 gv, quint16 bv)
{
return QRgba64::fromRgba64((rv * 0xffff) / mx, (gv * 0xffff) / mx, (bv * 0xffff) / mx, 0xffff).toArgb32();
}
static bool read_pbm_body(QIODevice *device, char type, int w, int h, int mcc, QImage *outImage)
{
int nbits, y;
@ -148,9 +154,6 @@ static bool read_pbm_body(QIODevice *device, char type, int w, int h, int mcc, Q
}
raw = type >= '4';
int maxc = mcc;
if (maxc > 255)
maxc = 255;
if (outImage->size() != QSize(w, h) || outImage->format() != format) {
*outImage = QImage(w, h, format);
if (outImage->isNull())
@ -175,22 +178,50 @@ static bool read_pbm_body(QIODevice *device, char type, int w, int h, int mcc, Q
b = buf24;
while (p < end) {
if (mcc < 256) {
*p++ = qRgb(b[0],b[1],b[2]);
if (mcc == 255)
*p++ = qRgb(b[0],b[1],b[2]);
else
*p++ = scale_pbm_color(mcc, b[0], b[1], b[2]);
b += 3;
} else {
*p++ = qRgb(((int(b[0]) * 256 + int(b[1]) + 1) * 256) / (mcc + 1) - 1,
((int(b[2]) * 256 + int(b[3]) + 1) * 256) / (mcc + 1) - 1,
((int(b[4]) * 256 + int(b[5]) + 1) * 256) / (mcc + 1) - 1);
quint16 rv = b[0] << 8 | b[1];
quint16 gv = b[2] << 8 | b[3];
quint16 bv = b[4] << 8 | b[5];
if (mcc == 0xffff)
*p++ = QRgba64::fromRgba64(rv, gv, bv, 0xffff).toArgb32();
else
*p++ = scale_pbm_color(mcc, rv, gv, bv);
b += 6;
}
}
}
delete[] buf24;
} else if (nbits == 8 && mcc > 255) { // type 5 16bit
pbm_bpl = 2*w;
uchar *buf16 = new uchar[pbm_bpl];
for (y=0; y<h; y++) {
if (device->read((char *)buf16, pbm_bpl) != pbm_bpl) {
delete[] buf16;
return false;
}
uchar *p = outImage->scanLine(y);
uchar *end = p + w;
uchar *b = buf16;
while (p < end) {
*p++ = (b[0] << 8 | b[1]) * 255 / mcc;
b += 2;
}
}
delete[] buf16;
} else { // type 4,5
for (y=0; y<h; y++) {
if (device->read((char *)outImage->scanLine(y), pbm_bpl)
!= pbm_bpl)
uchar *p = outImage->scanLine(y);
if (device->read((char *)p, pbm_bpl) != pbm_bpl)
return false;
if (nbits == 8 && mcc < 255) {
for (int i = 0; i < pbm_bpl; i++)
p[i] = (p[i] * 255) / mcc;
}
}
}
} else { // read ascii data
@ -227,7 +258,7 @@ static bool read_pbm_body(QIODevice *device, char type, int w, int h, int mcc, Q
} else { // 32 bits
n /= 4;
int r, g, b;
if (mcc == maxc) {
if (mcc == 255) {
while (n--) {
r = read_pbm_int(device);
g = read_pbm_int(device);
@ -237,10 +268,10 @@ static bool read_pbm_body(QIODevice *device, char type, int w, int h, int mcc, Q
}
} else {
while (n--) {
r = read_pbm_int(device) * maxc / mcc;
g = read_pbm_int(device) * maxc / mcc;
b = read_pbm_int(device) * maxc / mcc;
*((QRgb*)p) = qRgb(r, g, b);
r = read_pbm_int(device);
g = read_pbm_int(device);
b = read_pbm_int(device);
*((QRgb*)p) = scale_pbm_color(mcc, r, g, b);
p += 4;
}
}

View File

@ -133,6 +133,9 @@ private slots:
void gifImageCount();
void gifLoopCount();
void ppmMaxval_data();
void ppmMaxval();
void readCorruptImage_data();
void readCorruptImage();
void readCorruptBmp();
@ -956,6 +959,79 @@ void tst_QImageReader::gifLoopCount()
}
}
void tst_QImageReader::ppmMaxval_data()
{
QTest::addColumn<bool>("hasColor");
QTest::addColumn<QByteArray>("bytes");
QTest::newRow("PGM plain 8bit full") << false << QByteArray("P2 3 1 255 255 0 127\n");
QTest::newRow("PGM plain 8bit lim.") << false << QByteArray("P2 3 1 50 50 0 25\n");
QTest::newRow("PGM plain 16bit full") << false << QByteArray("P2 3 1 65535 65535 0 32767\n");
QTest::newRow("PGM plain 16bit lim.") << false << QByteArray("P2 3 1 5000 5000 0 2500\n");
QTest::newRow("PGM raw 8bit full") << false << QByteArray("P5 3 1 255 \xff\x00\x7f", 13 + 3);
QTest::newRow("PGM raw 8bit lim.") << false << QByteArray("P5 3 1 50 \x32\x00\x19", 13 + 3);
QTest::newRow("PGM raw 16bit full") << false << QByteArray("P5 3 1 65535 \xff\xff\x00\x00\x7f\xff", 13 + 3 * 2);
QTest::newRow("PGM raw 16bit lim.") << false << QByteArray("P5 3 1 5000 \x13\x88\x00\x00\x09\xc4", 13 + 3 * 2);
QTest::newRow("PPM plain 8bit full") << true << QByteArray("P3 3 2 255 "
"255 255 255 0 0 0 127 127 127 "
"255 0 0 0 255 0 0 0 255\n");
QTest::newRow("PPM plain 8bit lim.") << true << QByteArray("P3 3 2 50 "
" 50 50 50 0 0 0 25 25 25 "
" 50 0 0 0 50 0 0 0 50\n");
QTest::newRow("PPM plain 16bit full") << true << QByteArray("P3 3 2 65535 "
"65535 65535 65535 0 0 0 32767 32767 32767 "
"65535 0 0 0 65535 0 0 0 65535\n");
QTest::newRow("PPM plain 16bit lim.") << true << QByteArray("P3 3 2 5000 "
" 5000 5000 5000 0 0 0 2500 2500 2500 "
" 5000 0 0 0 5000 0 0 0 5000\n");
QTest::newRow("PPM raw 8bit full") << true << QByteArray("P6 3 2 255 "
"\xff\xff\xff\x00\x00\x00\x7f\x7f\x7f"
"\xff\x00\x00\x00\xff\x00\x00\x00\xff", 13 + 6 * 3);
QTest::newRow("PPM raw 8bit lim.") << true << QByteArray("P6 3 2 50 "
"\x32\x32\x32\x00\x00\x00\x19\x19\x19"
"\x32\x00\x00\x00\x32\x00\x00\x00\x32", 13 + 6 * 3);
QTest::newRow("PPM raw 16bit full") << true << QByteArray("P6 3 2 65535 "
"\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x7f\xff\x7f\xff\x7f\xff"
"\xff\xff\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\x00\x00\x00\x00\xff\xff", 13 + 6 * 3 * 2);
QTest::newRow("PPM raw 16bit lim.") << true << QByteArray("P6 3 2 5000 "
"\x13\x88\x13\x88\x13\x88\x00\x00\x00\x00\x00\x00\x09\xc4\x09\xc4\x09\xc4"
"\x13\x88\x00\x00\x00\x00\x00\x00\x13\x88\x00\x00\x00\x00\x00\x00\x13\x88", 13 + 6 * 3 * 2);
}
void tst_QImageReader::ppmMaxval()
{
SKIP_IF_UNSUPPORTED("ppm");
QFETCH(bool, hasColor);
QFETCH(QByteArray, bytes);
QImage img;
img.loadFromData(bytes);
QVERIFY(!img.isNull());
QCOMPARE(img.width(), 3);
QCOMPARE(img.height(), hasColor ? 2 : 1);
QCOMPARE(img.pixel(0,0), qRgb(0xff, 0xff, 0xff));
QCOMPARE(img.pixel(1,0), qRgb(0, 0, 0));
QRgb gray = img.pixel(2,0);
QVERIFY(qIsGray(gray));
QVERIFY(qRed(gray) > 0x70 && qRed(gray) < 0x90 );
if (hasColor) {
QCOMPARE(img.pixel(0,1), qRgb(0xff, 0, 0));
QCOMPARE(img.pixel(1,1), qRgb(0, 0xff, 0));
QCOMPARE(img.pixel(2,1), qRgb(0, 0, 0xff));
}
}
class Server : public QObject
{
Q_OBJECT