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 | s.device()->seek(pos: 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(width: ras.Width, height: ras.Height, format: imageFormat(header: 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(r: palette.at(i), g: palette.at(i: i + n), b: palette.at(i: i + 2 * n)); |
231 | } |
232 | for (; colorTable.size() < 256;) { |
233 | colorTable << qRgb(r: 255, g: 255, b: 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(a: img.bytesPerLine(), b: qsizetype(rasLineSize)); |
243 | for (quint32 y = 0; y < ras.Height; ++y) { |
244 | auto rasLine = dec.readLine(size: 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(dest: img.scanLine(y), src: rasLine.constData(), n: 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(dest: img.scanLine(y), src: rasLine.constData(), n: 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(i: x * 3 + 2); |
273 | green = rasLine.at(i: x * 3 + 1); |
274 | blue = rasLine.at(i: x * 3); |
275 | *(scanLine + x) = qRgb(r: red, g: green, b: 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(i: x * 3); |
288 | green = rasLine.at(i: x * 3 + 1); |
289 | blue = rasLine.at(i: x * 3 + 2); |
290 | *(scanLine + x) = qRgb(r: red, g: green, b: 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(i: x * 4 + 3); |
303 | green = rasLine.at(i: x * 4 + 2); |
304 | blue = rasLine.at(i: x * 4 + 1); |
305 | *(scanLine + x) = qRgb(r: red, g: green, b: 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(i: x * 4 + 1); |
319 | green = rasLine.at(i: x * 4 + 2); |
320 | blue = rasLine.at(i: x * 4 + 3); |
321 | *(scanLine + x) = qRgb(r: red, g: green, b: 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 | |
335 | RASHandler::RASHandler() |
336 | { |
337 | } |
338 | |
339 | bool RASHandler::canRead() const |
340 | { |
341 | if (canRead(device: device())) { |
342 | setFormat("ras" ); |
343 | return true; |
344 | } |
345 | return false; |
346 | } |
347 | |
348 | bool RASHandler::canRead(QIODevice *device) |
349 | { |
350 | if (!device) { |
351 | qWarning(msg: "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(maxlen: RasHeader::SIZE); // header is exactly 32 bytes, always FIXME |
362 | int readBytes = head.size(); // this should always be 32 bytes |
363 | |
364 | device->seek(pos: 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(head: ras); |
375 | } |
376 | |
377 | bool RASHandler::read(QImage *outImage) |
378 | { |
379 | QDataStream s(device()); |
380 | s.setByteOrder(QDataStream::BigEndian); |
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(head: 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 | |
409 | bool 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 | |
420 | QVariant 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(maxlen: RasHeader::SIZE); |
429 | d->rollbackTransaction(); |
430 | |
431 | QDataStream s(ba); |
432 | s.setByteOrder(QDataStream::BigEndian); |
433 | |
434 | RasHeader ; |
435 | s >> header; |
436 | |
437 | if (s.status() == QDataStream::Ok && IsSupported(head: header)) { |
438 | v = QVariant::fromValue(value: 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(maxlen: RasHeader::SIZE); |
448 | d->rollbackTransaction(); |
449 | |
450 | QDataStream s(ba); |
451 | s.setByteOrder(QDataStream::BigEndian); |
452 | |
453 | RasHeader ; |
454 | s >> header; |
455 | |
456 | if (s.status() == QDataStream::Ok && IsSupported(head: header)) { |
457 | v = QVariant::fromValue(value: imageFormat(header)); |
458 | } |
459 | } |
460 | } |
461 | |
462 | return v; |
463 | } |
464 | |
465 | QImageIOPlugin::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 | |
484 | QImageIOHandler *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" |
493 | |