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 | |
18 | namespace // Private. |
19 | { |
20 | // format info from http://www.fileformat.info/format/sunraster/egff.htm |
21 | |
22 | // Header format of saved files. |
23 | quint32 rasMagicBigEndian = 0x59a66a95; |
24 | // quint32 rasMagicLittleEndian = 0x956aa659; # used to support wrong encoded files |
25 | |
26 | enum 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 | |
36 | enum RASColorMapType { |
37 | RAS_COLOR_MAP_TYPE_NONE = 0x0, |
38 | RAS_COLOR_MAP_TYPE_RGB = 0x1, |
39 | RAS_COLOR_MAP_TYPE_RAW = 0x2, |
40 | }; |
41 | |
42 | struct { |
43 | quint32 = 0; |
44 | quint32 = 0; |
45 | quint32 = 0; |
46 | quint32 = 0; |
47 | quint32 = 0; |
48 | quint32 = 0; |
49 | quint32 = 0; |
50 | quint32 = 0; |
51 | enum { |
52 | = 32, |
53 | }; // 8 fields of four bytes each |
54 | }; |
55 | |
56 | static QDataStream &(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 | |
77 | static bool (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 | |
99 | static QImage::Format (const RasHeader &) |
100 | { |
101 | if (header.ColorMapType == RAS_COLOR_MAP_TYPE_RGB) { |
102 | return QImage::Format_Indexed8; |
103 | } |
104 | if (header.Depth == 8 && header.ColorMapType == RAS_COLOR_MAP_TYPE_NONE) { |
105 | return QImage::Format_Grayscale8; |
106 | } |
107 | if (header.Depth == 1) { |
108 | return QImage::Format_Mono; |
109 | } |
110 | return QImage::Format_RGB32; |
111 | } |
112 | |
113 | class LineDecoder |
114 | { |
115 | public: |
116 | (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(maxlen: 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(a: device->read(maxlen: std::min(a: qint64(32768), b: 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(c: char(0x80)); |
169 | continue; |
170 | } else if (ptr >= sz) { |
171 | ptr -= 2; |
172 | break; |
173 | } |
174 | auto val = data[ptr++]; |
175 | uncBuffer.append(a: QByteArray(1 + cnt, char(val))); |
176 | } else { |
177 | uncBuffer.append(c: char(flag)); |
178 | } |
179 | } |
180 | if (ptr) { // remove consumed data |
181 | rleBuffer.remove(index: 0, len: 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(index: 0, len: size); |
190 | uncBuffer.remove(index: 0, len: line.size()); // remove consumed data |
191 | return line; |
192 | } |
193 | |
194 | private: |
195 | QIODevice *device; |
196 | RasHeader ; |
197 | |
198 | // RLE decoding buffers |
199 | QByteArray rleBuffer; |
200 | QByteArray uncBuffer; |
201 | }; |
202 | |
203 | static bool (QDataStream &s, const RasHeader &ras, QImage &img) |
204 | { |
205 | // The width of a scan line is always a multiple of 16 bits, padded when necessary. |
206 | auto rasLineSize = (qint64(ras.Width) * ras.Depth + 7) / 8; |
207 | if (rasLineSize & 1) |
208 | ++rasLineSize; |
209 | if (rasLineSize > kMaxQVectorSize) { |
210 | qWarning() << "LoadRAS() unsupported line size" << rasLineSize; |
211 | return false; |
212 | } |
213 | |
214 | // Allocate image |
215 | img = imageAlloc(width: ras.Width, height: ras.Height, format: imageFormat(header: ras)); |
216 | if (img.isNull()) { |
217 | return false; |
218 | } |
219 | |
220 | // Read palette if needed. |
221 | if (ras.ColorMapType == RAS_COLOR_MAP_TYPE_RGB) { |
222 | // max 256 rgb elements palette is supported |
223 | if (ras.ColorMapLength > 768) { |
224 | return false; |
225 | } |
226 | QList<quint8> palette(ras.ColorMapLength); |
227 | for (quint32 i = 0; i < ras.ColorMapLength; ++i) { |
228 | s >> palette[i]; |
229 | if (s.status() != QDataStream::Ok) { |
230 | return false; |
231 | } |
232 | } |
233 | QList<QRgb> colorTable; |
234 | for (quint32 i = 0, n = ras.ColorMapLength / 3; i < n; ++i) { |
235 | colorTable << qRgb(r: palette.at(i), g: palette.at(i: i + n), b: palette.at(i: i + 2 * n)); |
236 | } |
237 | for (; colorTable.size() < 256;) { |
238 | colorTable << qRgb(r: 255, g: 255, b: 255); |
239 | } |
240 | img.setColorTable(colorTable); |
241 | } |
242 | |
243 | LineDecoder dec(s.device(), ras); |
244 | auto bytesPerLine = std::min(a: img.bytesPerLine(), b: qsizetype(rasLineSize)); |
245 | for (quint32 y = 0; y < ras.Height; ++y) { |
246 | auto rasLine = dec.readLine(size: rasLineSize); |
247 | if (rasLine.size() != rasLineSize) { |
248 | qWarning() << "LoadRAS() unable to read line" << y << ": the seems corrupted!" ; |
249 | return false; |
250 | } |
251 | |
252 | // Grayscale 1-bit / Grayscale 8-bit (never seen) |
253 | if (ras.ColorMapType == RAS_COLOR_MAP_TYPE_NONE && (ras.Depth == 1 || ras.Depth == 8)) { |
254 | for (auto &&b : rasLine) { |
255 | b = ~b; |
256 | } |
257 | std::memcpy(dest: img.scanLine(y), src: rasLine.constData(), n: bytesPerLine); |
258 | continue; |
259 | } |
260 | |
261 | // Image with palette |
262 | if (ras.ColorMapType == RAS_COLOR_MAP_TYPE_RGB && (ras.Depth == 1 || ras.Depth == 8)) { |
263 | std::memcpy(dest: img.scanLine(y), src: rasLine.constData(), n: bytesPerLine); |
264 | continue; |
265 | } |
266 | |
267 | // BGR 24-bit |
268 | if (ras.ColorMapType == RAS_COLOR_MAP_TYPE_NONE && ras.Depth == 24 && (ras.Type == RAS_TYPE_STANDARD || ras.Type == RAS_TYPE_BYTE_ENCODED)) { |
269 | quint8 red; |
270 | quint8 green; |
271 | quint8 blue; |
272 | auto scanLine = reinterpret_cast<QRgb *>(img.scanLine(y)); |
273 | for (quint32 x = 0; x < ras.Width; x++) { |
274 | red = rasLine.at(i: x * 3 + 2); |
275 | green = rasLine.at(i: x * 3 + 1); |
276 | blue = rasLine.at(i: x * 3); |
277 | *(scanLine + x) = qRgb(r: red, g: green, b: blue); |
278 | } |
279 | continue; |
280 | } |
281 | |
282 | // RGB 24-bit |
283 | if (ras.ColorMapType == RAS_COLOR_MAP_TYPE_NONE && ras.Depth == 24 && ras.Type == RAS_TYPE_RGB_FORMAT) { |
284 | quint8 red; |
285 | quint8 green; |
286 | quint8 blue; |
287 | auto scanLine = reinterpret_cast<QRgb *>(img.scanLine(y)); |
288 | for (quint32 x = 0; x < ras.Width; x++) { |
289 | red = rasLine.at(i: x * 3); |
290 | green = rasLine.at(i: x * 3 + 1); |
291 | blue = rasLine.at(i: x * 3 + 2); |
292 | *(scanLine + x) = qRgb(r: red, g: green, b: blue); |
293 | } |
294 | continue; |
295 | } |
296 | |
297 | // BGR 32-bit (not tested: test case missing) |
298 | if (ras.ColorMapType == RAS_COLOR_MAP_TYPE_NONE && ras.Depth == 32 && (ras.Type == RAS_TYPE_STANDARD || ras.Type == RAS_TYPE_BYTE_ENCODED)) { |
299 | quint8 red; |
300 | quint8 green; |
301 | quint8 blue; |
302 | auto scanLine = reinterpret_cast<QRgb *>(img.scanLine(y)); |
303 | for (quint32 x = 0; x < ras.Width; x++) { |
304 | red = rasLine.at(i: x * 4 + 3); |
305 | green = rasLine.at(i: x * 4 + 2); |
306 | blue = rasLine.at(i: x * 4 + 1); |
307 | *(scanLine + x) = qRgb(r: red, g: green, b: blue); |
308 | } |
309 | |
310 | continue; |
311 | } |
312 | |
313 | // RGB 32-bit (tested: test case missing due to image too large) |
314 | if (ras.ColorMapType == RAS_COLOR_MAP_TYPE_NONE && ras.Depth == 32 && ras.Type == RAS_TYPE_RGB_FORMAT) { |
315 | quint8 red; |
316 | quint8 green; |
317 | quint8 blue; |
318 | auto scanLine = reinterpret_cast<QRgb *>(img.scanLine(y)); |
319 | for (quint32 x = 0; x < ras.Width; x++) { |
320 | red = rasLine.at(i: x * 4 + 1); |
321 | green = rasLine.at(i: x * 4 + 2); |
322 | blue = rasLine.at(i: x * 4 + 3); |
323 | *(scanLine + x) = qRgb(r: red, g: green, b: blue); |
324 | } |
325 | continue; |
326 | } |
327 | |
328 | qWarning() << "LoadRAS() unsupported format!" |
329 | << "ColorMapType:" << ras.ColorMapType << "Type:" << ras.Type << "Depth:" << ras.Depth; |
330 | return false; |
331 | } |
332 | |
333 | return true; |
334 | } |
335 | } // namespace |
336 | |
337 | class RASHandlerPrivate |
338 | { |
339 | public: |
340 | RASHandlerPrivate() {} |
341 | ~RASHandlerPrivate() {} |
342 | |
343 | RasHeader m_header; |
344 | }; |
345 | |
346 | |
347 | RASHandler::RASHandler() |
348 | : QImageIOHandler() |
349 | , d(new RASHandlerPrivate) |
350 | { |
351 | } |
352 | |
353 | bool RASHandler::canRead() const |
354 | { |
355 | if (canRead(device: device())) { |
356 | setFormat("ras" ); |
357 | return true; |
358 | } |
359 | return false; |
360 | } |
361 | |
362 | bool RASHandler::canRead(QIODevice *device) |
363 | { |
364 | if (!device) { |
365 | qWarning(msg: "RASHandler::canRead() called with no device" ); |
366 | return false; |
367 | } |
368 | |
369 | auto head = device->peek(maxlen: RasHeader::SIZE); // header is exactly 32 bytes, always FIXME |
370 | if (head.size() < RasHeader::SIZE) { |
371 | return false; |
372 | } |
373 | |
374 | QDataStream stream(head); |
375 | stream.setByteOrder(QDataStream::BigEndian); |
376 | RasHeader ras; |
377 | stream >> ras; |
378 | return IsSupported(head: ras); |
379 | } |
380 | |
381 | bool RASHandler::read(QImage *outImage) |
382 | { |
383 | QDataStream s(device()); |
384 | s.setByteOrder(QDataStream::BigEndian); |
385 | |
386 | // Read image header. |
387 | auto&& ras = d->m_header; |
388 | s >> ras; |
389 | |
390 | if (ras.ColorMapLength > kMaxQVectorSize) { |
391 | qWarning() << "LoadRAS() unsupported image color map length in file header" << ras.ColorMapLength; |
392 | return false; |
393 | } |
394 | |
395 | // Check supported file types. |
396 | if (!IsSupported(head: ras)) { |
397 | // qDebug() << "This RAS file is not supported."; |
398 | return false; |
399 | } |
400 | |
401 | QImage img; |
402 | if (!LoadRAS(s, ras, img)) { |
403 | // qDebug() << "Error loading RAS file."; |
404 | return false; |
405 | } |
406 | |
407 | *outImage = img; |
408 | return true; |
409 | } |
410 | |
411 | bool RASHandler::supportsOption(ImageOption option) const |
412 | { |
413 | if (option == QImageIOHandler::Size) { |
414 | return true; |
415 | } |
416 | if (option == QImageIOHandler::ImageFormat) { |
417 | return true; |
418 | } |
419 | return false; |
420 | } |
421 | |
422 | QVariant RASHandler::option(ImageOption option) const |
423 | { |
424 | QVariant v; |
425 | |
426 | if (option == QImageIOHandler::Size) { |
427 | auto&& = d->m_header; |
428 | if (IsSupported(head: header)) { |
429 | v = QVariant::fromValue(value: QSize(header.Width, header.Height)); |
430 | } |
431 | else if (auto dev = device()) { |
432 | QDataStream s(dev->peek(maxlen: RasHeader::SIZE)); |
433 | s.setByteOrder(QDataStream::BigEndian); |
434 | s >> header; |
435 | if (s.status() == QDataStream::Ok && IsSupported(head: header)) { |
436 | v = QVariant::fromValue(value: QSize(header.Width, header.Height)); |
437 | } |
438 | } |
439 | } |
440 | |
441 | if (option == QImageIOHandler::ImageFormat) { |
442 | auto&& = d->m_header; |
443 | if (IsSupported(head: header)) { |
444 | v = QVariant::fromValue(value: imageFormat(header)); |
445 | } |
446 | else if (auto dev = device()) { |
447 | QDataStream s(dev->peek(maxlen: RasHeader::SIZE)); |
448 | s.setByteOrder(QDataStream::BigEndian); |
449 | s >> header; |
450 | if (s.status() == QDataStream::Ok && IsSupported(head: header)) { |
451 | v = QVariant::fromValue(value: imageFormat(header)); |
452 | } |
453 | } |
454 | } |
455 | |
456 | return v; |
457 | } |
458 | |
459 | QImageIOPlugin::Capabilities RASPlugin::capabilities(QIODevice *device, const QByteArray &format) const |
460 | { |
461 | if (format == "im1" || format == "im8" || format == "im24" || format == "im32" || format == "ras" || format == "sun" ) { |
462 | return Capabilities(CanRead); |
463 | } |
464 | if (!format.isEmpty()) { |
465 | return {}; |
466 | } |
467 | if (!device->isOpen()) { |
468 | return {}; |
469 | } |
470 | |
471 | Capabilities cap; |
472 | if (device->isReadable() && RASHandler::canRead(device)) { |
473 | cap |= CanRead; |
474 | } |
475 | return cap; |
476 | } |
477 | |
478 | QImageIOHandler *RASPlugin::create(QIODevice *device, const QByteArray &format) const |
479 | { |
480 | QImageIOHandler *handler = new RASHandler; |
481 | handler->setDevice(device); |
482 | handler->setFormat(format); |
483 | return handler; |
484 | } |
485 | |
486 | #include "moc_ras_p.cpp" |
487 | |