Skip to content

Commit

Permalink
ppm/pgm image formats: fix reading 16bit and limited range
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
aavit committed Mar 2, 2017
1 parent 593a707 commit c4c8886
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 14 deletions.
59 changes: 45 additions & 14 deletions src/gui/image/qppmhandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
#include <qvariant.h>
#include <qvector.h>
#include <ctype.h>
#include <qrgba64.h>

QT_BEGIN_NAMESPACE

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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())
Expand All @@ -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
Expand Down Expand Up @@ -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);
Expand All @@ -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;
}
}
Expand Down
76 changes: 76 additions & 0 deletions tests/auto/gui/image/qimagereader/tst_qimagereader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ private slots:
void gifImageCount();
void gifLoopCount();

void ppmMaxval_data();
void ppmMaxval();

void readCorruptImage_data();
void readCorruptImage();
void readCorruptBmp();
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit c4c8886

Please sign in to comment.