KImageFormats

pic.cpp
1 /*
2  Softimage PIC support for QImage.
3 
4  SPDX-FileCopyrightText: 1998 Halfdan Ingvarsson
5  SPDX-FileCopyrightText: 2007 Ruben Lopez <[email protected]>
6  SPDX-FileCopyrightText: 2014 Alex Merry <[email protected]>
7 
8  SPDX-License-Identifier: LGPL-2.0-or-later
9 */
10 
11 /* This code is based on the GIMP-PIC plugin by Halfdan Ingvarsson,
12  * and relicensed from GPL to LGPL to accommodate the KDE licensing policy
13  * with his permission.
14  */
15 
16 #include "pic_p.h"
17 
18 #include "rle_p.h"
19 
20 #include <QDataStream>
21 #include <QDebug>
22 #include <QImage>
23 #include <QVariant>
24 #include <algorithm>
25 #include <functional>
26 #include <qendian.h>
27 #include <utility>
28 
29 /**
30  * Reads a PIC file header from a data stream.
31  *
32  * @param s The data stream to read from.
33  * @param channels Where the read header will be stored.
34  * @returns @p s
35  *
36  * @relates PicHeader
37  */
38 static QDataStream &operator>>(QDataStream &s, PicHeader &header)
39 {
41  s >> header.magic;
42  s >> header.version;
43 
44  // the comment should be truncated to the first null byte
45  char comment[81] = {};
46  s.readRawData(comment, 80);
47  header.comment = QByteArray(comment);
48 
49  header.id.resize(4);
50  const int bytesRead = s.readRawData(header.id.data(), 4);
51  if (bytesRead != 4) {
52  header.id.resize(bytesRead);
53  }
54 
55  s >> header.width;
56  s >> header.height;
57  s >> header.ratio;
58  qint16 fields;
59  s >> fields;
60  header.fields = static_cast<PicFields>(fields);
61  qint16 pad;
62  s >> pad;
63  return s;
64 }
65 
66 /**
67  * Writes a PIC file header to a data stream.
68  *
69  * @param s The data stream to write to.
70  * @param channels The header to write.
71  * @returns @p s
72  *
73  * @relates PicHeader
74  */
75 static QDataStream &operator<<(QDataStream &s, const PicHeader &header)
76 {
78  s << header.magic;
79  s << header.version;
80 
81  char comment[80] = {};
82  strncpy(comment, header.comment.constData(), sizeof(comment));
83  s.writeRawData(comment, sizeof(comment));
84 
85  char id[4] = {};
86  strncpy(id, header.id.constData(), sizeof(id));
87  s.writeRawData(id, sizeof(id));
88 
89  s << header.width;
90  s << header.height;
91  s << header.ratio;
92  s << quint16(header.fields);
93  s << quint16(0);
94  return s;
95 }
96 
97 /**
98  * Reads a series of channel descriptions from a data stream.
99  *
100  * If the stream contains more than 8 channel descriptions, the status of @p s
101  * will be set to QDataStream::ReadCorruptData (note that more than 4 channels
102  * - one for each component - does not really make sense anyway).
103  *
104  * @param s The data stream to read from.
105  * @param channels The location to place the read channel descriptions; any
106  * existing entries will be cleared.
107  * @returns @p s
108  *
109  * @relates PicChannel
110  */
112 {
113  const unsigned maxChannels = 8;
114  unsigned count = 0;
115  quint8 chained = 1;
116  channels.clear();
117  while (chained && count < maxChannels && s.status() == QDataStream::Ok) {
118  PicChannel channel;
119  s >> chained;
120  s >> channel.size;
121  s >> channel.encoding;
122  s >> channel.code;
123  channels << channel;
124  ++count;
125  }
126  if (chained) {
127  // too many channels!
129  }
130  return s;
131 }
132 
133 /**
134  * Writes a series of channel descriptions to a data stream.
135  *
136  * Note that the corresponding read operation will not read more than 8 channel
137  * descriptions, although there should be no reason to have more than 4 channels
138  * anyway.
139  *
140  * @param s The data stream to write to.
141  * @param channels The channel descriptions to write.
142  * @returns @p s
143  *
144  * @relates PicChannel
145  */
146 static QDataStream &operator<<(QDataStream &s, const QList<PicChannel> &channels)
147 {
148  Q_ASSERT(channels.size() > 0);
149  for (int i = 0; i < channels.size() - 1; ++i) {
150  s << quint8(1); // chained
151  s << channels[i].size;
152  s << quint8(channels[i].encoding);
153  s << channels[i].code;
154  }
155  s << quint8(0); // chained
156  s << channels.last().size;
157  s << quint8(channels.last().encoding);
158  s << channels.last().code;
159  return s;
160 }
161 
162 static bool readRow(QDataStream &stream, QRgb *row, quint16 width, const QList<PicChannel> &channels)
163 {
164  for (const PicChannel &channel : channels) {
165  auto readPixel = [&](QDataStream &str) -> QRgb {
166  quint8 red = 0;
167  if (channel.code & RED) {
168  str >> red;
169  }
170  quint8 green = 0;
171  if (channel.code & GREEN) {
172  str >> green;
173  }
174  quint8 blue = 0;
175  if (channel.code & BLUE) {
176  str >> blue;
177  }
178  quint8 alpha = 0;
179  if (channel.code & ALPHA) {
180  str >> alpha;
181  }
182  return qRgba(red, green, blue, alpha);
183  };
184  auto updatePixel = [&](QRgb oldPixel, QRgb newPixel) -> QRgb {
185  return qRgba(qRed((channel.code & RED) ? newPixel : oldPixel),
186  qGreen((channel.code & GREEN) ? newPixel : oldPixel),
187  qBlue((channel.code & BLUE) ? newPixel : oldPixel),
188  qAlpha((channel.code & ALPHA) ? newPixel : oldPixel));
189  };
190  if (channel.encoding == MixedRLE) {
191  bool success = decodeRLEData(RLEVariant::PIC, stream, row, width, readPixel, updatePixel);
192  if (!success) {
193  qDebug() << "decodeRLEData failed";
194  return false;
195  }
196  } else if (channel.encoding == Uncompressed) {
197  for (quint16 i = 0; i < width; ++i) {
198  QRgb pixel = readPixel(stream);
199  row[i] = updatePixel(row[i], pixel);
200  }
201  } else {
202  // unknown encoding
203  qDebug() << "Unknown encoding";
204  return false;
205  }
206  }
207  if (stream.status() != QDataStream::Ok) {
208  qDebug() << "DataStream status was" << stream.status();
209  }
210  return stream.status() == QDataStream::Ok;
211 }
212 
213 bool SoftimagePICHandler::canRead() const
214 {
215  if (!SoftimagePICHandler::canRead(device())) {
216  return false;
217  }
218  setFormat("pic");
219  return true;
220 }
221 
222 bool SoftimagePICHandler::read(QImage *image)
223 {
224  if (!readChannels()) {
225  return false;
226  }
227 
229  for (const PicChannel &channel : std::as_const(m_channels)) {
230  if (channel.size != 8) {
231  // we cannot read images that do not come in bytes
232  qDebug() << "Channel size was" << channel.size;
233  m_state = Error;
234  return false;
235  }
236  if (channel.code & ALPHA) {
237  fmt = QImage::Format_ARGB32;
238  }
239  }
240 
241  QImage img(m_header.width, m_header.height, fmt);
242  if (img.isNull()) {
243  qDebug() << "Failed to allocate image, invalid dimensions?" << QSize(m_header.width, m_header.height) << fmt;
244  return false;
245  }
246 
247  img.fill(qRgb(0, 0, 0));
248 
249  for (int y = 0; y < m_header.height; y++) {
250  QRgb *row = reinterpret_cast<QRgb *>(img.scanLine(y));
251  if (!readRow(m_dataStream, row, m_header.width, m_channels)) {
252  qDebug() << "readRow failed";
253  m_state = Error;
254  return false;
255  }
256  }
257 
258  *image = img;
259  m_state = Ready;
260 
261  return true;
262 }
263 
264 bool SoftimagePICHandler::write(const QImage &_image)
265 {
266  bool alpha = _image.hasAlphaChannel();
267  const QImage image = _image.convertToFormat(alpha ? QImage::Format_ARGB32 : QImage::Format_RGB32);
268 
269  if (image.width() < 0 || image.height() < 0) {
270  qDebug() << "Image size invalid:" << image.width() << image.height();
271  return false;
272  }
273  if (image.width() > 65535 || image.height() > 65535) {
274  qDebug() << "Image too big:" << image.width() << image.height();
275  // there are only two bytes for each dimension
276  return false;
277  }
278 
279  QDataStream stream(device());
280 
281  stream << PicHeader(image.width(), image.height(), m_description);
282 
283  PicChannelEncoding encoding = m_compression ? MixedRLE : Uncompressed;
284  QList<PicChannel> channels;
285  channels << PicChannel(encoding, RED | GREEN | BLUE);
286  if (alpha) {
287  channels << PicChannel(encoding, ALPHA);
288  }
289  stream << channels;
290 
291  for (int r = 0; r < image.height(); r++) {
292  const QRgb *row = reinterpret_cast<const QRgb *>(image.scanLine(r));
293 
294  /* Write the RGB part of the scanline */
295  auto rgbEqual = [](QRgb p1, QRgb p2) -> bool {
296  return qRed(p1) == qRed(p2) && qGreen(p1) == qGreen(p2) && qBlue(p1) == qBlue(p2);
297  };
298  auto writeRgb = [](QDataStream &str, QRgb pixel) -> void {
299  str << quint8(qRed(pixel)) << quint8(qGreen(pixel)) << quint8(qBlue(pixel));
300  };
301  if (m_compression) {
302  encodeRLEData(RLEVariant::PIC, stream, row, image.width(), rgbEqual, writeRgb);
303  } else {
304  for (int i = 0; i < image.width(); ++i) {
305  writeRgb(stream, row[i]);
306  }
307  }
308 
309  /* Write the alpha channel */
310  if (alpha) {
311  auto alphaEqual = [](QRgb p1, QRgb p2) -> bool {
312  return qAlpha(p1) == qAlpha(p2);
313  };
314  auto writeAlpha = [](QDataStream &str, QRgb pixel) -> void {
315  str << quint8(qAlpha(pixel));
316  };
317  if (m_compression) {
318  encodeRLEData(RLEVariant::PIC, stream, row, image.width(), alphaEqual, writeAlpha);
319  } else {
320  for (int i = 0; i < image.width(); ++i) {
321  writeAlpha(stream, row[i]);
322  }
323  }
324  }
325  }
326  return stream.status() == QDataStream::Ok;
327 }
328 
329 bool SoftimagePICHandler::canRead(QIODevice *device)
330 {
331  char data[4];
332  if (device->peek(data, 4) != 4) {
333  return false;
334  }
335  return qFromBigEndian<qint32>(reinterpret_cast<uchar *>(data)) == PIC_MAGIC_NUMBER;
336 }
337 
338 bool SoftimagePICHandler::readHeader()
339 {
340  if (m_state == Ready) {
341  m_state = Error;
342  m_dataStream.setDevice(device());
343  m_dataStream >> m_header;
344  if (m_header.isValid() && m_dataStream.status() == QDataStream::Ok) {
345  m_state = ReadHeader;
346  }
347  }
348 
349  return m_state != Error;
350 }
351 
352 bool SoftimagePICHandler::readChannels()
353 {
354  readHeader();
355  if (m_state == ReadHeader) {
356  m_state = Error;
357  m_dataStream >> m_channels;
358  if (m_dataStream.status() == QDataStream::Ok) {
359  m_state = ReadChannels;
360  }
361  }
362  return m_state != Error;
363 }
364 
365 void SoftimagePICHandler::setOption(ImageOption option, const QVariant &value)
366 {
367  switch (option) {
368  case CompressionRatio:
369  m_compression = value.toBool();
370  break;
371  case Description: {
372  m_description.clear();
373  const QStringList entries = value.toString().split(QStringLiteral("\n\n"));
374  for (const QString &entry : entries) {
375  if (entry.startsWith(QStringLiteral("Description: "))) {
376  m_description = entry.mid(13).simplified().toUtf8();
377  }
378  }
379  break;
380  }
381  default:
382  break;
383  }
384 }
385 
386 QVariant SoftimagePICHandler::option(ImageOption option) const
387 {
388  const_cast<SoftimagePICHandler *>(this)->readHeader();
389  switch (option) {
390  case Size:
391  if (const_cast<SoftimagePICHandler *>(this)->readHeader()) {
392  return QSize(m_header.width, m_header.height);
393  } else {
394  return QVariant();
395  }
396  case CompressionRatio:
397  return m_compression;
398  case Description:
399  if (const_cast<SoftimagePICHandler *>(this)->readHeader()) {
400  QString descStr = QString::fromUtf8(m_header.comment);
401  if (!descStr.isEmpty()) {
402  return QString(QStringLiteral("Description: ") + descStr + QStringLiteral("\n\n"));
403  }
404  }
405  return QString();
406  case ImageFormat:
407  if (const_cast<SoftimagePICHandler *>(this)->readChannels()) {
408  for (const PicChannel &channel : std::as_const(m_channels)) {
409  if (channel.code & ALPHA) {
410  return QImage::Format_ARGB32;
411  }
412  }
413  return QImage::Format_RGB32;
414  }
415  return QVariant();
416  default:
417  return QVariant();
418  }
419 }
420 
421 bool SoftimagePICHandler::supportsOption(ImageOption option) const
422 {
423  return (option == CompressionRatio || option == Description || option == ImageFormat || option == Size);
424 }
425 
426 QImageIOPlugin::Capabilities SoftimagePICPlugin::capabilities(QIODevice *device, const QByteArray &format) const
427 {
428  if (format == "pic") {
429  return Capabilities(CanRead | CanWrite);
430  }
431  if (!format.isEmpty()) {
432  return {};
433  }
434  if (!device->isOpen()) {
435  return {};
436  }
437 
438  Capabilities cap;
439  if (device->isReadable() && SoftimagePICHandler::canRead(device)) {
440  cap |= CanRead;
441  }
442  if (device->isWritable()) {
443  cap |= CanWrite;
444  }
445  return cap;
446 }
447 
448 QImageIOHandler *SoftimagePICPlugin::create(QIODevice *device, const QByteArray &format) const
449 {
450  QImageIOHandler *handler = new SoftimagePICHandler();
451  handler->setDevice(device);
452  handler->setFormat(format);
453  return handler;
454 }
uchar * scanLine(int i)
void clear()
QImage convertToFormat(QImage::Format format, Qt::ImageConversionFlags flags) const &const
bool isWritable() const const
void setStatus(QDataStream::Status status)
QDataStream & operator>>(QDataStream &in, KDateTime::Spec &spec)
bool isEmpty() const const
bool hasAlphaChannel() const const
bool isReadable() const const
QDataStream::Status status() const const
int readRawData(char *s, int len)
int size() const const
Description
qint64 peek(char *data, qint64 maxSize)
void setFloatingPointPrecision(QDataStream::FloatingPointPrecision precision)
QString fromUtf8(const char *str, int size)
int width() const const
bool isEmpty() const const
QStringList split(const QString &sep, QString::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
void setDevice(QIODevice *device)
bool isOpen() const const
QDataStream & operator<<(QDataStream &out, const KDateTime::Spec &spec)
T & last()
int writeRawData(const char *s, int len)
QList< T > mid(int pos, int length) const const
bool toBool() const const
typedef Capabilities
void setFormat(const QByteArray &format)
int height() const const
int version() const const
QString toString() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Thu Dec 2 2021 22:45:39 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.