KImageFormats

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

KDE's Doxygen guidelines are available online.