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 // max 256 rgb elements palette is supported
225 if (ras.ColorMapLength > 768) {
226 return false;
227 }
228 QList<quint8> palette(ras.ColorMapLength);
229 for (quint32 i = 0; i < ras.ColorMapLength; ++i) {
230 s >> palette[i];
231 if (s.status() != QDataStream::Ok) {
232 return false;
233 }
234 }
235 QList<QRgb> colorTable;
236 for (quint32 i = 0, n = ras.ColorMapLength / 3; i < n; ++i) {
237 colorTable << qRgb(palette.at(i), palette.at(i + n), palette.at(i + 2 * n));
238 }
239 for (; colorTable.size() < 256;) {
240 colorTable << qRgb(255, 255, 255);
241 }
242 img.setColorTable(colorTable);
243 }
244
245 LineDecoder dec(s.device(), ras);
246 auto bytesPerLine = std::min(img.bytesPerLine(), qsizetype(rasLineSize));
247 for (quint32 y = 0; y < ras.Height; ++y) {
248 auto rasLine = dec.readLine(rasLineSize);
249 if (rasLine.size() != rasLineSize) {
250 qWarning() << "LoadRAS() unable to read line" << y << ": the seems corrupted!";
251 return false;
252 }
253
254 // Grayscale 1-bit / Grayscale 8-bit (never seen)
255 if (ras.ColorMapType == RAS_COLOR_MAP_TYPE_NONE && (ras.Depth == 1 || ras.Depth == 8)) {
256 for (auto &&b : rasLine) {
257 b = ~b;
258 }
259 std::memcpy(img.scanLine(y), rasLine.constData(), bytesPerLine);
260 continue;
261 }
262
263 // Image with palette
264 if (ras.ColorMapType == RAS_COLOR_MAP_TYPE_RGB && (ras.Depth == 1 || ras.Depth == 8)) {
265 std::memcpy(img.scanLine(y), rasLine.constData(), bytesPerLine);
266 continue;
267 }
268
269 // BGR 24-bit
270 if (ras.ColorMapType == RAS_COLOR_MAP_TYPE_NONE && ras.Depth == 24 && (ras.Type == RAS_TYPE_STANDARD || ras.Type == RAS_TYPE_BYTE_ENCODED)) {
271 quint8 red;
272 quint8 green;
273 quint8 blue;
274 auto scanLine = reinterpret_cast<QRgb *>(img.scanLine(y));
275 for (quint32 x = 0; x < ras.Width; x++) {
276 red = rasLine.at(x * 3 + 2);
277 green = rasLine.at(x * 3 + 1);
278 blue = rasLine.at(x * 3);
279 *(scanLine + x) = qRgb(red, green, blue);
280 }
281 continue;
282 }
283
284 // RGB 24-bit
285 if (ras.ColorMapType == RAS_COLOR_MAP_TYPE_NONE && ras.Depth == 24 && ras.Type == RAS_TYPE_RGB_FORMAT) {
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(x * 3);
292 green = rasLine.at(x * 3 + 1);
293 blue = rasLine.at(x * 3 + 2);
294 *(scanLine + x) = qRgb(red, green, blue);
295 }
296 continue;
297 }
298
299 // BGR 32-bit (not tested: test case missing)
300 if (ras.ColorMapType == RAS_COLOR_MAP_TYPE_NONE && ras.Depth == 32 && (ras.Type == RAS_TYPE_STANDARD || ras.Type == RAS_TYPE_BYTE_ENCODED)) {
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(x * 4 + 3);
307 green = rasLine.at(x * 4 + 2);
308 blue = rasLine.at(x * 4 + 1);
309 *(scanLine + x) = qRgb(red, green, blue);
310 }
311
312 continue;
313 }
314
315 // RGB 32-bit (tested: test case missing due to image too large)
316 if (ras.ColorMapType == RAS_COLOR_MAP_TYPE_NONE && ras.Depth == 32 && ras.Type == RAS_TYPE_RGB_FORMAT) {
317 quint8 red;
318 quint8 green;
319 quint8 blue;
320 auto scanLine = reinterpret_cast<QRgb *>(img.scanLine(y));
321 for (quint32 x = 0; x < ras.Width; x++) {
322 red = rasLine.at(x * 4 + 1);
323 green = rasLine.at(x * 4 + 2);
324 blue = rasLine.at(x * 4 + 3);
325 *(scanLine + x) = qRgb(red, green, blue);
326 }
327 continue;
328 }
329
330 qWarning() << "LoadRAS() unsupported format!"
331 << "ColorMapType:" << ras.ColorMapType << "Type:" << ras.Type << "Depth:" << ras.Depth;
332 return false;
333 }
334
335 return true;
336}
337} // namespace
338
339class RASHandlerPrivate
340{
341public:
342 RASHandlerPrivate() {}
343 ~RASHandlerPrivate() {}
344
345 RasHeader m_header;
346};
347
348
349RASHandler::RASHandler()
351 , d(new RASHandlerPrivate)
352{
353}
354
355bool RASHandler::canRead() const
356{
357 if (canRead(device())) {
358 setFormat("ras");
359 return true;
360 }
361 return false;
362}
363
364bool RASHandler::canRead(QIODevice *device)
365{
366 if (!device) {
367 qWarning("RASHandler::canRead() called with no device");
368 return false;
369 }
370
371 if (device->isSequential()) {
372 // qWarning("Reading ras files from sequential devices not supported");
373 return false;
374 }
375
376 qint64 oldPos = device->pos();
377 QByteArray head = device->read(RasHeader::SIZE); // header is exactly 32 bytes, always FIXME
378 int readBytes = head.size(); // this should always be 32 bytes
379
380 device->seek(oldPos);
381
382 if (readBytes < RasHeader::SIZE) {
383 return false;
384 }
385
386 QDataStream stream(head);
387 stream.setByteOrder(QDataStream::BigEndian);
388 RasHeader ras;
389 stream >> ras;
390 return IsSupported(ras);
391}
392
393bool RASHandler::read(QImage *outImage)
394{
395 QDataStream s(device());
397
398 // Read image header.
399 auto&& ras = d->m_header;
400 s >> ras;
401
402 if (ras.ColorMapLength > kMaxQVectorSize) {
403 qWarning() << "LoadRAS() unsupported image color map length in file header" << ras.ColorMapLength;
404 return false;
405 }
406
407 // Check supported file types.
408 if (!IsSupported(ras)) {
409 // qDebug() << "This RAS file is not supported.";
410 return false;
411 }
412
413 QImage img;
414 bool result = LoadRAS(s, ras, img);
415
416 if (result == false) {
417 // qDebug() << "Error loading RAS file.";
418 return false;
419 }
420
421 *outImage = img;
422 return true;
423}
424
425bool RASHandler::supportsOption(ImageOption option) const
426{
427 if (option == QImageIOHandler::Size) {
428 return true;
429 }
430 if (option == QImageIOHandler::ImageFormat) {
431 return true;
432 }
433 return false;
434}
435
436QVariant RASHandler::option(ImageOption option) const
437{
438 QVariant v;
439
440 if (option == QImageIOHandler::Size) {
441 auto&& header = d->m_header;
442 if (IsSupported(header)) {
443 v = QVariant::fromValue(QSize(header.Width, header.Height));
444 }
445 else if (auto dev = device()) {
446 // transactions works on both random and sequential devices
447 dev->startTransaction();
448 auto ba = dev->read(RasHeader::SIZE);
449 dev->rollbackTransaction();
450
451 QDataStream s(ba);
453 s >> header;
454 if (s.status() == QDataStream::Ok && IsSupported(header)) {
455 v = QVariant::fromValue(QSize(header.Width, header.Height));
456 }
457 }
458 }
459
460 if (option == QImageIOHandler::ImageFormat) {
461 auto&& header = d->m_header;
462 if (IsSupported(header)) {
463 v = QVariant::fromValue(imageFormat(header));
464 }
465 else if (auto dev = device()) {
466 // transactions works on both random and sequential devices
467 dev->startTransaction();
468 auto ba = dev->read(RasHeader::SIZE);
469 dev->rollbackTransaction();
470
471 QDataStream s(ba);
473 s >> header;
474 if (s.status() == QDataStream::Ok && IsSupported(header)) {
475 v = QVariant::fromValue(imageFormat(header));
476 }
477 }
478 }
479
480 return v;
481}
482
483QImageIOPlugin::Capabilities RASPlugin::capabilities(QIODevice *device, const QByteArray &format) const
484{
485 if (format == "im1" || format == "im8" || format == "im24" || format == "im32" || format == "ras" || format == "sun") {
486 return Capabilities(CanRead);
487 }
488 if (!format.isEmpty()) {
489 return {};
490 }
491 if (!device->isOpen()) {
492 return {};
493 }
494
495 Capabilities cap;
496 if (device->isReadable() && RASHandler::canRead(device)) {
497 cap |= CanRead;
498 }
499 return cap;
500}
501
502QImageIOHandler *RASPlugin::create(QIODevice *device, const QByteArray &format) const
503{
504 QImageIOHandler *handler = new RASHandler;
505 handler->setDevice(device);
506 handler->setFormat(format);
507 return handler;
508}
509
510#include "moc_ras_p.cpp"
KCALENDARCORE_EXPORT QDataStream & operator>>(QDataStream &in, const KCalendarCore::Alarm::Ptr &)
QFlags< Capability > Capabilities
bool isEmpty() const const
QByteArray mid(qsizetype pos, qsizetype len) const const
QByteArray & remove(qsizetype pos, qsizetype len)
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)
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 Fri Oct 4 2024 12:01:33 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.