| /**************************************************************************** |
| ** |
| ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). |
| ** All rights reserved. |
| ** Contact: Nokia Corporation (qt-info@nokia.com) |
| ** |
| ** This file is part of the QtGui module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** GNU Lesser General Public License Usage |
| ** This file may be used under the terms of the GNU Lesser General Public |
| ** License version 2.1 as published by the Free Software Foundation and |
| ** appearing in the file LICENSE.LGPL included in the packaging of this |
| ** file. Please review the following information to ensure the GNU Lesser |
| ** General Public License version 2.1 requirements will be met: |
| ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
| ** |
| ** In addition, as a special exception, Nokia gives you certain additional |
| ** rights. These rights are described in the Nokia Qt LGPL Exception |
| ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU General |
| ** Public License version 3.0 as published by the Free Software Foundation |
| ** and appearing in the file LICENSE.GPL included in the packaging of this |
| ** file. Please review the following information to ensure the GNU General |
| ** Public License version 3.0 requirements will be met: |
| ** http://www.gnu.org/copyleft/gpl.html. |
| ** |
| ** Other Usage |
| ** Alternatively, this file may be used in accordance with the terms and |
| ** conditions contained in a signed written agreement between you and Nokia. |
| ** |
| ** |
| ** |
| ** |
| ** |
| ** $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> |
| |
| QT_BEGIN_NAMESPACE |
| |
| /***************************************************************************** |
| PBM/PGM/PPM (ASCII and RAW) image read/write functions |
| *****************************************************************************/ |
| |
| static int read_pbm_int(QIODevice *d) |
| { |
| char c; |
| int val = -1; |
| bool digit; |
| const int buflen = 100; |
| char buf[buflen]; |
| for (;;) { |
| if (!d->getChar(&c)) // end of file |
| break; |
| digit = isdigit((uchar) c); |
| if (val != -1) { |
| if (digit) { |
| val = 10*val + c - '0'; |
| continue; |
| } else { |
| if (c == '#') // comment |
| d->readLine(buf, buflen); |
| break; |
| } |
| } |
| if (digit) // first digit |
| val = c - '0'; |
| else if (isspace((uchar) c)) |
| continue; |
| else if (c == '#') |
| (void)d->readLine(buf, buflen); |
| else |
| break; |
| } |
| return 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) |
| return false; // weird P.M image |
| |
| return true; |
| } |
| |
| 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_Indexed8; |
| break; |
| case '3': // ascii PPM |
| case '6': // raw PPM |
| nbits = 32; |
| format = QImage::Format_RGB32; |
| break; |
| default: |
| return false; |
| } |
| 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()) |
| 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) { |
| *p++ = qRgb(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); |
| b += 6; |
| } |
| } |
| } |
| delete[] buf24; |
| } else { // type 4,5 |
| for (y=0; y<h; y++) { |
| if (device->read((char *)outImage->scanLine(y), pbm_bpl) |
| != pbm_bpl) |
| return false; |
| } |
| } |
| } else { // read ascii data |
| register uchar *p; |
| int n; |
| for (y=0; y<h; 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 == maxc) { |
| while (n--) { |
| *p++ = read_pbm_int(device); |
| } |
| } else { |
| while (n--) { |
| *p++ = read_pbm_int(device) * maxc / mcc; |
| } |
| } |
| } else { // 32 bits |
| n /= 4; |
| int r, g, b; |
| if (mcc == maxc) { |
| 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) * maxc / mcc; |
| g = read_pbm_int(device) * maxc / mcc; |
| b = read_pbm_int(device) * maxc / mcc; |
| *((QRgb*)p) = qRgb(r, g, b); |
| p += 4; |
| } |
| } |
| } |
| } |
| } |
| |
| if (nbits == 1) { // bitmap |
| outImage->setColorCount(2); |
| outImage->setColor(0, qRgb(255,255,255)); // white |
| outImage->setColor(1, qRgb(0,0,0)); // black |
| } else if (nbits == 8) { // graymap |
| outImage->setColorCount(maxc+1); |
| for (int i=0; i<=maxc; i++) |
| outImage->setColor(i, qRgb(i*255/maxc,i*255/maxc,i*255/maxc)); |
| } |
| |
| 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 (image.depth() == 1) { |
| image = image.convertToFormat(QImage::Format_Indexed8); |
| } else { |
| switch (image.format()) { |
| case QImage::Format_RGB16: |
| case QImage::Format_RGB666: |
| case QImage::Format_RGB555: |
| case QImage::Format_RGB888: |
| case QImage::Format_RGB444: |
| image = image.convertToFormat(QImage::Format_RGB32); |
| break; |
| case QImage::Format_ARGB8565_Premultiplied: |
| case QImage::Format_ARGB6666_Premultiplied: |
| case QImage::Format_ARGB8555_Premultiplied: |
| case QImage::Format_ARGB4444_Premultiplied: |
| image = image.convertToFormat(QImage::Format_ARGB32); |
| break; |
| default: |
| 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; |
| QVector<QRgb> color = image.colorTable(); |
| uint bpl = w*(gray ? 1 : 3); |
| uchar *buf = new uchar[bpl]; |
| for (uint y=0; y<h; y++) { |
| uchar *b = image.scanLine(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; |
| } |
| delete [] buf; |
| } |
| break; |
| |
| case 32: { |
| 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]; |
| for (uint y=0; y<h; y++) { |
| QRgb *b = (QRgb*)image.scanLine(y); |
| uchar *p = buf; |
| uchar *end = buf+bpl; |
| if (gray) { |
| while (p < end) { |
| uchar g = (uchar)qGray(*b++); |
| *p++ = g; |
| } |
| } else { |
| 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_Indexed8; |
| 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(); |
| } |
| |
| QByteArray QPpmHandler::name() const |
| { |
| return subType.isEmpty() ? QByteArray("ppm") : subType; |
| } |
| |
| QT_END_NAMESPACE |
| |
| #endif // QT_NO_IMAGEFORMAT_PPM |