File indexing completed on 2024-04-28 15:25:42

0001 /*
0002     Softimage PIC support for QImage.
0003 
0004     SPDX-FileCopyrightText: 1998 Halfdan Ingvarsson
0005     SPDX-FileCopyrightText: 2007 Ruben Lopez <r.lopez@bren.es>
0006     SPDX-FileCopyrightText: 2014 Alex Merry <alex.merry@kde.org>
0007 
0008     SPDX-License-Identifier: LGPL-2.0-or-later
0009 */
0010 
0011 /* This code is based on the GIMP-PIC plugin by Halfdan Ingvarsson,
0012  * and relicensed from GPL to LGPL to accommodate the KDE licensing policy
0013  * with his permission.
0014  */
0015 
0016 #include "pic_p.h"
0017 #include "rle_p.h"
0018 #include "util_p.h"
0019 
0020 #include <QDataStream>
0021 #include <QDebug>
0022 #include <QImage>
0023 #include <QVariant>
0024 #include <algorithm>
0025 #include <functional>
0026 #include <qendian.h>
0027 #include <utility>
0028 
0029 /**
0030  * Reads a PIC file header from a data stream.
0031  *
0032  * @param s         The data stream to read from.
0033  * @param channels  Where the read header will be stored.
0034  * @returns  @p s
0035  *
0036  * @relates PicHeader
0037  */
0038 static QDataStream &operator>>(QDataStream &s, PicHeader &header)
0039 {
0040     s.setFloatingPointPrecision(QDataStream::SinglePrecision);
0041     s >> header.magic;
0042     s >> header.version;
0043 
0044     // the comment should be truncated to the first null byte
0045     char comment[81] = {};
0046     s.readRawData(comment, 80);
0047     header.comment = QByteArray(comment);
0048 
0049     header.id.resize(4);
0050     const int bytesRead = s.readRawData(header.id.data(), 4);
0051     if (bytesRead != 4) {
0052         header.id.resize(bytesRead);
0053     }
0054 
0055     s >> header.width;
0056     s >> header.height;
0057     s >> header.ratio;
0058     qint16 fields;
0059     s >> fields;
0060     header.fields = static_cast<PicFields>(fields);
0061     qint16 pad;
0062     s >> pad;
0063     return s;
0064 }
0065 
0066 /**
0067  * Writes a PIC file header to a data stream.
0068  *
0069  * @param s         The data stream to write to.
0070  * @param channels  The header to write.
0071  * @returns  @p s
0072  *
0073  * @relates PicHeader
0074  */
0075 static QDataStream &operator<<(QDataStream &s, const PicHeader &header)
0076 {
0077     s.setFloatingPointPrecision(QDataStream::SinglePrecision);
0078     s << header.magic;
0079     s << header.version;
0080 
0081     char comment[80] = {};
0082     strncpy(comment, header.comment.constData(), sizeof(comment));
0083     s.writeRawData(comment, sizeof(comment));
0084 
0085     char id[4] = {};
0086     strncpy(id, header.id.constData(), sizeof(id));
0087     s.writeRawData(id, sizeof(id));
0088 
0089     s << header.width;
0090     s << header.height;
0091     s << header.ratio;
0092     s << quint16(header.fields);
0093     s << quint16(0);
0094     return s;
0095 }
0096 
0097 /**
0098  * Reads a series of channel descriptions from a data stream.
0099  *
0100  * If the stream contains more than 8 channel descriptions, the status of @p s
0101  * will be set to QDataStream::ReadCorruptData (note that more than 4 channels
0102  * - one for each component - does not really make sense anyway).
0103  *
0104  * @param s         The data stream to read from.
0105  * @param channels  The location to place the read channel descriptions; any
0106  *                  existing entries will be cleared.
0107  * @returns  @p s
0108  *
0109  * @relates PicChannel
0110  */
0111 static QDataStream &operator>>(QDataStream &s, QList<PicChannel> &channels)
0112 {
0113     const unsigned maxChannels = 8;
0114     unsigned count = 0;
0115     quint8 chained = 1;
0116     channels.clear();
0117     while (chained && count < maxChannels && s.status() == QDataStream::Ok) {
0118         PicChannel channel;
0119         s >> chained;
0120         s >> channel.size;
0121         s >> channel.encoding;
0122         s >> channel.code;
0123         channels << channel;
0124         ++count;
0125     }
0126     if (chained) {
0127         // too many channels!
0128         s.setStatus(QDataStream::ReadCorruptData);
0129     }
0130     return s;
0131 }
0132 
0133 /**
0134  * Writes a series of channel descriptions to a data stream.
0135  *
0136  * Note that the corresponding read operation will not read more than 8 channel
0137  * descriptions, although there should be no reason to have more than 4 channels
0138  * anyway.
0139  *
0140  * @param s         The data stream to write to.
0141  * @param channels  The channel descriptions to write.
0142  * @returns  @p s
0143  *
0144  * @relates PicChannel
0145  */
0146 static QDataStream &operator<<(QDataStream &s, const QList<PicChannel> &channels)
0147 {
0148     Q_ASSERT(channels.size() > 0);
0149     for (int i = 0; i < channels.size() - 1; ++i) {
0150         s << quint8(1); // chained
0151         s << channels[i].size;
0152         s << quint8(channels[i].encoding);
0153         s << channels[i].code;
0154     }
0155     s << quint8(0); // chained
0156     s << channels.last().size;
0157     s << quint8(channels.last().encoding);
0158     s << channels.last().code;
0159     return s;
0160 }
0161 
0162 static bool readRow(QDataStream &stream, QRgb *row, quint16 width, const QList<PicChannel> &channels)
0163 {
0164     for (const PicChannel &channel : channels) {
0165         auto readPixel = [&](QDataStream &str) -> QRgb {
0166             quint8 red = 0;
0167             if (channel.code & RED) {
0168                 str >> red;
0169             }
0170             quint8 green = 0;
0171             if (channel.code & GREEN) {
0172                 str >> green;
0173             }
0174             quint8 blue = 0;
0175             if (channel.code & BLUE) {
0176                 str >> blue;
0177             }
0178             quint8 alpha = 0;
0179             if (channel.code & ALPHA) {
0180                 str >> alpha;
0181             }
0182             return qRgba(red, green, blue, alpha);
0183         };
0184         auto updatePixel = [&](QRgb oldPixel, QRgb newPixel) -> QRgb {
0185             return qRgba(qRed((channel.code & RED) ? newPixel : oldPixel),
0186                          qGreen((channel.code & GREEN) ? newPixel : oldPixel),
0187                          qBlue((channel.code & BLUE) ? newPixel : oldPixel),
0188                          qAlpha((channel.code & ALPHA) ? newPixel : oldPixel));
0189         };
0190         if (channel.encoding == MixedRLE) {
0191             bool success = decodeRLEData(RLEVariant::PIC, stream, row, width, readPixel, updatePixel);
0192             if (!success) {
0193                 qDebug() << "decodeRLEData failed";
0194                 return false;
0195             }
0196         } else if (channel.encoding == Uncompressed) {
0197             for (quint16 i = 0; i < width; ++i) {
0198                 QRgb pixel = readPixel(stream);
0199                 row[i] = updatePixel(row[i], pixel);
0200             }
0201         } else {
0202             // unknown encoding
0203             qDebug() << "Unknown encoding";
0204             return false;
0205         }
0206     }
0207     if (stream.status() != QDataStream::Ok) {
0208         qDebug() << "DataStream status was" << stream.status();
0209     }
0210     return stream.status() == QDataStream::Ok;
0211 }
0212 
0213 bool SoftimagePICHandler::canRead() const
0214 {
0215     if (!SoftimagePICHandler::canRead(device())) {
0216         return false;
0217     }
0218     setFormat("pic");
0219     return true;
0220 }
0221 
0222 bool SoftimagePICHandler::read(QImage *image)
0223 {
0224     if (!readChannels()) {
0225         return false;
0226     }
0227 
0228     QImage::Format fmt = QImage::Format_RGB32;
0229     for (const PicChannel &channel : std::as_const(m_channels)) {
0230         if (channel.size != 8) {
0231             // we cannot read images that do not come in bytes
0232             qDebug() << "Channel size was" << channel.size;
0233             m_state = Error;
0234             return false;
0235         }
0236         if (channel.code & ALPHA) {
0237             fmt = QImage::Format_ARGB32;
0238         }
0239     }
0240 
0241     QImage img = imageAlloc(m_header.width, m_header.height, fmt);
0242     if (img.isNull()) {
0243         qDebug() << "Failed to allocate image, invalid dimensions?" << QSize(m_header.width, m_header.height) << fmt;
0244         return false;
0245     }
0246 
0247     img.fill(qRgb(0, 0, 0));
0248 
0249     for (int y = 0; y < m_header.height; y++) {
0250         QRgb *row = reinterpret_cast<QRgb *>(img.scanLine(y));
0251         if (!readRow(m_dataStream, row, m_header.width, m_channels)) {
0252             qDebug() << "readRow failed";
0253             m_state = Error;
0254             return false;
0255         }
0256     }
0257 
0258     *image = img;
0259     m_state = Ready;
0260 
0261     return true;
0262 }
0263 
0264 bool SoftimagePICHandler::write(const QImage &_image)
0265 {
0266     bool alpha = _image.hasAlphaChannel();
0267     const QImage image = _image.convertToFormat(alpha ? QImage::Format_ARGB32 : QImage::Format_RGB32);
0268 
0269     if (image.width() < 0 || image.height() < 0) {
0270         qDebug() << "Image size invalid:" << image.width() << image.height();
0271         return false;
0272     }
0273     if (image.width() > 65535 || image.height() > 65535) {
0274         qDebug() << "Image too big:" << image.width() << image.height();
0275         // there are only two bytes for each dimension
0276         return false;
0277     }
0278 
0279     QDataStream stream(device());
0280 
0281     stream << PicHeader(image.width(), image.height(), m_description);
0282 
0283     PicChannelEncoding encoding = m_compression ? MixedRLE : Uncompressed;
0284     QList<PicChannel> channels;
0285     channels << PicChannel(encoding, RED | GREEN | BLUE);
0286     if (alpha) {
0287         channels << PicChannel(encoding, ALPHA);
0288     }
0289     stream << channels;
0290 
0291     for (int r = 0; r < image.height(); r++) {
0292         const QRgb *row = reinterpret_cast<const QRgb *>(image.scanLine(r));
0293 
0294         /* Write the RGB part of the scanline */
0295         auto rgbEqual = [](QRgb p1, QRgb p2) -> bool {
0296             return qRed(p1) == qRed(p2) && qGreen(p1) == qGreen(p2) && qBlue(p1) == qBlue(p2);
0297         };
0298         auto writeRgb = [](QDataStream &str, QRgb pixel) -> void {
0299             str << quint8(qRed(pixel)) << quint8(qGreen(pixel)) << quint8(qBlue(pixel));
0300         };
0301         if (m_compression) {
0302             encodeRLEData(RLEVariant::PIC, stream, row, image.width(), rgbEqual, writeRgb);
0303         } else {
0304             for (int i = 0; i < image.width(); ++i) {
0305                 writeRgb(stream, row[i]);
0306             }
0307         }
0308 
0309         /* Write the alpha channel */
0310         if (alpha) {
0311             auto alphaEqual = [](QRgb p1, QRgb p2) -> bool {
0312                 return qAlpha(p1) == qAlpha(p2);
0313             };
0314             auto writeAlpha = [](QDataStream &str, QRgb pixel) -> void {
0315                 str << quint8(qAlpha(pixel));
0316             };
0317             if (m_compression) {
0318                 encodeRLEData(RLEVariant::PIC, stream, row, image.width(), alphaEqual, writeAlpha);
0319             } else {
0320                 for (int i = 0; i < image.width(); ++i) {
0321                     writeAlpha(stream, row[i]);
0322                 }
0323             }
0324         }
0325     }
0326     return stream.status() == QDataStream::Ok;
0327 }
0328 
0329 bool SoftimagePICHandler::canRead(QIODevice *device)
0330 {
0331     char data[4];
0332     if (device->peek(data, 4) != 4) {
0333         return false;
0334     }
0335     return qFromBigEndian<qint32>(reinterpret_cast<uchar *>(data)) == PIC_MAGIC_NUMBER;
0336 }
0337 
0338 bool SoftimagePICHandler::readHeader()
0339 {
0340     if (m_state == Ready) {
0341         m_state = Error;
0342         m_dataStream.setDevice(device());
0343         m_dataStream >> m_header;
0344         if (m_header.isValid() && m_dataStream.status() == QDataStream::Ok) {
0345             m_state = ReadHeader;
0346         }
0347     }
0348 
0349     return m_state != Error;
0350 }
0351 
0352 bool SoftimagePICHandler::readChannels()
0353 {
0354     readHeader();
0355     if (m_state == ReadHeader) {
0356         m_state = Error;
0357         m_dataStream >> m_channels;
0358         if (m_dataStream.status() == QDataStream::Ok) {
0359             m_state = ReadChannels;
0360         }
0361     }
0362     return m_state != Error;
0363 }
0364 
0365 void SoftimagePICHandler::setOption(ImageOption option, const QVariant &value)
0366 {
0367     switch (option) {
0368     case CompressionRatio:
0369         m_compression = value.toBool();
0370         break;
0371     case Description: {
0372         m_description.clear();
0373         const QStringList entries = value.toString().split(QStringLiteral("\n\n"));
0374         for (const QString &entry : entries) {
0375             if (entry.startsWith(QStringLiteral("Description: "))) {
0376                 m_description = entry.mid(13).simplified().toUtf8();
0377             }
0378         }
0379         break;
0380     }
0381     default:
0382         break;
0383     }
0384 }
0385 
0386 QVariant SoftimagePICHandler::option(ImageOption option) const
0387 {
0388     const_cast<SoftimagePICHandler *>(this)->readHeader();
0389     switch (option) {
0390     case Size:
0391         if (const_cast<SoftimagePICHandler *>(this)->readHeader()) {
0392             return QSize(m_header.width, m_header.height);
0393         } else {
0394             return QVariant();
0395         }
0396     case CompressionRatio:
0397         return m_compression;
0398     case Description:
0399         if (const_cast<SoftimagePICHandler *>(this)->readHeader()) {
0400             QString descStr = QString::fromUtf8(m_header.comment);
0401             if (!descStr.isEmpty()) {
0402                 return QString(QStringLiteral("Description: ") + descStr + QStringLiteral("\n\n"));
0403             }
0404         }
0405         return QString();
0406     case ImageFormat:
0407         if (const_cast<SoftimagePICHandler *>(this)->readChannels()) {
0408             for (const PicChannel &channel : std::as_const(m_channels)) {
0409                 if (channel.code & ALPHA) {
0410                     return QImage::Format_ARGB32;
0411                 }
0412             }
0413             return QImage::Format_RGB32;
0414         }
0415         return QVariant();
0416     default:
0417         return QVariant();
0418     }
0419 }
0420 
0421 bool SoftimagePICHandler::supportsOption(ImageOption option) const
0422 {
0423     return (option == CompressionRatio || option == Description || option == ImageFormat || option == Size);
0424 }
0425 
0426 QImageIOPlugin::Capabilities SoftimagePICPlugin::capabilities(QIODevice *device, const QByteArray &format) const
0427 {
0428     if (format == "pic") {
0429         return Capabilities(CanRead | CanWrite);
0430     }
0431     if (!format.isEmpty()) {
0432         return {};
0433     }
0434     if (!device->isOpen()) {
0435         return {};
0436     }
0437 
0438     Capabilities cap;
0439     if (device->isReadable() && SoftimagePICHandler::canRead(device)) {
0440         cap |= CanRead;
0441     }
0442     if (device->isWritable()) {
0443         cap |= CanWrite;
0444     }
0445     return cap;
0446 }
0447 
0448 QImageIOHandler *SoftimagePICPlugin::create(QIODevice *device, const QByteArray &format) const
0449 {
0450     QImageIOHandler *handler = new SoftimagePICHandler();
0451     handler->setDevice(device);
0452     handler->setFormat(format);
0453     return handler;
0454 }
0455 
0456 #include "moc_pic_p.cpp"