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"