KImageFormats

rgb.cpp
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(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);
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(line)) {
201 return false;
202 }
203 c = (QRgb *)img.scanLine(_ysize - y - 1);
204 for (x = 0; x < _xsize; x++, c++) {
205 *c = qRgb(line[x], line[x], 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(line)) {
219 return false;
220 }
221 c = (QRgb *)img.scanLine(_ysize - y - 1);
222 for (x = 0; x < _xsize; x++, c++) {
223 *c = qRgb(qRed(*c), line[x], line[x]);
224 }
225 }
226
227 for (y = 0; y < _ysize; y++) {
228 if (_rle) {
229 _pos = _data.begin() + *start++;
230 }
231 if (!getRow(line)) {
232 return false;
233 }
234 c = (QRgb *)img.scanLine(_ysize - y - 1);
235 for (x = 0; x < _xsize; x++, c++) {
236 *c = qRgb(qRed(*c), qGreen(*c), 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(line)) {
250 return false;
251 }
252 c = (QRgb *)img.scanLine(_ysize - y - 1);
253 for (x = 0; x < _xsize; x++, c++) {
254 *c = qRgba(qRed(*c), qGreen(*c), qBlue(*c), 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, 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(_xsize, _ysize, 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) {
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(size(), 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(data);
416 if (it != end()) {
417 return it.value();
418 }
419
420 _offset += l;
421 return QMap<RLEData, uint>::insert(data, _counter++).value();
422}
423
424QList<const RLEData *> RLEMap::vector()
425{
427 for (Iterator it = begin(); it != end(); ++it) {
428 v.replace(it.value(), &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(qRed(*c++));
511 }
512 len = compact(line, buf);
513 *start++ = _rlemap.insert(line, 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(qGreen(*c++));
531 }
532 len = compact(line, buf);
533 *start++ = _rlemap.insert(line, 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(qBlue(*c++));
546 }
547 len = compact(line, buf);
548 *start++ = _rlemap.insert(line, 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(qAlpha(*c++));
566 }
567 len = compact(line, buf);
568 *start++ = _rlemap.insert(line, 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, 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(_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(*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(*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(*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(*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) {
682 } else if (!hasAlpha && img.format() != 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())) {
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(*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("RGBHandler::canRead() called with no device");
759 return false;
760 }
761
762 const qint64 oldPos = device->pos();
763 const QByteArray head = device->readLine(64);
764 int readBytes = head.size();
765
766 if (device->isSequential()) {
767 while (readBytes > 0) {
768 device->ungetChar(head[readBytes-- - 1]);
769 }
770
771 } else {
772 device->seek(oldPos);
773 }
774
775 return head.size() >= 4 && head.startsWith("\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"
Q_SCRIPTABLE Q_NOREPLY void start()
QFlags< Capability > Capabilities
const QList< QKeySequence > & end()
iterator begin()
iterator end()
bool isEmpty() const const
qsizetype size() const const
bool startsWith(QByteArrayView bv) const const
bool atEnd() const const
int readRawData(char *s, int len)
int writeRawData(const char *s, int len)
bool allGray() const const
QImage convertToFormat(Format format, Qt::ImageConversionFlags flags) &&
Format format() const const
bool hasAlphaChannel() const const
int height() const const
bool isNull() const const
uchar * scanLine(int i)
int width() const const
void setDevice(QIODevice *device)
void setFormat(const QByteArray &format)
typedef Capabilities
bool isOpen() const const
bool isReadable() const const
virtual bool isSequential() const const
bool isWritable() const const
virtual qint64 pos() const const
QByteArray readAll()
QByteArray readLine(qint64 maxSize)
virtual bool seek(qint64 pos)
void ungetChar(char c)
void append(QList< T > &&value)
const_reference at(qsizetype i) const const
qsizetype size() const const
iterator find(const Key &key)
iterator insert(const Key &key, const T &value)
size_type size() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:12:40 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.