KImageFormats

ras.cpp
1/*
2 This file is part of the KDE project
3 SPDX-FileCopyrightText: 2003 Dominik Seichter <domseichter@web.de>
4 SPDX-FileCopyrightText: 2004 Ignacio CastaƱo <castano@ludicon.com>
5 SPDX-FileCopyrightText: 2010 Troy Unrau <troy@kde.org>
6 SPDX-FileCopyrightText: 2023 Mirco Miranda <mircomir@outlook.com>
7
8 SPDX-License-Identifier: LGPL-2.0-or-later
9*/
10
11#include "ras_p.h"
12#include "util_p.h"
13
14#include <QDataStream>
15#include <QDebug>
16#include <QImage>
17
18namespace // Private.
19{
20// format info from http://www.fileformat.info/format/sunraster/egff.htm
21
22// Header format of saved files.
23quint32 rasMagicBigEndian = 0x59a66a95;
24// quint32 rasMagicLittleEndian = 0x956aa659; # used to support wrong encoded files
25
26enum RASType {
27 RAS_TYPE_OLD = 0x0,
28 RAS_TYPE_STANDARD = 0x1,
29 RAS_TYPE_BYTE_ENCODED = 0x2,
30 RAS_TYPE_RGB_FORMAT = 0x3,
31 RAS_TYPE_TIFF_FORMAT = 0x4,
32 RAS_TYPE_IFF_FORMAT = 0x5,
33 RAS_TYPE_EXPERIMENTAL = 0xFFFF,
34};
35
36enum RASColorMapType {
37 RAS_COLOR_MAP_TYPE_NONE = 0x0,
38 RAS_COLOR_MAP_TYPE_RGB = 0x1,
39 RAS_COLOR_MAP_TYPE_RAW = 0x2,
40};
41
42struct RasHeader {
43 quint32 MagicNumber = 0;
44 quint32 Width = 0;
45 quint32 Height = 0;
46 quint32 Depth = 0;
47 quint32 Length = 0;
48 quint32 Type = 0;
49 quint32 ColorMapType = 0;
50 quint32 ColorMapLength = 0;
51 enum {
52 SIZE = 32,
53 }; // 8 fields of four bytes each
54};
55
56static QDataStream &operator>>(QDataStream &s, RasHeader &head)
57{
58 s >> head.MagicNumber;
59 s >> head.Width;
60 s >> head.Height;
61 s >> head.Depth;
62 s >> head.Length;
63 s >> head.Type;
64 s >> head.ColorMapType;
65 s >> head.ColorMapLength;
66 /*qDebug() << "MagicNumber: " << head.MagicNumber
67 << "Width: " << head.Width
68 << "Height: " << head.Height
69 << "Depth: " << head.Depth
70 << "Length: " << head.Length
71 << "Type: " << head.Type
72 << "ColorMapType: " << head.ColorMapType
73 << "ColorMapLength: " << head.ColorMapLength;*/
74 return s;
75}
76
77static bool IsSupported(const RasHeader &head)
78{
79 // check magic number
80 if (head.MagicNumber != rasMagicBigEndian) {
81 return false;
82 }
83 // check for an appropriate depth
84 if (head.Depth != 1 && head.Depth != 8 && head.Depth != 24 && head.Depth != 32) {
85 return false;
86 }
87 if (head.Width == 0 || head.Height == 0) {
88 return false;
89 }
90 // the Type field adds support for RLE(BGR), RGB and other encodings
91 // we support Type 1: Normal(BGR), Type 2: RLE(BGR) and Type 3: Normal(RGB) ONLY!
92 // TODO: add support for Type 4,5: TIFF/IFF
93 if (!(head.Type == RAS_TYPE_STANDARD || head.Type == RAS_TYPE_RGB_FORMAT || head.Type == RAS_TYPE_BYTE_ENCODED)) {
94 return false;
95 }
96 return true;
97}
98
99static QImage::Format imageFormat(const RasHeader &header)
100{
101 if (header.ColorMapType == RAS_COLOR_MAP_TYPE_RGB) {
103 }
104 if (header.Depth == 8 && header.ColorMapType == RAS_COLOR_MAP_TYPE_NONE) {
106 }
107 if (header.Depth == 1) {
108 return QImage::Format_Mono;
109 }
111}
112
113class LineDecoder
114{
115public:
116 LineDecoder(QIODevice *d, const RasHeader &ras)
117 : device(d)
118 , header(ras)
119 {
120 }
121
122 QByteArray readLine(qint64 size)
123 {
124 /* *** uncompressed
125 */
126 if (header.Type != RAS_TYPE_BYTE_ENCODED) {
127 return device->read(size);
128 }
129
130 /* *** rle compressed
131 * The Run-length encoding (RLE) scheme optionally used in Sun Raster
132 * files (Type = 0002h) is used to encode bytes of image data
133 * separately. RLE encoding may be found in any Sun Raster file
134 * regardless of the type of image data it contains.
135 *
136 * The RLE packets are typically three bytes in size:
137 * - The first byte is a Flag Value indicating the type of RLE packet.
138 * - The second byte is the Run Count.
139 * - The third byte is the Run Value.
140 *
141 * A Flag Value of 80h is followed by a Run Count in the range of 01h
142 * to FFh. The Run Value follows the Run count and is in the range of
143 * 00h to FFh. The pixel run is the Run Value repeated Run Count times.
144 * There are two exceptions to this algorithm. First, if the Run Count
145 * following the Flag Value is 00h, this is an indication that the run
146 * is a single byte in length and has a value of 80h. And second, if
147 * the Flag Value is not 80h, then it is assumed that the data is
148 * unencoded pixel data and is written directly to the output stream.
149 *
150 * source: http://www.fileformat.info/format/sunraster/egff.htm
151 */
152 for (qsizetype psz = 0, ptr = 0; uncBuffer.size() < size;) {
153 rleBuffer.append(device->read(std::min(qint64(32768), size)));
154 qsizetype sz = rleBuffer.size();
155 if (psz == sz) {
156 break; // avoid infinite loop (data corrupted?!)
157 }
158 auto data = reinterpret_cast<uchar *>(rleBuffer.data());
159 for (; ptr < sz;) {
160 auto flag = data[ptr++];
161 if (flag == 0x80) {
162 if (ptr >= sz) {
163 ptr -= 1;
164 break;
165 }
166 auto cnt = data[ptr++];
167 if (cnt == 0) {
168 uncBuffer.append(char(0x80));
169 continue;
170 } else if (ptr >= sz) {
171 ptr -= 2;
172 break;
173 }
174 auto val = data[ptr++];
175 uncBuffer.append(QByteArray(1 + cnt, char(val)));
176 } else {
177 uncBuffer.append(char(flag));
178 }
179 }
180 if (ptr) { // remove consumed data
181 rleBuffer.remove(0, ptr);
182 ptr = 0;
183 }
184 psz = rleBuffer.size();
185 }
186 if (uncBuffer.size() < size) {
187 return QByteArray(); // something wrong
188 }
189 auto line = uncBuffer.mid(0, size);
190 uncBuffer.remove(0, line.size()); // remove consumed data
191 return line;
192 }
193
194private:
195 QIODevice *device;
196 RasHeader header;
197
198 // RLE decoding buffers
199 QByteArray rleBuffer;
200 QByteArray uncBuffer;
201};
202
203static bool LoadRAS(QDataStream &s, const RasHeader &ras, QImage &img)
204{
205 s.device()->seek(RasHeader::SIZE);
206
207 // The width of a scan line is always a multiple of 16 bits, padded when necessary.
208 auto rasLineSize = (qint64(ras.Width) * ras.Depth + 7) / 8;
209 if (rasLineSize & 1)
210 ++rasLineSize;
211 if (rasLineSize > kMaxQVectorSize) {
212 qWarning() << "LoadRAS() unsupported line size" << rasLineSize;
213 return false;
214 }
215
216 // Allocate image
217 img = imageAlloc(ras.Width, ras.Height, imageFormat(ras));
218 if (img.isNull()) {
219 return false;
220 }
221
222 // Read palette if needed.
223 if (ras.ColorMapType == RAS_COLOR_MAP_TYPE_RGB) {
224 QList<quint8> palette(ras.ColorMapLength);
225 for (quint32 i = 0; i < ras.ColorMapLength; ++i) {
226 s >> palette[i];
227 }
228 QList<QRgb> colorTable;
229 for (quint32 i = 0, n = ras.ColorMapLength / 3; i < n; ++i) {
230 colorTable << qRgb(palette.at(i), palette.at(i + n), palette.at(i + 2 * n));
231 }
232 for (; colorTable.size() < 256;) {
233 colorTable << qRgb(255, 255, 255);
234 }
235 img.setColorTable(colorTable);
236 if (s.status() != QDataStream::Ok) {
237 return false;
238 }
239 }
240
241 LineDecoder dec(s.device(), ras);
242 auto bytesPerLine = std::min(img.bytesPerLine(), qsizetype(rasLineSize));
243 for (quint32 y = 0; y < ras.Height; ++y) {
244 auto rasLine = dec.readLine(rasLineSize);
245 if (rasLine.size() != rasLineSize) {
246 qWarning() << "LoadRAS() unable to read line" << y << ": the seems corrupted!";
247 return false;
248 }
249
250 // Grayscale 1-bit / Grayscale 8-bit (never seen)
251 if (ras.ColorMapType == RAS_COLOR_MAP_TYPE_NONE && (ras.Depth == 1 || ras.Depth == 8)) {
252 for (auto &&b : rasLine) {
253 b = ~b;
254 }
255 std::memcpy(img.scanLine(y), rasLine.constData(), bytesPerLine);
256 continue;
257 }
258
259 // Image with palette
260 if (ras.ColorMapType == RAS_COLOR_MAP_TYPE_RGB && (ras.Depth == 1 || ras.Depth == 8)) {
261 std::memcpy(img.scanLine(y), rasLine.constData(), bytesPerLine);
262 continue;
263 }
264
265 // BGR 24-bit
266 if (ras.ColorMapType == RAS_COLOR_MAP_TYPE_NONE && ras.Depth == 24 && (ras.Type == RAS_TYPE_STANDARD || ras.Type == RAS_TYPE_BYTE_ENCODED)) {
267 quint8 red;
268 quint8 green;
269 quint8 blue;
270 auto scanLine = reinterpret_cast<QRgb *>(img.scanLine(y));
271 for (quint32 x = 0; x < ras.Width; x++) {
272 red = rasLine.at(x * 3 + 2);
273 green = rasLine.at(x * 3 + 1);
274 blue = rasLine.at(x * 3);
275 *(scanLine + x) = qRgb(red, green, blue);
276 }
277 continue;
278 }
279
280 // RGB 24-bit
281 if (ras.ColorMapType == RAS_COLOR_MAP_TYPE_NONE && ras.Depth == 24 && ras.Type == RAS_TYPE_RGB_FORMAT) {
282 quint8 red;
283 quint8 green;
284 quint8 blue;
285 auto scanLine = reinterpret_cast<QRgb *>(img.scanLine(y));
286 for (quint32 x = 0; x < ras.Width; x++) {
287 red = rasLine.at(x * 3);
288 green = rasLine.at(x * 3 + 1);
289 blue = rasLine.at(x * 3 + 2);
290 *(scanLine + x) = qRgb(red, green, blue);
291 }
292 continue;
293 }
294
295 // BGR 32-bit (not tested: test case missing)
296 if (ras.ColorMapType == RAS_COLOR_MAP_TYPE_NONE && ras.Depth == 32 && (ras.Type == RAS_TYPE_STANDARD || ras.Type == RAS_TYPE_BYTE_ENCODED)) {
297 quint8 red;
298 quint8 green;
299 quint8 blue;
300 auto scanLine = reinterpret_cast<QRgb *>(img.scanLine(y));
301 for (quint32 x = 0; x < ras.Width; x++) {
302 red = rasLine.at(x * 4 + 3);
303 green = rasLine.at(x * 4 + 2);
304 blue = rasLine.at(x * 4 + 1);
305 *(scanLine + x) = qRgb(red, green, blue);
306 }
307
308 continue;
309 }
310
311 // RGB 32-bit (tested: test case missing due to image too large)
312 if (ras.ColorMapType == RAS_COLOR_MAP_TYPE_NONE && ras.Depth == 32 && ras.Type == RAS_TYPE_RGB_FORMAT) {
313 quint8 red;
314 quint8 green;
315 quint8 blue;
316 auto scanLine = reinterpret_cast<QRgb *>(img.scanLine(y));
317 for (quint32 x = 0; x < ras.Width; x++) {
318 red = rasLine.at(x * 4 + 1);
319 green = rasLine.at(x * 4 + 2);
320 blue = rasLine.at(x * 4 + 3);
321 *(scanLine + x) = qRgb(red, green, blue);
322 }
323 continue;
324 }
325
326 qWarning() << "LoadRAS() unsupported format!"
327 << "ColorMapType:" << ras.ColorMapType << "Type:" << ras.Type << "Depth:" << ras.Depth;
328 return false;
329 }
330
331 return true;
332}
333} // namespace
334
335RASHandler::RASHandler()
336{
337}
338
339bool RASHandler::canRead() const
340{
341 if (canRead(device())) {
342 setFormat("ras");
343 return true;
344 }
345 return false;
346}
347
348bool RASHandler::canRead(QIODevice *device)
349{
350 if (!device) {
351 qWarning("RASHandler::canRead() called with no device");
352 return false;
353 }
354
355 if (device->isSequential()) {
356 // qWarning("Reading ras files from sequential devices not supported");
357 return false;
358 }
359
360 qint64 oldPos = device->pos();
361 QByteArray head = device->read(RasHeader::SIZE); // header is exactly 32 bytes, always FIXME
362 int readBytes = head.size(); // this should always be 32 bytes
363
364 device->seek(oldPos);
365
366 if (readBytes < RasHeader::SIZE) {
367 return false;
368 }
369
370 QDataStream stream(head);
371 stream.setByteOrder(QDataStream::BigEndian);
372 RasHeader ras;
373 stream >> ras;
374 return IsSupported(ras);
375}
376
377bool RASHandler::read(QImage *outImage)
378{
379 QDataStream s(device());
381
382 // Read image header.
383 RasHeader ras;
384 s >> ras;
385
386 if (ras.ColorMapLength > kMaxQVectorSize) {
387 qWarning() << "LoadRAS() unsupported image color map length in file header" << ras.ColorMapLength;
388 return false;
389 }
390
391 // Check supported file types.
392 if (!IsSupported(ras)) {
393 // qDebug() << "This RAS file is not supported.";
394 return false;
395 }
396
397 QImage img;
398 bool result = LoadRAS(s, ras, img);
399
400 if (result == false) {
401 // qDebug() << "Error loading RAS file.";
402 return false;
403 }
404
405 *outImage = img;
406 return true;
407}
408
409bool RASHandler::supportsOption(ImageOption option) const
410{
411 if (option == QImageIOHandler::Size) {
412 return true;
413 }
414 if (option == QImageIOHandler::ImageFormat) {
415 return true;
416 }
417 return false;
418}
419
420QVariant RASHandler::option(ImageOption option) const
421{
422 QVariant v;
423
424 if (option == QImageIOHandler::Size) {
425 if (auto d = device()) {
426 // transactions works on both random and sequential devices
427 d->startTransaction();
428 auto ba = d->read(RasHeader::SIZE);
429 d->rollbackTransaction();
430
431 QDataStream s(ba);
433
434 RasHeader header;
435 s >> header;
436
437 if (s.status() == QDataStream::Ok && IsSupported(header)) {
438 v = QVariant::fromValue(QSize(header.Width, header.Height));
439 }
440 }
441 }
442
443 if (option == QImageIOHandler::ImageFormat) {
444 if (auto d = device()) {
445 // transactions works on both random and sequential devices
446 d->startTransaction();
447 auto ba = d->read(RasHeader::SIZE);
448 d->rollbackTransaction();
449
450 QDataStream s(ba);
452
453 RasHeader header;
454 s >> header;
455
456 if (s.status() == QDataStream::Ok && IsSupported(header)) {
457 v = QVariant::fromValue(imageFormat(header));
458 }
459 }
460 }
461
462 return v;
463}
464
465QImageIOPlugin::Capabilities RASPlugin::capabilities(QIODevice *device, const QByteArray &format) const
466{
467 if (format == "im1" || format == "im8" || format == "im24" || format == "im32" || format == "ras" || format == "sun") {
468 return Capabilities(CanRead);
469 }
470 if (!format.isEmpty()) {
471 return {};
472 }
473 if (!device->isOpen()) {
474 return {};
475 }
476
477 Capabilities cap;
478 if (device->isReadable() && RASHandler::canRead(device)) {
479 cap |= CanRead;
480 }
481 return cap;
482}
483
484QImageIOHandler *RASPlugin::create(QIODevice *device, const QByteArray &format) const
485{
486 QImageIOHandler *handler = new RASHandler;
487 handler->setDevice(device);
488 handler->setFormat(format);
489 return handler;
490}
491
492#include "moc_ras_p.cpp"
KCALENDARCORE_EXPORT QDataStream & operator>>(QDataStream &in, const KCalendarCore::Alarm::Ptr &)
QFlags< Capability > Capabilities
QByteArray & append(QByteArrayView data)
bool isEmpty() const const
qsizetype size() const const
QIODevice * device() const const
void setByteOrder(ByteOrder bo)
Status status() const const
qsizetype bytesPerLine() const const
bool isNull() const const
uchar * scanLine(int i)
void setColorTable(const QList< QRgb > &colors)
void setDevice(QIODevice *device)
void setFormat(const QByteArray &format)
typedef Capabilities
bool isOpen() const const
bool isReadable() const const
virtual bool isSequential() const const
virtual qint64 pos() const const
QByteArray read(qint64 maxSize)
virtual bool seek(qint64 pos)
qsizetype size() const const
QTextStream & dec(QTextStream &stream)
QString readLine(qint64 maxlen)
QVariant fromValue(T &&value)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:12:40 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.