/src/kimageformats/src/imageformats/rgb.cpp
Line | Count | Source |
1 | | /* |
2 | | kimgio module for SGI images |
3 | | SPDX-FileCopyrightText: 2004 Melchior FRANZ <mfranz@kde.org> |
4 | | |
5 | | SPDX-License-Identifier: LGPL-2.0-or-later |
6 | | */ |
7 | | |
8 | | /* this code supports: |
9 | | * reading: |
10 | | * everything, except images with 1 dimension or images with |
11 | | * mapmode != NORMAL (e.g. dithered); Images with 16 bit |
12 | | * precision or more than 4 layers are stripped down. |
13 | | * writing: |
14 | | * Run Length Encoded (RLE) or Verbatim (uncompressed) |
15 | | * (whichever is smaller) |
16 | | * |
17 | | * Please report if you come across rgb/rgba/sgi/bw files that aren't |
18 | | * recognized. Also report applications that can't deal with images |
19 | | * saved by this filter. |
20 | | */ |
21 | | |
22 | | #include "rgb_p.h" |
23 | | #include "scanlineconverter_p.h" |
24 | | #include "util_p.h" |
25 | | |
26 | | #include <cstring> |
27 | | |
28 | | #include <QColorSpace> |
29 | | #include <QImage> |
30 | | #include <QList> |
31 | | #include <QLoggingCategory> |
32 | | #include <QMap> |
33 | | |
34 | | #ifdef QT_DEBUG |
35 | | Q_LOGGING_CATEGORY(LOG_RGBPLUGIN, "kf.imageformats.plugins.rgb", QtDebugMsg) |
36 | | #else |
37 | | Q_LOGGING_CATEGORY(LOG_RGBPLUGIN, "kf.imageformats.plugins.rgb", QtWarningMsg) |
38 | | #endif |
39 | | |
40 | | class RLEData : public QList<uchar> |
41 | | { |
42 | | public: |
43 | | RLEData() |
44 | 0 | { |
45 | 0 | } |
46 | | RLEData(const uchar *d, uint l, uint o) |
47 | 0 | : _offset(o) |
48 | 0 | { |
49 | 0 | for (uint i = 0; i < l; i++) { |
50 | 0 | append(d[i]); |
51 | 0 | } |
52 | 0 | } |
53 | | bool operator<(const RLEData &) const; |
54 | | void write(QDataStream &s); |
55 | | uint offset() const |
56 | 0 | { |
57 | 0 | return _offset; |
58 | 0 | } |
59 | | |
60 | | private: |
61 | | uint _offset; |
62 | | }; |
63 | | |
64 | | class RLEMap : public QMap<RLEData, uint> |
65 | | { |
66 | | public: |
67 | | RLEMap() |
68 | 1.79k | : _counter(0) |
69 | 1.79k | , _offset(0) |
70 | 1.79k | { |
71 | 1.79k | } |
72 | | uint insert(const uchar *d, uint l); |
73 | | QList<const RLEData *> vector(); |
74 | | void setBaseOffset(uint o) |
75 | 0 | { |
76 | 0 | _offset = o; |
77 | 0 | } |
78 | | |
79 | | private: |
80 | | uint _counter; |
81 | | uint _offset; |
82 | | }; |
83 | | |
84 | | class SGIImagePrivate |
85 | | { |
86 | | public: |
87 | | SGIImagePrivate(); |
88 | | ~SGIImagePrivate(); |
89 | | |
90 | | bool readImage(QImage &); |
91 | | bool writeImage(const QImage &); |
92 | | |
93 | | bool isValid() const; |
94 | | bool isSupported() const; |
95 | | |
96 | | bool peekHeader(QIODevice *device); |
97 | | |
98 | | QSize size() const; |
99 | | QImage::Format format() const; |
100 | | |
101 | | void setDevice(QIODevice *device); |
102 | | |
103 | | private: |
104 | | enum { |
105 | | NORMAL, |
106 | | DITHERED, |
107 | | SCREEN, |
108 | | COLORMAP, |
109 | | }; // colormap |
110 | | QIODevice *_dev; |
111 | | QDataStream _stream; |
112 | | |
113 | | quint16 _magic = 0; |
114 | | quint8 _rle = 0; |
115 | | quint8 _bpc = 0; |
116 | | quint16 _dim = 0; |
117 | | quint16 _xsize = 0; |
118 | | quint16 _ysize = 0; |
119 | | quint16 _zsize = 0; |
120 | | quint32 _pixmin = 0; |
121 | | quint32 _pixmax = 0; |
122 | | char _imagename[80]; |
123 | | quint32 _colormap = 0; |
124 | | quint8 _unused[404]; |
125 | | quint32 _unused32 = 0; |
126 | | |
127 | | quint32 *_starttab; |
128 | | quint32 *_lengthtab; |
129 | | QByteArray _data; |
130 | | QByteArray::Iterator _pos; |
131 | | RLEMap _rlemap; |
132 | | QList<const RLEData *> _rlevector; |
133 | | uint _numrows; |
134 | | |
135 | | bool readData(QImage &); |
136 | | bool getRow(uchar *dest); |
137 | | bool readHeader(); |
138 | | |
139 | | static bool readHeader(QDataStream &ds, SGIImagePrivate *sgi); |
140 | | |
141 | | bool writeHeader(); |
142 | | bool writeRle(); |
143 | | bool writeVerbatim(const QImage &, const QImage::Format&, const QColorSpace&); |
144 | | bool scanData(const QImage &, const QImage::Format&, const QColorSpace&); |
145 | | uint compact(uchar *, uchar *); |
146 | | uchar intensity(uchar); |
147 | | }; |
148 | | |
149 | | SGIImagePrivate::SGIImagePrivate() |
150 | 1.79k | : _dev(nullptr) |
151 | 1.79k | , _starttab(nullptr) |
152 | 1.79k | , _lengthtab(nullptr) |
153 | 1.79k | { |
154 | 1.79k | std::memset(_imagename, 0, sizeof(_imagename)); |
155 | 1.79k | std::memset(_unused, 0, sizeof(_unused)); |
156 | 1.79k | } |
157 | | |
158 | | SGIImagePrivate::~SGIImagePrivate() |
159 | 1.79k | { |
160 | 1.79k | delete[] _starttab; |
161 | 1.79k | delete[] _lengthtab; |
162 | 1.79k | } |
163 | | |
164 | | /////////////////////////////////////////////////////////////////////////////// |
165 | | |
166 | | void SGIImagePrivate::setDevice(QIODevice *device) |
167 | 896 | { |
168 | 896 | _dev = device; |
169 | 896 | _stream.setDevice(_dev); |
170 | 896 | } |
171 | | |
172 | | bool SGIImagePrivate::getRow(uchar *dest) |
173 | 1.77M | { |
174 | 1.77M | int n; |
175 | 1.77M | int i; |
176 | 1.77M | if (!_rle) { |
177 | 6.63M | for (i = 0; i < _xsize; i++) { |
178 | 4.85M | if (_pos >= _data.end()) { |
179 | 135 | return false; |
180 | 135 | } |
181 | 4.85M | dest[i] = uchar(*_pos); |
182 | 4.85M | _pos += _bpc; |
183 | 4.85M | } |
184 | 1.77M | return true; |
185 | 1.77M | } |
186 | | |
187 | 63.0k | for (i = 0; i < _xsize;) { |
188 | 59.8k | if (_bpc == 2) { |
189 | 604 | _pos++; |
190 | 604 | } |
191 | 59.8k | if (_pos >= _data.end()) { |
192 | 76 | return false; |
193 | 76 | } |
194 | 59.8k | n = *_pos & 0x7f; |
195 | 59.8k | if (!n) { |
196 | 50 | break; |
197 | 50 | } |
198 | | |
199 | 59.7k | if (*_pos++ & 0x80) { |
200 | 82.4k | for (; i < _xsize && _pos < _data.end() && n--; i++) { |
201 | 67.1k | *dest++ = *_pos; |
202 | 67.1k | _pos += _bpc; |
203 | 67.1k | } |
204 | 44.4k | } else { |
205 | 1.65M | for (; i < _xsize && n--; i++) { |
206 | 1.60M | *dest++ = *_pos; |
207 | 1.60M | } |
208 | | |
209 | 44.4k | _pos += _bpc; |
210 | 44.4k | } |
211 | 59.7k | } |
212 | 3.25k | return i == _xsize; |
213 | 3.33k | } |
214 | | |
215 | | bool SGIImagePrivate::readData(QImage &img) |
216 | 363 | { |
217 | 363 | QRgb *c; |
218 | 363 | quint32 *start = _starttab; |
219 | 363 | QByteArray lguard(_xsize, 0); |
220 | 363 | uchar *line = (uchar *)lguard.data(); |
221 | 363 | unsigned x; |
222 | 363 | unsigned y; |
223 | | |
224 | 363 | if (!_rle) { |
225 | 173 | _pos = _data.begin(); |
226 | 173 | } |
227 | | |
228 | 668k | for (y = 0; y < _ysize; y++) { |
229 | 667k | if (_rle) { |
230 | 1.50k | _pos = _data.begin() + *start++; |
231 | 1.50k | } |
232 | 667k | if (!getRow(line)) { |
233 | 153 | return false; |
234 | 153 | } |
235 | 667k | c = reinterpret_cast<QRgb *>(img.scanLine(_ysize - y - 1)); |
236 | 2.86M | for (x = 0; x < _xsize; x++, c++) { |
237 | 2.19M | *c = qRgb(line[x], line[x], line[x]); |
238 | 2.19M | } |
239 | 667k | } |
240 | | |
241 | 210 | if (_zsize == 1) { |
242 | 18 | return true; |
243 | 18 | } |
244 | | |
245 | 192 | if (_zsize != 2) { |
246 | 580k | for (y = 0; y < _ysize; y++) { |
247 | 580k | if (_rle) { |
248 | 935 | _pos = _data.begin() + *start++; |
249 | 935 | } |
250 | 580k | if (!getRow(line)) { |
251 | 22 | return false; |
252 | 22 | } |
253 | 580k | c = reinterpret_cast<QRgb *>(img.scanLine(_ysize - y - 1)); |
254 | 2.25M | for (x = 0; x < _xsize; x++, c++) { |
255 | 1.67M | *c = qRgb(qRed(*c), line[x], line[x]); |
256 | 1.67M | } |
257 | 580k | } |
258 | | |
259 | 400k | for (y = 0; y < _ysize; y++) { |
260 | 400k | if (_rle) { |
261 | 660 | _pos = _data.begin() + *start++; |
262 | 660 | } |
263 | 400k | if (!getRow(line)) { |
264 | 36 | return false; |
265 | 36 | } |
266 | 400k | c = reinterpret_cast<QRgb *>(img.scanLine(_ysize - y - 1)); |
267 | 1.70M | for (x = 0; x < _xsize; x++, c++) { |
268 | 1.30M | *c = qRgb(qRed(*c), qGreen(*c), line[x]); |
269 | 1.30M | } |
270 | 400k | } |
271 | | |
272 | 69 | if (_zsize == 3) { |
273 | 27 | return true; |
274 | 27 | } |
275 | 69 | } |
276 | | |
277 | 130k | for (y = 0; y < _ysize; y++) { |
278 | 130k | if (_rle) { |
279 | 238 | _pos = _data.begin() + *start++; |
280 | 238 | } |
281 | 130k | if (!getRow(line)) { |
282 | 50 | return false; |
283 | 50 | } |
284 | 130k | c = reinterpret_cast<QRgb *>(img.scanLine(_ysize - y - 1)); |
285 | 1.26M | for (x = 0; x < _xsize; x++, c++) { |
286 | 1.13M | *c = qRgba(qRed(*c), qGreen(*c), qBlue(*c), line[x]); |
287 | 1.13M | } |
288 | 130k | } |
289 | | |
290 | 57 | return true; |
291 | 107 | } |
292 | | |
293 | | bool SGIImagePrivate::readImage(QImage &img) |
294 | 896 | { |
295 | 896 | if (!readHeader() || !isSupported()) { |
296 | 203 | return false; |
297 | 203 | } |
298 | | |
299 | 693 | if (_stream.atEnd()) { |
300 | 9 | return false; |
301 | 9 | } |
302 | | |
303 | 684 | if (_zsize > KIF_MAX_IMAGE_CHANNELS) { |
304 | 74 | qCDebug(LOG_RGBPLUGIN) << "Too many channels: the plugin is limited to" << KIF_MAX_IMAGE_CHANNELS << "channels"; |
305 | 74 | return false; |
306 | 74 | } |
307 | | |
308 | 610 | img = imageAlloc(size(), format()); |
309 | 610 | if (img.isNull()) { |
310 | 30 | qCWarning(LOG_RGBPLUGIN) << "Failed to allocate image, invalid dimensions?" << QSize(_xsize, _ysize); |
311 | 30 | return false; |
312 | 30 | } |
313 | | |
314 | 580 | _numrows = _ysize * _zsize; |
315 | | |
316 | 580 | if (_rle) { |
317 | 407 | uint l; |
318 | 407 | _starttab = new (std::nothrow) quint32[_numrows]; |
319 | 407 | if (_starttab == nullptr) { |
320 | 0 | return false; |
321 | 0 | } |
322 | 512k | for (l = 0; !_stream.atEnd() && l < _numrows; l++) { |
323 | 512k | _stream >> _starttab[l]; |
324 | 512k | _starttab[l] -= 512 + _numrows * 2 * sizeof(quint32); |
325 | 512k | if (_stream.status() != QDataStream::Ok) { |
326 | 54 | return false; |
327 | 54 | } |
328 | 512k | } |
329 | 16.8M | for (; l < _numrows; l++) { |
330 | 16.8M | _starttab[l] = 0; |
331 | 16.8M | } |
332 | | |
333 | 353 | _lengthtab = new (std::nothrow) quint32[_numrows]; |
334 | 353 | if (_lengthtab == nullptr) { |
335 | 0 | return false; |
336 | 0 | } |
337 | 87.5k | for (l = 0; !_stream.atEnd() && l < _numrows; l++) { |
338 | 87.2k | _stream >> _lengthtab[l]; |
339 | 87.2k | if (_stream.status() != QDataStream::Ok) { |
340 | 19 | return false; |
341 | 19 | } |
342 | 87.2k | } |
343 | 17.0M | for (; l < _numrows; l++) { |
344 | 17.0M | _lengthtab[l] = 0; |
345 | 17.0M | } |
346 | 334 | } |
347 | | |
348 | 507 | if (_stream.status() != QDataStream::Ok) { |
349 | 0 | return false; |
350 | 0 | } |
351 | | |
352 | 507 | _data = deviceRead(_dev, kMaxQVectorSize); |
353 | | |
354 | | // sanity check |
355 | 507 | if (_rle) { |
356 | 7.29k | for (uint o = 0; o < _numrows; o++) { |
357 | | // don't change to greater-or-equal! |
358 | 7.10k | if (_starttab[o] + _lengthtab[o] > (uint)_data.size()) { |
359 | | // qCDebug(LOG_RGBPLUGIN) << "image corrupt (sanity check failed)"; |
360 | 144 | return false; |
361 | 144 | } |
362 | 7.10k | } |
363 | 334 | } |
364 | | |
365 | 363 | if (!readData(img)) { |
366 | | // qCDebug(LOG_RGBPLUGIN) << "image corrupt (incomplete scanline)"; |
367 | 261 | return false; |
368 | 261 | } |
369 | | |
370 | 102 | return true; |
371 | 363 | } |
372 | | |
373 | | /////////////////////////////////////////////////////////////////////////////// |
374 | | |
375 | | void RLEData::write(QDataStream &s) |
376 | 0 | { |
377 | 0 | for (int i = 0; i < size(); i++) { |
378 | 0 | s << at(i); |
379 | 0 | } |
380 | 0 | } |
381 | | |
382 | | bool RLEData::operator<(const RLEData &b) const |
383 | 0 | { |
384 | 0 | uchar ac; |
385 | 0 | uchar bc; |
386 | 0 | for (int i = 0; i < qMin(size(), b.size()); i++) { |
387 | 0 | ac = at(i); |
388 | 0 | bc = b[i]; |
389 | 0 | if (ac != bc) { |
390 | 0 | return ac < bc; |
391 | 0 | } |
392 | 0 | } |
393 | 0 | return size() < b.size(); |
394 | 0 | } |
395 | | |
396 | | uint RLEMap::insert(const uchar *d, uint l) |
397 | 0 | { |
398 | 0 | RLEData data = RLEData(d, l, _offset); |
399 | 0 | Iterator it = find(data); |
400 | 0 | if (it != end()) { |
401 | 0 | return it.value(); |
402 | 0 | } |
403 | | |
404 | 0 | _offset += l; |
405 | 0 | return QMap<RLEData, uint>::insert(data, _counter++).value(); |
406 | 0 | } |
407 | | |
408 | | QList<const RLEData *> RLEMap::vector() |
409 | 0 | { |
410 | 0 | QList<const RLEData *> v(size()); |
411 | 0 | for (Iterator it = begin(); it != end(); ++it) { |
412 | 0 | v.replace(it.value(), &it.key()); |
413 | 0 | } |
414 | |
|
415 | 0 | return v; |
416 | 0 | } |
417 | | |
418 | | uchar SGIImagePrivate::intensity(uchar c) |
419 | 0 | { |
420 | 0 | if (c < _pixmin) { |
421 | 0 | _pixmin = c; |
422 | 0 | } |
423 | 0 | if (c > _pixmax) { |
424 | 0 | _pixmax = c; |
425 | 0 | } |
426 | 0 | return c; |
427 | 0 | } |
428 | | |
429 | | uint SGIImagePrivate::compact(uchar *d, uchar *s) |
430 | 0 | { |
431 | 0 | uchar *dest = d; |
432 | 0 | uchar *src = s; |
433 | 0 | uchar patt; |
434 | 0 | uchar *t; |
435 | 0 | uchar *end = s + _xsize; |
436 | 0 | int i; |
437 | 0 | int n; |
438 | 0 | while (src < end) { |
439 | 0 | for (n = 0, t = src; t + 2 < end && !(*t == t[1] && *t == t[2]); t++) { |
440 | 0 | n++; |
441 | 0 | } |
442 | |
|
443 | 0 | while (n) { |
444 | 0 | i = n > 126 ? 126 : n; |
445 | 0 | n -= i; |
446 | 0 | *dest++ = 0x80 | i; |
447 | 0 | while (i--) { |
448 | 0 | *dest++ = *src++; |
449 | 0 | } |
450 | 0 | } |
451 | |
|
452 | 0 | if (src == end) { |
453 | 0 | break; |
454 | 0 | } |
455 | | |
456 | 0 | patt = *src++; |
457 | 0 | for (n = 1; src < end && *src == patt; src++) { |
458 | 0 | n++; |
459 | 0 | } |
460 | |
|
461 | 0 | while (n) { |
462 | 0 | i = n > 126 ? 126 : n; |
463 | 0 | n -= i; |
464 | 0 | *dest++ = i; |
465 | 0 | *dest++ = patt; |
466 | 0 | } |
467 | 0 | } |
468 | 0 | *dest++ = 0; |
469 | 0 | return dest - d; |
470 | 0 | } |
471 | | |
472 | | bool SGIImagePrivate::scanData(const QImage &img, const QImage::Format &tfmt, const QColorSpace &tcs) |
473 | 0 | { |
474 | 0 | quint32 *start = _starttab; |
475 | 0 | QByteArray lineguard(_xsize * 2, 0); |
476 | 0 | QByteArray bufguard(_xsize, 0); |
477 | 0 | uchar *line = (uchar *)lineguard.data(); |
478 | 0 | uchar *buf = (uchar *)bufguard.data(); |
479 | 0 | const QRgb *c; |
480 | 0 | unsigned x; |
481 | 0 | unsigned y; |
482 | 0 | uint len; |
483 | |
|
484 | 0 | ScanLineConverter scl(tfmt); |
485 | 0 | scl.setTargetColorSpace(tcs); |
486 | 0 | for (y = 0; y < _ysize; y++) { |
487 | 0 | const int yPos = _ysize - y - 1; // scanline doesn't do any sanity checking |
488 | 0 | if (yPos >= img.height()) { |
489 | 0 | qCWarning(LOG_RGBPLUGIN) << "Failed to get scanline for" << yPos; |
490 | 0 | return false; |
491 | 0 | } |
492 | | |
493 | 0 | c = reinterpret_cast<const QRgb *>(scl.convertedScanLine(img, yPos)); |
494 | |
|
495 | 0 | for (x = 0; x < _xsize; x++) { |
496 | 0 | buf[x] = intensity(qRed(*c++)); |
497 | 0 | } |
498 | 0 | len = compact(line, buf); |
499 | 0 | *start++ = _rlemap.insert(line, len); |
500 | 0 | } |
501 | | |
502 | 0 | if (_zsize == 1) { |
503 | 0 | return true; |
504 | 0 | } |
505 | | |
506 | 0 | if (_zsize != 2) { |
507 | 0 | for (y = 0; y < _ysize; y++) { |
508 | 0 | const int yPos = _ysize - y - 1; |
509 | 0 | if (yPos >= img.height()) { |
510 | 0 | qCWarning(LOG_RGBPLUGIN) << "Failed to get scanline for" << yPos; |
511 | 0 | return false; |
512 | 0 | } |
513 | | |
514 | 0 | c = reinterpret_cast<const QRgb *>(scl.convertedScanLine(img, yPos)); |
515 | 0 | for (x = 0; x < _xsize; x++) { |
516 | 0 | buf[x] = intensity(qGreen(*c++)); |
517 | 0 | } |
518 | 0 | len = compact(line, buf); |
519 | 0 | *start++ = _rlemap.insert(line, len); |
520 | 0 | } |
521 | | |
522 | 0 | for (y = 0; y < _ysize; y++) { |
523 | 0 | const int yPos = _ysize - y - 1; |
524 | 0 | if (yPos >= img.height()) { |
525 | 0 | qCWarning(LOG_RGBPLUGIN) << "Failed to get scanline for" << yPos; |
526 | 0 | return false; |
527 | 0 | } |
528 | | |
529 | 0 | c = reinterpret_cast<const QRgb *>(scl.convertedScanLine(img, yPos)); |
530 | 0 | for (x = 0; x < _xsize; x++) { |
531 | 0 | buf[x] = intensity(qBlue(*c++)); |
532 | 0 | } |
533 | 0 | len = compact(line, buf); |
534 | 0 | *start++ = _rlemap.insert(line, len); |
535 | 0 | } |
536 | | |
537 | 0 | if (_zsize == 3) { |
538 | 0 | return true; |
539 | 0 | } |
540 | 0 | } |
541 | | |
542 | 0 | for (y = 0; y < _ysize; y++) { |
543 | 0 | const int yPos = _ysize - y - 1; |
544 | 0 | if (yPos >= img.height()) { |
545 | 0 | qCWarning(LOG_RGBPLUGIN) << "Failed to get scanline for" << yPos; |
546 | 0 | return false; |
547 | 0 | } |
548 | | |
549 | 0 | c = reinterpret_cast<const QRgb *>(scl.convertedScanLine(img, yPos)); |
550 | 0 | for (x = 0; x < _xsize; x++) { |
551 | 0 | buf[x] = intensity(qAlpha(*c++)); |
552 | 0 | } |
553 | 0 | len = compact(line, buf); |
554 | 0 | *start++ = _rlemap.insert(line, len); |
555 | 0 | } |
556 | | |
557 | 0 | return true; |
558 | 0 | } |
559 | | |
560 | | bool SGIImagePrivate::isValid() const |
561 | 2.34k | { |
562 | | // File signature/magic number |
563 | 2.34k | if (_magic != 0x01da) { |
564 | 54 | return false; |
565 | 54 | } |
566 | | // Compression, 0 = Uncompressed, 1 = RLE compressed |
567 | 2.29k | if (_rle > 1) { |
568 | 14 | return false; |
569 | 14 | } |
570 | | // Bytes per pixel, 1 = 8 bit, 2 = 16 bit |
571 | 2.27k | if (_bpc != 1 && _bpc != 2) { |
572 | 22 | return false; |
573 | 22 | } |
574 | | // Image dimension, 3 for RGBA image |
575 | 2.25k | if (_dim < 1 || _dim > 3) { |
576 | 38 | return false; |
577 | 38 | } |
578 | | // Number channels in the image file, 4 for RGBA image |
579 | 2.21k | if (_zsize < 1) { |
580 | 2 | return false; |
581 | 2 | } |
582 | 2.21k | return true; |
583 | 2.21k | } |
584 | | |
585 | | bool SGIImagePrivate::isSupported() const |
586 | 1.54k | { |
587 | 1.54k | if (!isValid()) { |
588 | 65 | return false; |
589 | 65 | } |
590 | 1.47k | if (_colormap != NORMAL) { |
591 | 90 | return false; // only NORMAL supported |
592 | 90 | } |
593 | 1.38k | if (_dim == 1) { |
594 | 2 | return false; |
595 | 2 | } |
596 | 1.38k | return true; |
597 | 1.38k | } |
598 | | |
599 | | bool SGIImagePrivate::peekHeader(QIODevice *device) |
600 | 896 | { |
601 | 896 | QDataStream ds(device->peek(512)); |
602 | 896 | return SGIImagePrivate::readHeader(ds, this) && isValid(); |
603 | 896 | } |
604 | | |
605 | | QSize SGIImagePrivate::size() const |
606 | 610 | { |
607 | 610 | return QSize(_xsize, _ysize); |
608 | 610 | } |
609 | | |
610 | | QImage::Format SGIImagePrivate::format() const |
611 | 610 | { |
612 | 610 | if (_zsize == 2 || _zsize == 4) { |
613 | 174 | return QImage::Format_ARGB32; |
614 | 174 | } |
615 | 436 | return QImage::Format_RGB32; |
616 | 610 | } |
617 | | |
618 | | bool SGIImagePrivate::readHeader() |
619 | 896 | { |
620 | 896 | return readHeader(_stream, this); |
621 | 896 | } |
622 | | |
623 | | bool SGIImagePrivate::readHeader(QDataStream &ds, SGIImagePrivate *sgi) |
624 | 1.79k | { |
625 | | // magic |
626 | 1.79k | ds >> sgi->_magic; |
627 | | |
628 | | // verbatim/rle |
629 | 1.79k | ds >> sgi->_rle; |
630 | | |
631 | | // bytes per channel |
632 | 1.79k | ds >> sgi->_bpc; |
633 | | |
634 | | // number of dimensions |
635 | 1.79k | ds >> sgi->_dim; |
636 | | |
637 | 1.79k | ds >> sgi->_xsize >> sgi->_ysize >> sgi->_zsize >> sgi->_pixmin >> sgi->_pixmax >> sgi->_unused32; |
638 | | |
639 | | // name |
640 | 1.79k | ds.readRawData(sgi->_imagename, 80); |
641 | 1.79k | sgi->_imagename[79] = '\0'; |
642 | | |
643 | 1.79k | ds >> sgi->_colormap; |
644 | | |
645 | 725k | for (size_t i = 0; i < sizeof(_unused); i++) { |
646 | 723k | ds >> sgi->_unused[i]; |
647 | 723k | } |
648 | | |
649 | 1.79k | return ds.status() == QDataStream::Ok; |
650 | 1.79k | } |
651 | | |
652 | | bool SGIImagePrivate::writeHeader() |
653 | 0 | { |
654 | 0 | _stream << _magic; |
655 | 0 | _stream << _rle << _bpc << _dim; |
656 | 0 | _stream << _xsize << _ysize << _zsize; |
657 | 0 | _stream << _pixmin << _pixmax; |
658 | 0 | _stream << _unused32; |
659 | |
|
660 | 0 | for (int i = 0; i < 80; i++) { |
661 | 0 | _imagename[i] = '\0'; |
662 | 0 | } |
663 | 0 | _stream.writeRawData(_imagename, 80); |
664 | |
|
665 | 0 | _stream << _colormap; |
666 | 0 | for (size_t i = 0; i < sizeof(_unused); i++) { |
667 | 0 | _stream << _unused[i]; |
668 | 0 | } |
669 | 0 | return _stream.status() == QDataStream::Ok; |
670 | 0 | } |
671 | | |
672 | | bool SGIImagePrivate::writeRle() |
673 | 0 | { |
674 | 0 | _rle = 1; |
675 | | // qCDebug(LOG_RGBPLUGIN) << "writing RLE data"; |
676 | 0 | if (!writeHeader()) { |
677 | 0 | return false; |
678 | 0 | } |
679 | | |
680 | 0 | uint i; |
681 | | |
682 | | // write start table |
683 | 0 | for (i = 0; i < _numrows; i++) { |
684 | 0 | _stream << quint32(_rlevector[_starttab[i]]->offset()); |
685 | 0 | } |
686 | | |
687 | | // write length table |
688 | 0 | for (i = 0; i < _numrows; i++) { |
689 | 0 | _stream << quint32(_rlevector[_starttab[i]]->size()); |
690 | 0 | } |
691 | | |
692 | | // write data |
693 | 0 | for (i = 0; (int)i < _rlevector.size(); i++) { |
694 | 0 | const_cast<RLEData *>(_rlevector[i])->write(_stream); |
695 | 0 | } |
696 | |
|
697 | 0 | return _stream.status() == QDataStream::Ok; |
698 | 0 | } |
699 | | |
700 | | bool SGIImagePrivate::writeVerbatim(const QImage &img, const QImage::Format &tfmt, const QColorSpace &tcs) |
701 | 0 | { |
702 | 0 | _rle = 0; |
703 | 0 | if (!writeHeader()) { |
704 | 0 | return false; |
705 | 0 | } |
706 | | |
707 | 0 | const QRgb *c; |
708 | 0 | unsigned x; |
709 | 0 | unsigned y; |
710 | |
|
711 | 0 | ScanLineConverter scl(tfmt); |
712 | 0 | scl.setTargetColorSpace(tcs); |
713 | 0 | QByteArray ba(_xsize, char()); |
714 | 0 | for (y = 0; y < _ysize; y++) { |
715 | 0 | c = reinterpret_cast<const QRgb *>(scl.convertedScanLine(img, _ysize - y - 1)); |
716 | 0 | for (x = 0; x < _xsize; x++) { |
717 | 0 | ba[x] = char(qRed(*c++)); |
718 | 0 | } |
719 | 0 | _stream.writeRawData(ba.data(), ba.size()); |
720 | 0 | } |
721 | |
|
722 | 0 | if (_zsize == 1) { |
723 | 0 | return _stream.status() == QDataStream::Ok; |
724 | 0 | } |
725 | | |
726 | 0 | if (_zsize != 2) { |
727 | 0 | for (y = 0; y < _ysize; y++) { |
728 | 0 | c = reinterpret_cast<const QRgb *>(scl.convertedScanLine(img, _ysize - y - 1)); |
729 | 0 | for (x = 0; x < _xsize; x++) { |
730 | 0 | ba[x] = char(qGreen(*c++)); |
731 | 0 | } |
732 | 0 | _stream.writeRawData(ba.data(), ba.size()); |
733 | 0 | } |
734 | |
|
735 | 0 | for (y = 0; y < _ysize; y++) { |
736 | 0 | c = reinterpret_cast<const QRgb *>(scl.convertedScanLine(img, _ysize - y - 1)); |
737 | 0 | for (x = 0; x < _xsize; x++) { |
738 | 0 | ba[x] = char(qBlue(*c++)); |
739 | 0 | } |
740 | 0 | _stream.writeRawData(ba.data(), ba.size()); |
741 | 0 | } |
742 | |
|
743 | 0 | if (_zsize == 3) { |
744 | 0 | return _stream.status() == QDataStream::Ok; |
745 | 0 | } |
746 | 0 | } |
747 | | |
748 | 0 | for (y = 0; y < _ysize; y++) { |
749 | 0 | c = reinterpret_cast<const QRgb *>(scl.convertedScanLine(img, _ysize - y - 1)); |
750 | 0 | for (x = 0; x < _xsize; x++) { |
751 | 0 | ba[x] = char(qAlpha(*c++)); |
752 | 0 | } |
753 | 0 | _stream.writeRawData(ba.data(), ba.size()); |
754 | 0 | } |
755 | |
|
756 | 0 | return _stream.status() == QDataStream::Ok; |
757 | 0 | } |
758 | | |
759 | | bool SGIImagePrivate::writeImage(const QImage &image) |
760 | 0 | { |
761 | 0 | if (image.allGray()) { |
762 | 0 | _dim = 2, _zsize = 1; |
763 | 0 | } else { |
764 | 0 | _dim = 3, _zsize = 3; |
765 | 0 | } |
766 | |
|
767 | 0 | auto hasAlpha = image.hasAlphaChannel(); |
768 | 0 | if (hasAlpha) { |
769 | 0 | _dim = 3, _zsize++; |
770 | 0 | } |
771 | |
|
772 | 0 | auto tcs = QColorSpace(); |
773 | 0 | auto tfmt = image.format(); |
774 | 0 | auto cs = image.colorSpace(); |
775 | 0 | if (cs.isValid() && cs.colorModel() == QColorSpace::ColorModel::Cmyk && tfmt == QImage::Format_CMYK8888) { |
776 | 0 | tcs = QColorSpace(QColorSpace::SRgb); |
777 | 0 | tfmt = QImage::Format_RGB32; |
778 | 0 | } else if (hasAlpha && tfmt != QImage::Format_ARGB32) { |
779 | 0 | tfmt = QImage::Format_ARGB32; |
780 | 0 | } else if (!hasAlpha && tfmt != QImage::Format_RGB32) { |
781 | 0 | tfmt = QImage::Format_RGB32; |
782 | 0 | } |
783 | |
|
784 | 0 | const int w = image.width(); |
785 | 0 | const int h = image.height(); |
786 | |
|
787 | 0 | if (w > 65535 || h > 65535) { |
788 | 0 | return false; |
789 | 0 | } |
790 | | |
791 | 0 | _magic = 0x01da; |
792 | 0 | _bpc = 1; |
793 | 0 | _xsize = w; |
794 | 0 | _ysize = h; |
795 | 0 | _pixmin = ~0u; |
796 | 0 | _pixmax = 0; |
797 | 0 | _colormap = NORMAL; |
798 | 0 | _numrows = _ysize * _zsize; |
799 | 0 | _starttab = new (std::nothrow) quint32[_numrows]; |
800 | 0 | if (_starttab == nullptr) { |
801 | 0 | return false; |
802 | 0 | } |
803 | 0 | _rlemap.setBaseOffset(512 + _numrows * 2 * sizeof(quint32)); |
804 | |
|
805 | 0 | if (!scanData(image, tfmt, tcs)) { |
806 | | // qCDebug(LOG_RGBPLUGIN) << "this can't happen"; |
807 | 0 | return false; |
808 | 0 | } |
809 | | |
810 | 0 | _rlevector = _rlemap.vector(); |
811 | |
|
812 | 0 | long verbatim_size = _numrows * _xsize; |
813 | 0 | long rle_size = _numrows * 2 * sizeof(quint32); |
814 | 0 | for (int i = 0; i < _rlevector.size(); i++) { |
815 | 0 | rle_size += _rlevector[i]->size(); |
816 | 0 | } |
817 | |
|
818 | 0 | if (verbatim_size <= rle_size) { |
819 | 0 | return writeVerbatim(image, tfmt, tcs); |
820 | 0 | } |
821 | 0 | return writeRle(); |
822 | 0 | } |
823 | | |
824 | | /////////////////////////////////////////////////////////////////////////////// |
825 | | |
826 | | RGBHandler::RGBHandler() |
827 | 896 | : QImageIOHandler() |
828 | 896 | , d(new SGIImagePrivate) |
829 | 896 | { |
830 | 896 | } |
831 | | |
832 | | bool RGBHandler::canRead() const |
833 | 896 | { |
834 | 896 | if (canRead(device())) { |
835 | 693 | setFormat("rgb"); |
836 | 693 | return true; |
837 | 693 | } |
838 | 203 | return false; |
839 | 896 | } |
840 | | |
841 | | bool RGBHandler::read(QImage *outImage) |
842 | 896 | { |
843 | 896 | d->setDevice(device()); |
844 | 896 | return d->readImage(*outImage); |
845 | 896 | } |
846 | | |
847 | | bool RGBHandler::write(const QImage &image) |
848 | 0 | { |
849 | 0 | d->setDevice(device()); |
850 | 0 | return d->writeImage(image); |
851 | 0 | } |
852 | | |
853 | | bool RGBHandler::supportsOption(ImageOption option) const |
854 | 0 | { |
855 | 0 | if (option == QImageIOHandler::Size) { |
856 | 0 | return true; |
857 | 0 | } |
858 | 0 | if (option == QImageIOHandler::ImageFormat) { |
859 | 0 | return true; |
860 | 0 | } |
861 | 0 | return false; |
862 | 0 | } |
863 | | |
864 | | QVariant RGBHandler::option(ImageOption option) const |
865 | 0 | { |
866 | 0 | QVariant v; |
867 | |
|
868 | 0 | if (option == QImageIOHandler::Size) { |
869 | 0 | auto &&sgi = d; |
870 | 0 | if (sgi->isSupported()) { |
871 | 0 | v = QVariant::fromValue(sgi->size()); |
872 | 0 | } else if (auto dev = device()) { |
873 | 0 | if (d->peekHeader(dev) && sgi->isSupported()) { |
874 | 0 | v = QVariant::fromValue(sgi->size()); |
875 | 0 | } |
876 | 0 | } |
877 | 0 | } |
878 | |
|
879 | 0 | if (option == QImageIOHandler::ImageFormat) { |
880 | 0 | auto &&sgi = d; |
881 | 0 | if (sgi->isSupported()) { |
882 | 0 | v = QVariant::fromValue(sgi->format()); |
883 | 0 | } else if (auto dev = device()) { |
884 | 0 | if (d->peekHeader(dev) && sgi->isSupported()) { |
885 | 0 | v = QVariant::fromValue(sgi->format()); |
886 | 0 | } |
887 | 0 | } |
888 | 0 | } |
889 | |
|
890 | 0 | return v; |
891 | 0 | } |
892 | | |
893 | | bool RGBHandler::canRead(QIODevice *device) |
894 | 896 | { |
895 | 896 | if (!device) { |
896 | 0 | qCWarning(LOG_RGBPLUGIN) << "RGBHandler::canRead() called with no device"; |
897 | 0 | return false; |
898 | 0 | } |
899 | | |
900 | 896 | SGIImagePrivate sgi; |
901 | 896 | return sgi.peekHeader(device) && sgi.isSupported(); |
902 | 896 | } |
903 | | |
904 | | /////////////////////////////////////////////////////////////////////////////// |
905 | | |
906 | | QImageIOPlugin::Capabilities RGBPlugin::capabilities(QIODevice *device, const QByteArray &format) const |
907 | 0 | { |
908 | 0 | if (format == "rgb" || format == "rgba" || format == "bw" || format == "sgi") { |
909 | 0 | return Capabilities(CanRead | CanWrite); |
910 | 0 | } |
911 | 0 | if (!format.isEmpty()) { |
912 | 0 | return {}; |
913 | 0 | } |
914 | 0 | if (!device->isOpen()) { |
915 | 0 | return {}; |
916 | 0 | } |
917 | | |
918 | 0 | Capabilities cap; |
919 | 0 | if (device->isReadable() && RGBHandler::canRead(device)) { |
920 | 0 | cap |= CanRead; |
921 | 0 | } |
922 | 0 | if (device->isWritable()) { |
923 | 0 | cap |= CanWrite; |
924 | 0 | } |
925 | 0 | return cap; |
926 | 0 | } |
927 | | |
928 | | QImageIOHandler *RGBPlugin::create(QIODevice *device, const QByteArray &format) const |
929 | 0 | { |
930 | 0 | QImageIOHandler *handler = new RGBHandler; |
931 | 0 | handler->setDevice(device); |
932 | 0 | handler->setFormat(format); |
933 | 0 | return handler; |
934 | 0 | } |
935 | | |
936 | | #include "moc_rgb_p.cpp" |