| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QtGui module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "private/qppmhandler_p.h" |
| |
| #ifndef QT_NO_IMAGEFORMAT_PPM |
| |
| #include <qimage.h> |
| #include <qvariant.h> |
| #include <qvector.h> |
| #include <ctype.h> |
| #include <qrgba64.h> |
| |
| QT_BEGIN_NAMESPACE |
| |
| /***************************************************************************** |
| PBM/PGM/PPM (ASCII and RAW) image read/write functions |
| *****************************************************************************/ |
| |
| static void discard_pbm_line(QIODevice *d) |
| { |
| const int buflen = 100; |
| char buf[buflen]; |
| int res = 0; |
| do { |
| res = d->readLine(buf, buflen); |
| } while (res > 0 && buf[res-1] != '\n'); |
| } |
| |
| static int read_pbm_int(QIODevice *d) |
| { |
| char c; |
| int val = -1; |
| bool digit; |
| bool hasOverflow = false; |
| for (;;) { |
| if (!d->getChar(&c)) // end of file |
| break; |
| digit = isdigit((uchar) c); |
| if (val != -1) { |
| if (digit) { |
| const int cValue = c - '0'; |
| if (val <= (INT_MAX - cValue) / 10) { |
| val = 10*val + cValue; |
| } else { |
| hasOverflow = true; |
| } |
| continue; |
| } else { |
| if (c == '#') // comment |
| discard_pbm_line(d); |
| break; |
| } |
| } |
| if (digit) // first digit |
| val = c - '0'; |
| else if (isspace((uchar) c)) |
| continue; |
| else if (c == '#') |
| discard_pbm_line(d); |
| else |
| break; |
| } |
| return hasOverflow ? -1 : val; |
| } |
| |
| static bool read_pbm_header(QIODevice *device, char& type, int& w, int& h, int& mcc) |
| { |
| char buf[3]; |
| if (device->read(buf, 3) != 3) // read P[1-6]<white-space> |
| return false; |
| |
| if (!(buf[0] == 'P' && isdigit((uchar) buf[1]) && isspace((uchar) buf[2]))) |
| return false; |
| |
| type = buf[1]; |
| if (type < '1' || type > '6') |
| return false; |
| |
| w = read_pbm_int(device); // get image width |
| h = read_pbm_int(device); // get image height |
| |
| if (type == '1' || type == '4') |
| mcc = 1; // ignore max color component |
| else |
| mcc = read_pbm_int(device); // get max color component |
| |
| if (w <= 0 || w > 32767 || h <= 0 || h > 32767 || mcc <= 0 || mcc > 0xffff) |
| return false; // weird P.M image |
| |
| return true; |
| } |
| |
| static inline QRgb scale_pbm_color(quint16 mx, quint16 rv, quint16 gv, quint16 bv) |
| { |
| return QRgba64::fromRgba64((rv * 0xffffu) / mx, (gv * 0xffffu) / mx, (bv * 0xffffu) / mx, 0xffff).toArgb32(); |
| } |
| |
| static bool read_pbm_body(QIODevice *device, char type, int w, int h, int mcc, QImage *outImage) |
| { |
| int nbits, y; |
| int pbm_bpl; |
| bool raw; |
| |
| QImage::Format format; |
| switch (type) { |
| case '1': // ascii PBM |
| case '4': // raw PBM |
| nbits = 1; |
| format = QImage::Format_Mono; |
| break; |
| case '2': // ascii PGM |
| case '5': // raw PGM |
| nbits = 8; |
| format = QImage::Format_Grayscale8; |
| break; |
| case '3': // ascii PPM |
| case '6': // raw PPM |
| nbits = 32; |
| format = QImage::Format_RGB32; |
| break; |
| default: |
| return false; |
| } |
| raw = type >= '4'; |
| |
| if (outImage->size() != QSize(w, h) || outImage->format() != format) { |
| *outImage = QImage(w, h, format); |
| if (outImage->isNull()) |
| return false; |
| } |
| |
| pbm_bpl = (nbits*w+7)/8; // bytes per scanline in PBM |
| |
| if (raw) { // read raw data |
| if (nbits == 32) { // type 6 |
| pbm_bpl = mcc < 256 ? 3*w : 6*w; |
| uchar *buf24 = new uchar[pbm_bpl], *b; |
| QRgb *p; |
| QRgb *end; |
| for (y=0; y<h; y++) { |
| if (device->read((char *)buf24, pbm_bpl) != pbm_bpl) { |
| delete[] buf24; |
| return false; |
| } |
| p = (QRgb *)outImage->scanLine(y); |
| end = p + w; |
| b = buf24; |
| while (p < end) { |
| if (mcc < 256) { |
| 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 { |
| 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++) { |
| 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 |
| uchar *p; |
| int n; |
| char buf; |
| for (y = 0; (y < h) && (device->peek(&buf, 1) == 1); y++) { |
| p = outImage->scanLine(y); |
| n = pbm_bpl; |
| if (nbits == 1) { |
| int b; |
| int bitsLeft = w; |
| while (n--) { |
| b = 0; |
| for (int i=0; i<8; i++) { |
| if (i < bitsLeft) |
| b = (b << 1) | (read_pbm_int(device) & 1); |
| else |
| b = (b << 1) | (0 & 1); // pad it our self if we need to |
| } |
| bitsLeft -= 8; |
| *p++ = b; |
| } |
| } else if (nbits == 8) { |
| if (mcc == 255) { |
| while (n--) { |
| *p++ = read_pbm_int(device); |
| } |
| } else { |
| while (n--) { |
| *p++ = (read_pbm_int(device) & 0xffff) * 255 / mcc; |
| } |
| } |
| } else { // 32 bits |
| n /= 4; |
| int r, g, b; |
| if (mcc == 255) { |
| while (n--) { |
| r = read_pbm_int(device); |
| g = read_pbm_int(device); |
| b = read_pbm_int(device); |
| *((QRgb*)p) = qRgb(r, g, b); |
| p += 4; |
| } |
| } else { |
| while (n--) { |
| 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; |
| } |
| } |
| } |
| } |
| } |
| |
| if (format == QImage::Format_Mono) { |
| outImage->setColorCount(2); |
| outImage->setColor(0, qRgb(255,255,255)); // white |
| outImage->setColor(1, qRgb(0,0,0)); // black |
| } |
| |
| return true; |
| } |
| |
| static bool write_pbm_image(QIODevice *out, const QImage &sourceImage, const QByteArray &sourceFormat) |
| { |
| QByteArray str; |
| QImage image = sourceImage; |
| QByteArray format = sourceFormat; |
| |
| format = format.left(3); // ignore RAW part |
| bool gray = format == "pgm"; |
| |
| if (format == "pbm") { |
| image = image.convertToFormat(QImage::Format_Mono); |
| } else if (gray) { |
| image = image.convertToFormat(QImage::Format_Grayscale8); |
| } else { |
| switch (image.format()) { |
| case QImage::Format_Mono: |
| case QImage::Format_MonoLSB: |
| image = image.convertToFormat(QImage::Format_Indexed8); |
| break; |
| case QImage::Format_Indexed8: |
| case QImage::Format_RGB32: |
| case QImage::Format_ARGB32: |
| break; |
| default: |
| if (image.hasAlphaChannel()) |
| image = image.convertToFormat(QImage::Format_ARGB32); |
| else |
| image = image.convertToFormat(QImage::Format_RGB32); |
| break; |
| } |
| } |
| |
| if (image.depth() == 1 && image.colorCount() == 2) { |
| if (qGray(image.color(0)) < qGray(image.color(1))) { |
| // 0=dark/black, 1=light/white - invert |
| image.detach(); |
| for (int y=0; y<image.height(); y++) { |
| uchar *p = image.scanLine(y); |
| uchar *end = p + image.bytesPerLine(); |
| while (p < end) |
| *p++ ^= 0xff; |
| } |
| } |
| } |
| |
| uint w = image.width(); |
| uint h = image.height(); |
| |
| str = "P\n"; |
| str += QByteArray::number(w); |
| str += ' '; |
| str += QByteArray::number(h); |
| str += '\n'; |
| |
| switch (image.depth()) { |
| case 1: { |
| str.insert(1, '4'); |
| if (out->write(str, str.length()) != str.length()) |
| return false; |
| w = (w+7)/8; |
| for (uint y=0; y<h; y++) { |
| uchar* line = image.scanLine(y); |
| if (w != (uint)out->write((char*)line, w)) |
| return false; |
| } |
| } |
| break; |
| |
| case 8: { |
| str.insert(1, gray ? '5' : '6'); |
| str.append("255\n"); |
| if (out->write(str, str.length()) != str.length()) |
| return false; |
| uint bpl = w * (gray ? 1 : 3); |
| uchar *buf = new uchar[bpl]; |
| if (image.format() == QImage::Format_Indexed8) { |
| QVector<QRgb> color = image.colorTable(); |
| for (uint y=0; y<h; y++) { |
| const uchar *b = image.constScanLine(y); |
| uchar *p = buf; |
| uchar *end = buf+bpl; |
| if (gray) { |
| while (p < end) { |
| uchar g = (uchar)qGray(color[*b++]); |
| *p++ = g; |
| } |
| } else { |
| while (p < end) { |
| QRgb rgb = color[*b++]; |
| *p++ = qRed(rgb); |
| *p++ = qGreen(rgb); |
| *p++ = qBlue(rgb); |
| } |
| } |
| if (bpl != (uint)out->write((char*)buf, bpl)) |
| return false; |
| } |
| } else { |
| for (uint y=0; y<h; y++) { |
| const uchar *b = image.constScanLine(y); |
| uchar *p = buf; |
| uchar *end = buf + bpl; |
| if (gray) { |
| while (p < end) |
| *p++ = *b++; |
| } else { |
| while (p < end) { |
| uchar color = *b++; |
| *p++ = color; |
| *p++ = color; |
| *p++ = color; |
| } |
| } |
| if (bpl != (uint)out->write((char*)buf, bpl)) |
| return false; |
| } |
| } |
| delete [] buf; |
| break; |
| } |
| |
| case 32: { |
| str.insert(1, '6'); |
| str.append("255\n"); |
| if (out->write(str, str.length()) != str.length()) |
| return false; |
| uint bpl = w * 3; |
| uchar *buf = new uchar[bpl]; |
| for (uint y=0; y<h; y++) { |
| const QRgb *b = reinterpret_cast<const QRgb *>(image.constScanLine(y)); |
| uchar *p = buf; |
| uchar *end = buf+bpl; |
| while (p < end) { |
| QRgb rgb = *b++; |
| *p++ = qRed(rgb); |
| *p++ = qGreen(rgb); |
| *p++ = qBlue(rgb); |
| } |
| if (bpl != (uint)out->write((char*)buf, bpl)) |
| return false; |
| } |
| delete [] buf; |
| break; |
| } |
| |
| default: |
| return false; |
| } |
| |
| return true; |
| } |
| |
| QPpmHandler::QPpmHandler() |
| : state(Ready) |
| { |
| } |
| |
| bool QPpmHandler::readHeader() |
| { |
| state = Error; |
| if (!read_pbm_header(device(), type, width, height, mcc)) |
| return false; |
| state = ReadHeader; |
| return true; |
| } |
| |
| bool QPpmHandler::canRead() const |
| { |
| if (state == Ready && !canRead(device(), &subType)) |
| return false; |
| |
| if (state != Error) { |
| setFormat(subType); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool QPpmHandler::canRead(QIODevice *device, QByteArray *subType) |
| { |
| if (!device) { |
| qWarning("QPpmHandler::canRead() called with no device"); |
| return false; |
| } |
| |
| char head[2]; |
| if (device->peek(head, sizeof(head)) != sizeof(head)) |
| return false; |
| |
| if (head[0] != 'P') |
| return false; |
| |
| if (head[1] == '1' || head[1] == '4') { |
| if (subType) |
| *subType = "pbm"; |
| } else if (head[1] == '2' || head[1] == '5') { |
| if (subType) |
| *subType = "pgm"; |
| } else if (head[1] == '3' || head[1] == '6') { |
| if (subType) |
| *subType = "ppm"; |
| } else { |
| return false; |
| } |
| return true; |
| } |
| |
| bool QPpmHandler::read(QImage *image) |
| { |
| if (state == Error) |
| return false; |
| |
| if (state == Ready && !readHeader()) { |
| state = Error; |
| return false; |
| } |
| |
| if (!read_pbm_body(device(), type, width, height, mcc, image)) { |
| state = Error; |
| return false; |
| } |
| |
| state = Ready; |
| return true; |
| } |
| |
| bool QPpmHandler::write(const QImage &image) |
| { |
| return write_pbm_image(device(), image, subType); |
| } |
| |
| bool QPpmHandler::supportsOption(ImageOption option) const |
| { |
| return option == SubType |
| || option == Size |
| || option == ImageFormat; |
| } |
| |
| QVariant QPpmHandler::option(ImageOption option) const |
| { |
| if (option == SubType) { |
| return subType; |
| } else if (option == Size) { |
| if (state == Error) |
| return QVariant(); |
| if (state == Ready && !const_cast<QPpmHandler*>(this)->readHeader()) |
| return QVariant(); |
| return QSize(width, height); |
| } else if (option == ImageFormat) { |
| if (state == Error) |
| return QVariant(); |
| if (state == Ready && !const_cast<QPpmHandler*>(this)->readHeader()) |
| return QVariant(); |
| QImage::Format format = QImage::Format_Invalid; |
| switch (type) { |
| case '1': // ascii PBM |
| case '4': // raw PBM |
| format = QImage::Format_Mono; |
| break; |
| case '2': // ascii PGM |
| case '5': // raw PGM |
| format = QImage::Format_Grayscale8; |
| break; |
| case '3': // ascii PPM |
| case '6': // raw PPM |
| format = QImage::Format_RGB32; |
| break; |
| default: |
| break; |
| } |
| return format; |
| } |
| return QVariant(); |
| } |
| |
| void QPpmHandler::setOption(ImageOption option, const QVariant &value) |
| { |
| if (option == SubType) |
| subType = value.toByteArray().toLower(); |
| } |
| |
| QT_END_NAMESPACE |
| |
| #endif // QT_NO_IMAGEFORMAT_PPM |