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

source code of kimageformats/src/imageformats/ras.cpp