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 "util_p.h"
24
25#include <QList>
26#include <QMap>
27
28#include <QDebug>
29#include <QImage>
30
31class RLEData : public QList<uchar>
32{
33public:
34 RLEData()
35 {
36 }
37 RLEData(const uchar *d, uint l, uint o)
38 : _offset(o)
39 {
40 for (uint i = 0; i < l; i++) {
41 append(t: d[i]);
42 }
43 }
44 bool operator<(const RLEData &) const;
45 void write(QDataStream &s);
46 uint offset() const
47 {
48 return _offset;
49 }
50
51private:
52 uint _offset;
53};
54
55class RLEMap : public QMap<RLEData, uint>
56{
57public:
58 RLEMap()
59 : _counter(0)
60 , _offset(0)
61 {
62 }
63 uint insert(const uchar *d, uint l);
64 QList<const RLEData *> vector();
65 void setBaseOffset(uint o)
66 {
67 _offset = o;
68 }
69
70private:
71 uint _counter;
72 uint _offset;
73};
74
75class SGIImage
76{
77public:
78 SGIImage(QIODevice *device);
79 ~SGIImage();
80
81 bool readImage(QImage &);
82 bool writeImage(const QImage &);
83
84private:
85 enum {
86 NORMAL,
87 DITHERED,
88 SCREEN,
89 COLORMAP,
90 }; // colormap
91 QIODevice *_dev;
92 QDataStream _stream;
93
94 quint8 _rle;
95 quint8 _bpc;
96 quint16 _dim;
97 quint16 _xsize;
98 quint16 _ysize;
99 quint16 _zsize;
100 quint32 _pixmin;
101 quint32 _pixmax;
102 char _imagename[80];
103 quint32 _colormap;
104
105 quint32 *_starttab;
106 quint32 *_lengthtab;
107 QByteArray _data;
108 QByteArray::Iterator _pos;
109 RLEMap _rlemap;
110 QList<const RLEData *> _rlevector;
111 uint _numrows;
112
113 bool readData(QImage &);
114 bool getRow(uchar *dest);
115
116 void writeHeader();
117 void writeRle();
118 void writeVerbatim(const QImage &);
119 bool scanData(const QImage &);
120 uint compact(uchar *, uchar *);
121 uchar intensity(uchar);
122};
123
124SGIImage::SGIImage(QIODevice *io)
125 : _starttab(nullptr)
126 , _lengthtab(nullptr)
127{
128 _dev = io;
129 _stream.setDevice(_dev);
130}
131
132SGIImage::~SGIImage()
133{
134 delete[] _starttab;
135 delete[] _lengthtab;
136}
137
138///////////////////////////////////////////////////////////////////////////////
139
140bool SGIImage::getRow(uchar *dest)
141{
142 int n;
143 int i;
144 if (!_rle) {
145 for (i = 0; i < _xsize; i++) {
146 if (_pos >= _data.end()) {
147 return false;
148 }
149 dest[i] = uchar(*_pos);
150 _pos += _bpc;
151 }
152 return true;
153 }
154
155 for (i = 0; i < _xsize;) {
156 if (_bpc == 2) {
157 _pos++;
158 }
159 if (_pos >= _data.end()) {
160 return false;
161 }
162 n = *_pos & 0x7f;
163 if (!n) {
164 break;
165 }
166
167 if (*_pos++ & 0x80) {
168 for (; i < _xsize && _pos < _data.end() && n--; i++) {
169 *dest++ = *_pos;
170 _pos += _bpc;
171 }
172 } else {
173 for (; i < _xsize && n--; i++) {
174 *dest++ = *_pos;
175 }
176
177 _pos += _bpc;
178 }
179 }
180 return i == _xsize;
181}
182
183bool SGIImage::readData(QImage &img)
184{
185 QRgb *c;
186 quint32 *start = _starttab;
187 QByteArray lguard(_xsize, 0);
188 uchar *line = (uchar *)lguard.data();
189 unsigned x;
190 unsigned y;
191
192 if (!_rle) {
193 _pos = _data.begin();
194 }
195
196 for (y = 0; y < _ysize; y++) {
197 if (_rle) {
198 _pos = _data.begin() + *start++;
199 }
200 if (!getRow(dest: line)) {
201 return false;
202 }
203 c = (QRgb *)img.scanLine(_ysize - y - 1);
204 for (x = 0; x < _xsize; x++, c++) {
205 *c = qRgb(r: line[x], g: line[x], b: line[x]);
206 }
207 }
208
209 if (_zsize == 1) {
210 return true;
211 }
212
213 if (_zsize != 2) {
214 for (y = 0; y < _ysize; y++) {
215 if (_rle) {
216 _pos = _data.begin() + *start++;
217 }
218 if (!getRow(dest: line)) {
219 return false;
220 }
221 c = (QRgb *)img.scanLine(_ysize - y - 1);
222 for (x = 0; x < _xsize; x++, c++) {
223 *c = qRgb(r: qRed(rgb: *c), g: line[x], b: line[x]);
224 }
225 }
226
227 for (y = 0; y < _ysize; y++) {
228 if (_rle) {
229 _pos = _data.begin() + *start++;
230 }
231 if (!getRow(dest: line)) {
232 return false;
233 }
234 c = (QRgb *)img.scanLine(_ysize - y - 1);
235 for (x = 0; x < _xsize; x++, c++) {
236 *c = qRgb(r: qRed(rgb: *c), g: qGreen(rgb: *c), b: line[x]);
237 }
238 }
239
240 if (_zsize == 3) {
241 return true;
242 }
243 }
244
245 for (y = 0; y < _ysize; y++) {
246 if (_rle) {
247 _pos = _data.begin() + *start++;
248 }
249 if (!getRow(dest: line)) {
250 return false;
251 }
252 c = (QRgb *)img.scanLine(_ysize - y - 1);
253 for (x = 0; x < _xsize; x++, c++) {
254 *c = qRgba(r: qRed(rgb: *c), g: qGreen(rgb: *c), b: qBlue(rgb: *c), a: line[x]);
255 }
256 }
257
258 return true;
259}
260
261bool SGIImage::readImage(QImage &img)
262{
263 qint8 u8;
264 qint16 u16;
265 qint32 u32;
266
267 // qDebug() << "reading rgb ";
268
269 // magic
270 _stream >> u16;
271 if (u16 != 0x01da) {
272 return false;
273 }
274
275 // verbatim/rle
276 _stream >> _rle;
277 // qDebug() << (_rle ? "RLE" : "verbatim");
278 if (_rle > 1) {
279 return false;
280 }
281
282 // bytes per channel
283 _stream >> _bpc;
284 // qDebug() << "bytes per channel: " << int(_bpc);
285 if (_bpc == 1) {
286 ;
287 } else if (_bpc == 2) {
288 // qDebug() << "dropping least significant byte";
289 } else {
290 return false;
291 }
292
293 // number of dimensions
294 _stream >> _dim;
295 // qDebug() << "dimensions: " << _dim;
296 if (_dim < 1 || _dim > 3) {
297 return false;
298 }
299
300 _stream >> _xsize >> _ysize >> _zsize >> _pixmin >> _pixmax >> u32;
301 // qDebug() << "x: " << _xsize;
302 // qDebug() << "y: " << _ysize;
303 // qDebug() << "z: " << _zsize;
304
305 // name
306 _stream.readRawData(_imagename, len: 80);
307 _imagename[79] = '\0';
308
309 _stream >> _colormap;
310 // qDebug() << "colormap: " << _colormap;
311 if (_colormap != NORMAL) {
312 return false; // only NORMAL supported
313 }
314
315 for (int i = 0; i < 404; i++) {
316 _stream >> u8;
317 }
318
319 if (_dim == 1) {
320 // qDebug() << "1-dimensional images aren't supported yet";
321 return false;
322 }
323
324 if (_stream.atEnd()) {
325 return false;
326 }
327
328 img = imageAlloc(width: _xsize, height: _ysize, format: QImage::Format_RGB32);
329 if (img.isNull()) {
330 qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(_xsize, _ysize);
331 return false;
332 }
333
334 if (_zsize == 0) {
335 return false;
336 }
337
338 if (_zsize == 2 || _zsize == 4) {
339 img = img.convertToFormat(f: QImage::Format_ARGB32);
340 } else if (_zsize > 4) {
341 // qDebug() << "using first 4 of " << _zsize << " channels";
342 // Only let this continue if it won't cause a int overflow later
343 // this is most likely a broken file anyway
344 if (_ysize > std::numeric_limits<int>::max() / _zsize) {
345 return false;
346 }
347 }
348
349 _numrows = _ysize * _zsize;
350
351 if (_rle) {
352 uint l;
353 _starttab = new quint32[_numrows];
354 for (l = 0; !_stream.atEnd() && l < _numrows; l++) {
355 _stream >> _starttab[l];
356 _starttab[l] -= 512 + _numrows * 2 * sizeof(quint32);
357 }
358 for (; l < _numrows; l++) {
359 _starttab[l] = 0;
360 }
361
362 _lengthtab = new quint32[_numrows];
363 for (l = 0; l < _numrows; l++) {
364 _stream >> _lengthtab[l];
365 }
366 }
367
368 _data = _dev->readAll();
369
370 // sanity check
371 if (_rle) {
372 for (uint o = 0; o < _numrows; o++) {
373 // don't change to greater-or-equal!
374 if (_starttab[o] + _lengthtab[o] > (uint)_data.size()) {
375 // qDebug() << "image corrupt (sanity check failed)";
376 return false;
377 }
378 }
379 }
380
381 if (!readData(img)) {
382 // qDebug() << "image corrupt (incomplete scanline)";
383 return false;
384 }
385
386 return true;
387}
388
389///////////////////////////////////////////////////////////////////////////////
390
391void RLEData::write(QDataStream &s)
392{
393 for (int i = 0; i < size(); i++) {
394 s << at(i);
395 }
396}
397
398bool RLEData::operator<(const RLEData &b) const
399{
400 uchar ac;
401 uchar bc;
402 for (int i = 0; i < qMin(a: size(), b: b.size()); i++) {
403 ac = at(i);
404 bc = b[i];
405 if (ac != bc) {
406 return ac < bc;
407 }
408 }
409 return size() < b.size();
410}
411
412uint RLEMap::insert(const uchar *d, uint l)
413{
414 RLEData data = RLEData(d, l, _offset);
415 Iterator it = find(key: data);
416 if (it != end()) {
417 return it.value();
418 }
419
420 _offset += l;
421 return QMap<RLEData, uint>::insert(key: data, value: _counter++).value();
422}
423
424QList<const RLEData *> RLEMap::vector()
425{
426 QList<const RLEData *> v(size());
427 for (Iterator it = begin(); it != end(); ++it) {
428 v.replace(i: it.value(), t: &it.key());
429 }
430
431 return v;
432}
433
434uchar SGIImage::intensity(uchar c)
435{
436 if (c < _pixmin) {
437 _pixmin = c;
438 }
439 if (c > _pixmax) {
440 _pixmax = c;
441 }
442 return c;
443}
444
445uint SGIImage::compact(uchar *d, uchar *s)
446{
447 uchar *dest = d;
448 uchar *src = s;
449 uchar patt;
450 uchar *t;
451 uchar *end = s + _xsize;
452 int i;
453 int n;
454 while (src < end) {
455 for (n = 0, t = src; t + 2 < end && !(*t == t[1] && *t == t[2]); t++) {
456 n++;
457 }
458
459 while (n) {
460 i = n > 126 ? 126 : n;
461 n -= i;
462 *dest++ = 0x80 | i;
463 while (i--) {
464 *dest++ = *src++;
465 }
466 }
467
468 if (src == end) {
469 break;
470 }
471
472 patt = *src++;
473 for (n = 1; src < end && *src == patt; src++) {
474 n++;
475 }
476
477 while (n) {
478 i = n > 126 ? 126 : n;
479 n -= i;
480 *dest++ = i;
481 *dest++ = patt;
482 }
483 }
484 *dest++ = 0;
485 return dest - d;
486}
487
488bool SGIImage::scanData(const QImage &img)
489{
490 quint32 *start = _starttab;
491 QByteArray lineguard(_xsize * 2, 0);
492 QByteArray bufguard(_xsize, 0);
493 uchar *line = (uchar *)lineguard.data();
494 uchar *buf = (uchar *)bufguard.data();
495 const QRgb *c;
496 unsigned x;
497 unsigned y;
498 uint len;
499
500 for (y = 0; y < _ysize; y++) {
501 const int yPos = _ysize - y - 1; // scanline doesn't do any sanity checking
502 if (yPos >= img.height()) {
503 qWarning() << "Failed to get scanline for" << yPos;
504 return false;
505 }
506
507 c = reinterpret_cast<const QRgb *>(img.scanLine(yPos));
508
509 for (x = 0; x < _xsize; x++) {
510 buf[x] = intensity(c: qRed(rgb: *c++));
511 }
512 len = compact(d: line, s: buf);
513 *start++ = _rlemap.insert(d: line, l: len);
514 }
515
516 if (_zsize == 1) {
517 return true;
518 }
519
520 if (_zsize != 2) {
521 for (y = 0; y < _ysize; y++) {
522 const int yPos = _ysize - y - 1;
523 if (yPos >= img.height()) {
524 qWarning() << "Failed to get scanline for" << yPos;
525 return false;
526 }
527
528 c = reinterpret_cast<const QRgb *>(img.scanLine(yPos));
529 for (x = 0; x < _xsize; x++) {
530 buf[x] = intensity(c: qGreen(rgb: *c++));
531 }
532 len = compact(d: line, s: buf);
533 *start++ = _rlemap.insert(d: line, l: len);
534 }
535
536 for (y = 0; y < _ysize; y++) {
537 const int yPos = _ysize - y - 1;
538 if (yPos >= img.height()) {
539 qWarning() << "Failed to get scanline for" << yPos;
540 return false;
541 }
542
543 c = reinterpret_cast<const QRgb *>(img.scanLine(yPos));
544 for (x = 0; x < _xsize; x++) {
545 buf[x] = intensity(c: qBlue(rgb: *c++));
546 }
547 len = compact(d: line, s: buf);
548 *start++ = _rlemap.insert(d: line, l: len);
549 }
550
551 if (_zsize == 3) {
552 return true;
553 }
554 }
555
556 for (y = 0; y < _ysize; y++) {
557 const int yPos = _ysize - y - 1;
558 if (yPos >= img.height()) {
559 qWarning() << "Failed to get scanline for" << yPos;
560 return false;
561 }
562
563 c = reinterpret_cast<const QRgb *>(img.scanLine(yPos));
564 for (x = 0; x < _xsize; x++) {
565 buf[x] = intensity(c: qAlpha(rgb: *c++));
566 }
567 len = compact(d: line, s: buf);
568 *start++ = _rlemap.insert(d: line, l: len);
569 }
570
571 return true;
572}
573
574void SGIImage::writeHeader()
575{
576 _stream << quint16(0x01da);
577 _stream << _rle << _bpc << _dim;
578 _stream << _xsize << _ysize << _zsize;
579 _stream << _pixmin << _pixmax;
580 _stream << quint32(0);
581
582 for (int i = 0; i < 80; i++) {
583 _imagename[i] = '\0';
584 }
585 _stream.writeRawData(_imagename, len: 80);
586
587 _stream << _colormap;
588 for (int i = 0; i < 404; i++) {
589 _stream << quint8(0);
590 }
591}
592
593void SGIImage::writeRle()
594{
595 _rle = 1;
596 // qDebug() << "writing RLE data";
597 writeHeader();
598 uint i;
599
600 // write start table
601 for (i = 0; i < _numrows; i++) {
602 _stream << quint32(_rlevector[_starttab[i]]->offset());
603 }
604
605 // write length table
606 for (i = 0; i < _numrows; i++) {
607 _stream << quint32(_rlevector[_starttab[i]]->size());
608 }
609
610 // write data
611 for (i = 0; (int)i < _rlevector.size(); i++) {
612 const_cast<RLEData *>(_rlevector[i])->write(s&: _stream);
613 }
614}
615
616void SGIImage::writeVerbatim(const QImage &img)
617{
618 _rle = 0;
619 // qDebug() << "writing verbatim data";
620 writeHeader();
621
622 const QRgb *c;
623 unsigned x;
624 unsigned y;
625
626 for (y = 0; y < _ysize; y++) {
627 c = reinterpret_cast<const QRgb *>(img.scanLine(_ysize - y - 1));
628 for (x = 0; x < _xsize; x++) {
629 _stream << quint8(qRed(rgb: *c++));
630 }
631 }
632
633 if (_zsize == 1) {
634 return;
635 }
636
637 if (_zsize != 2) {
638 for (y = 0; y < _ysize; y++) {
639 c = reinterpret_cast<const QRgb *>(img.scanLine(_ysize - y - 1));
640 for (x = 0; x < _xsize; x++) {
641 _stream << quint8(qGreen(rgb: *c++));
642 }
643 }
644
645 for (y = 0; y < _ysize; y++) {
646 c = reinterpret_cast<const QRgb *>(img.scanLine(_ysize - y - 1));
647 for (x = 0; x < _xsize; x++) {
648 _stream << quint8(qBlue(rgb: *c++));
649 }
650 }
651
652 if (_zsize == 3) {
653 return;
654 }
655 }
656
657 for (y = 0; y < _ysize; y++) {
658 c = reinterpret_cast<const QRgb *>(img.scanLine(_ysize - y - 1));
659 for (x = 0; x < _xsize; x++) {
660 _stream << quint8(qAlpha(rgb: *c++));
661 }
662 }
663}
664
665bool SGIImage::writeImage(const QImage &image)
666{
667 // qDebug() << "writing "; // TODO add filename
668 QImage img = image;
669 if (img.allGray()) {
670 _dim = 2, _zsize = 1;
671 } else {
672 _dim = 3, _zsize = 3;
673 }
674
675 auto hasAlpha = img.hasAlphaChannel();
676 if (hasAlpha) {
677 _dim = 3, _zsize++;
678 }
679
680 if (hasAlpha && img.format() != QImage::Format_ARGB32) {
681 img = img.convertToFormat(f: QImage::Format_ARGB32);
682 } else if (!hasAlpha && img.format() != QImage::Format_RGB32) {
683 img = img.convertToFormat(f: QImage::Format_RGB32);
684 }
685 if (img.isNull()) {
686 // qDebug() << "can't convert image to depth 32";
687 return false;
688 }
689
690 const int w = img.width();
691 const int h = img.height();
692
693 if (w > 65535 || h > 65535) {
694 return false;
695 }
696
697 _bpc = 1;
698 _xsize = w;
699 _ysize = h;
700 _pixmin = ~0u;
701 _pixmax = 0;
702 _colormap = NORMAL;
703 _numrows = _ysize * _zsize;
704 _starttab = new quint32[_numrows];
705 _rlemap.setBaseOffset(512 + _numrows * 2 * sizeof(quint32));
706
707 if (!scanData(img)) {
708 // qDebug() << "this can't happen";
709 return false;
710 }
711
712 _rlevector = _rlemap.vector();
713
714 long verbatim_size = _numrows * _xsize;
715 long rle_size = _numrows * 2 * sizeof(quint32);
716 for (int i = 0; i < _rlevector.size(); i++) {
717 rle_size += _rlevector[i]->size();
718 }
719
720 if (verbatim_size <= rle_size) {
721 writeVerbatim(img);
722 } else {
723 writeRle();
724 }
725 return true;
726}
727
728///////////////////////////////////////////////////////////////////////////////
729
730RGBHandler::RGBHandler()
731{
732}
733
734bool RGBHandler::canRead() const
735{
736 if (canRead(device: device())) {
737 setFormat("rgb");
738 return true;
739 }
740 return false;
741}
742
743bool RGBHandler::read(QImage *outImage)
744{
745 SGIImage sgi(device());
746 return sgi.readImage(img&: *outImage);
747}
748
749bool RGBHandler::write(const QImage &image)
750{
751 SGIImage sgi(device());
752 return sgi.writeImage(image);
753}
754
755bool RGBHandler::canRead(QIODevice *device)
756{
757 if (!device) {
758 qWarning(msg: "RGBHandler::canRead() called with no device");
759 return false;
760 }
761
762 const qint64 oldPos = device->pos();
763 const QByteArray head = device->readLine(maxlen: 64);
764 int readBytes = head.size();
765
766 if (device->isSequential()) {
767 while (readBytes > 0) {
768 device->ungetChar(c: head[readBytes-- - 1]);
769 }
770
771 } else {
772 device->seek(pos: oldPos);
773 }
774
775 return head.size() >= 4 && head.startsWith(bv: "\x01\xda") && (head[2] == 0 || head[2] == 1) && (head[3] == 1 || head[3] == 2);
776}
777
778///////////////////////////////////////////////////////////////////////////////
779
780QImageIOPlugin::Capabilities RGBPlugin::capabilities(QIODevice *device, const QByteArray &format) const
781{
782 if (format == "rgb" || format == "rgba" || format == "bw" || format == "sgi") {
783 return Capabilities(CanRead | CanWrite);
784 }
785 if (!format.isEmpty()) {
786 return {};
787 }
788 if (!device->isOpen()) {
789 return {};
790 }
791
792 Capabilities cap;
793 if (device->isReadable() && RGBHandler::canRead(device)) {
794 cap |= CanRead;
795 }
796 if (device->isWritable()) {
797 cap |= CanWrite;
798 }
799 return cap;
800}
801
802QImageIOHandler *RGBPlugin::create(QIODevice *device, const QByteArray &format) const
803{
804 QImageIOHandler *handler = new RGBHandler;
805 handler->setDevice(device);
806 handler->setFormat(format);
807 return handler;
808}
809
810#include "moc_rgb_p.cpp"
811

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