KImageFormats

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

KDE's Doxygen guidelines are available online.