1// kimgio module for SGI images
2//
3// Copyright (C) 2004 Melchior FRANZ <mfranz@kde.org>
4//
5// This program is free software; you can redistribute it and/or
6// modify it under the terms of the Lesser GNU General Public License as
7// published by the Free Software Foundation; either version 2 of the
8// License, or (at your option) any later version.
9
10
11/* this code supports:
12 * reading:
13 * everything, except images with 1 dimension or images with
14 * mapmode != NORMAL (e.g. dithered); Images with 16 bit
15 * precision or more than 4 layers are stripped down.
16 * writing:
17 * Run Length Encoded (RLE) or Verbatim (uncompressed)
18 * (whichever is smaller)
19 *
20 * Please report if you come across rgb/rgba/sgi/bw files that aren't
21 * recognized. Also report applications that can't deal with images
22 * saved by this filter.
23 */
24
25
26#include "rgb.h"
27#include <QtGui/QImage>
28#include <kdebug.h>
29
30
31SGIImage::SGIImage(QIODevice *io) :
32 _starttab(0),
33 _lengthtab(0)
34{
35 _dev = io;
36 _stream.setDevice(_dev);
37}
38
39
40SGIImage::~SGIImage()
41{
42 delete[] _starttab;
43 delete[] _lengthtab;
44}
45
46
47///////////////////////////////////////////////////////////////////////////////
48
49
50bool SGIImage::getRow(uchar *dest)
51{
52 int n, i;
53 if (!_rle) {
54 for (i = 0; i < _xsize; i++) {
55 if (_pos >= _data.end())
56 return false;
57 dest[i] = uchar(*_pos);
58 _pos += _bpc;
59 }
60 return true;
61 }
62
63 for (i = 0; i < _xsize;) {
64 if (_bpc == 2)
65 _pos++;
66 n = *_pos & 0x7f;
67 if (!n)
68 break;
69
70 if (*_pos++ & 0x80) {
71 for (; i < _xsize && n--; i++) {
72 *dest++ = *_pos;
73 _pos += _bpc;
74 }
75 } else {
76 for (; i < _xsize && n--; i++)
77 *dest++ = *_pos;
78
79 _pos += _bpc;
80 }
81 }
82 return i == _xsize;
83}
84
85
86bool SGIImage::readData(QImage& img)
87{
88 QRgb *c;
89 quint32 *start = _starttab;
90 QByteArray lguard(_xsize, 0);
91 uchar *line = (uchar *)lguard.data();
92 unsigned x, y;
93
94 if (!_rle)
95 _pos = _data.begin();
96
97 for (y = 0; y < _ysize; y++) {
98 if (_rle)
99 _pos = _data.begin() + *start++;
100 if (!getRow(line))
101 return false;
102 c = (QRgb *)img.scanLine(_ysize - y - 1);
103 for (x = 0; x < _xsize; x++, c++)
104 *c = qRgb(line[x], line[x], line[x]);
105 }
106
107 if (_zsize == 1)
108 return true;
109
110 if (_zsize != 2) {
111 for (y = 0; y < _ysize; y++) {
112 if (_rle)
113 _pos = _data.begin() + *start++;
114 if (!getRow(line))
115 return false;
116 c = (QRgb *)img.scanLine(_ysize - y - 1);
117 for (x = 0; x < _xsize; x++, c++)
118 *c = qRgb(qRed(*c), line[x], line[x]);
119 }
120
121 for (y = 0; y < _ysize; y++) {
122 if (_rle)
123 _pos = _data.begin() + *start++;
124 if (!getRow(line))
125 return false;
126 c = (QRgb *)img.scanLine(_ysize - y - 1);
127 for (x = 0; x < _xsize; x++, c++)
128 *c = qRgb(qRed(*c), qGreen(*c), line[x]);
129 }
130
131 if (_zsize == 3)
132 return true;
133 }
134
135 for (y = 0; y < _ysize; y++) {
136 if (_rle)
137 _pos = _data.begin() + *start++;
138 if (!getRow(line))
139 return false;
140 c = (QRgb *)img.scanLine(_ysize - y - 1);
141 for (x = 0; x < _xsize; x++, c++)
142 *c = qRgba(qRed(*c), qGreen(*c), qBlue(*c), line[x]);
143 }
144
145 return true;
146}
147
148
149bool SGIImage::readImage(QImage& img)
150{
151 qint8 u8;
152 qint16 u16;
153 qint32 u32;
154
155 kDebug(399) << "reading rgb ";
156
157 // magic
158 _stream >> u16;
159 if (u16 != 0x01da)
160 return false;
161
162 // verbatim/rle
163 _stream >> _rle;
164 kDebug(399) << (_rle ? "RLE" : "verbatim");
165 if (_rle > 1)
166 return false;
167
168 // bytes per channel
169 _stream >> _bpc;
170 kDebug(399) << "bytes per channel: " << int(_bpc);
171 if (_bpc == 1)
172 ;
173 else if (_bpc == 2)
174 kDebug(399) << "dropping least significant byte";
175 else
176 return false;
177
178 // number of dimensions
179 _stream >> _dim;
180 kDebug(399) << "dimensions: " << _dim;
181 if (_dim < 1 || _dim > 3)
182 return false;
183
184 _stream >> _xsize >> _ysize >> _zsize >> _pixmin >> _pixmax >> u32;
185 kDebug(399) << "x: " << _xsize;
186 kDebug(399) << "y: " << _ysize;
187 kDebug(399) << "z: " << _zsize;
188
189 // name
190 _stream.readRawData(_imagename, 80);
191 _imagename[79] = '\0';
192
193 _stream >> _colormap;
194 kDebug(399) << "colormap: " << _colormap;
195 if (_colormap != NORMAL)
196 return false; // only NORMAL supported
197
198 for (int i = 0; i < 404; i++)
199 _stream >> u8;
200
201 if (_dim == 1) {
202 kDebug(399) << "1-dimensional images aren't supported yet";
203 return false;
204 }
205
206 if( _stream.atEnd())
207 return false;
208
209 _numrows = _ysize * _zsize;
210
211 img = QImage( _xsize, _ysize, QImage::Format_RGB32 );
212
213 if (_zsize == 2 || _zsize == 4)
214 img = img.convertToFormat(QImage::Format_ARGB32);
215 else if (_zsize > 4)
216 kDebug(399) << "using first 4 of " << _zsize << " channels";
217
218 if (_rle) {
219 uint l;
220 _starttab = new quint32[_numrows];
221 for (l = 0; !_stream.atEnd() && l < _numrows; l++) {
222 _stream >> _starttab[l];
223 _starttab[l] -= 512 + _numrows * 2 * sizeof(quint32);
224 }
225
226 _lengthtab = new quint32[_numrows];
227 for (l = 0; l < _numrows; l++)
228 _stream >> _lengthtab[l];
229 }
230
231 _data = _dev->readAll();
232
233 // sanity check
234 if (_rle)
235 for (uint o = 0; o < _numrows; o++)
236 // don't change to greater-or-equal!
237 if (_starttab[o] + _lengthtab[o] > (uint)_data.size()) {
238 kDebug(399) << "image corrupt (sanity check failed)";
239 return false;
240 }
241
242 if (!readData(img)) {
243 kDebug(399) << "image corrupt (incomplete scanline)";
244 return false;
245 }
246
247 return true;
248}
249
250
251///////////////////////////////////////////////////////////////////////////////
252
253
254void RLEData::write(QDataStream& s)
255{
256 for (int i = 0; i < size(); i++)
257 s << at(i);
258}
259
260
261bool RLEData::operator<(const RLEData& b) const
262{
263 uchar ac, bc;
264 for (int i = 0; i < qMin(size(), b.size()); i++) {
265 ac = at(i);
266 bc = b[i];
267 if (ac != bc)
268 return ac < bc;
269 }
270 return size() < b.size();
271}
272
273
274uint RLEMap::insert(const uchar *d, uint l)
275{
276 RLEData data = RLEData(d, l, _offset);
277 Iterator it = find(data);
278 if (it != end())
279 return it.value();
280
281 _offset += l;
282 return QMap<RLEData, uint>::insert(data, _counter++).value();
283}
284
285
286QVector<const RLEData*> RLEMap::vector()
287{
288 QVector<const RLEData*> v(size());
289 for (Iterator it = begin(); it != end(); ++it)
290 v.replace(it.value(), &it.key());
291
292 return v;
293}
294
295
296uchar SGIImage::intensity(uchar c)
297{
298 if (c < _pixmin)
299 _pixmin = c;
300 if (c > _pixmax)
301 _pixmax = c;
302 return c;
303}
304
305
306uint SGIImage::compact(uchar *d, uchar *s)
307{
308 uchar *dest = d, *src = s, patt, *t, *end = s + _xsize;
309 int i, n;
310 while (src < end) {
311 for (n = 0, t = src; t + 2 < end && !(*t == t[1] && *t == t[2]); t++)
312 n++;
313
314 while (n) {
315 i = n > 126 ? 126 : n;
316 n -= i;
317 *dest++ = 0x80 | i;
318 while (i--)
319 *dest++ = *src++;
320 }
321
322 if (src == end)
323 break;
324
325 patt = *src++;
326 for (n = 1; src < end && *src == patt; src++)
327 n++;
328
329 while (n) {
330 i = n > 126 ? 126 : n;
331 n -= i;
332 *dest++ = i;
333 *dest++ = patt;
334 }
335 }
336 *dest++ = 0;
337 return dest - d;
338}
339
340
341bool SGIImage::scanData(const QImage& img)
342{
343 quint32 *start = _starttab;
344 QByteArray lineguard(_xsize * 2, 0);
345 QByteArray bufguard(_xsize, 0);
346 uchar *line = (uchar *)lineguard.data();
347 uchar *buf = (uchar *)bufguard.data();
348 const QRgb *c;
349 unsigned x, y;
350 uint len;
351
352 for (y = 0; y < _ysize; y++) {
353 c = reinterpret_cast<const QRgb *>(img.scanLine(_ysize - y - 1));
354 for (x = 0; x < _xsize; x++)
355 buf[x] = intensity(qRed(*c++));
356 len = compact(line, buf);
357 *start++ = _rlemap.insert(line, len);
358 }
359
360 if (_zsize == 1)
361 return true;
362
363 if (_zsize != 2) {
364 for (y = 0; y < _ysize; y++) {
365 c = reinterpret_cast<const QRgb *>(img.scanLine(_ysize - y - 1));
366 for (x = 0; x < _xsize; x++)
367 buf[x] = intensity(qGreen(*c++));
368 len = compact(line, buf);
369 *start++ = _rlemap.insert(line, len);
370 }
371
372 for (y = 0; y < _ysize; y++) {
373 c = reinterpret_cast<const QRgb *>(img.scanLine(_ysize - y - 1));
374 for (x = 0; x < _xsize; x++)
375 buf[x] = intensity(qBlue(*c++));
376 len = compact(line, buf);
377 *start++ = _rlemap.insert(line, len);
378 }
379
380 if (_zsize == 3)
381 return true;
382 }
383
384 for (y = 0; y < _ysize; y++) {
385 c = reinterpret_cast<const QRgb *>(img.scanLine(_ysize - y - 1));
386 for (x = 0; x < _xsize; x++)
387 buf[x] = intensity(qAlpha(*c++));
388 len = compact(line, buf);
389 *start++ = _rlemap.insert(line, len);
390 }
391
392 return true;
393}
394
395
396void SGIImage::writeHeader()
397{
398 _stream << quint16(0x01da);
399 _stream << _rle << _bpc << _dim;
400 _stream << _xsize << _ysize << _zsize;
401 _stream << _pixmin << _pixmax;
402 _stream << quint32(0);
403
404 for (int i = 0; i < 80; i++)
405 _imagename[i] = '\0';
406 _stream.writeRawData(_imagename, 80);
407
408 _stream << _colormap;
409 for (int i = 0; i < 404; i++)
410 _stream << quint8(0);
411}
412
413
414void SGIImage::writeRle()
415{
416 _rle = 1;
417 kDebug(399) << "writing RLE data";
418 writeHeader();
419 uint i;
420
421 // write start table
422 for (i = 0; i < _numrows; i++)
423 _stream << quint32(_rlevector[_starttab[i]]->offset());
424
425 // write length table
426 for (i = 0; i < _numrows; i++)
427 _stream << quint32(_rlevector[_starttab[i]]->size());
428
429 // write data
430 for (i = 0; (int)i < _rlevector.size(); i++)
431 const_cast<RLEData*>(_rlevector[i])->write(_stream);
432}
433
434
435void SGIImage::writeVerbatim(const QImage& img)
436{
437 _rle = 0;
438 kDebug(399) << "writing verbatim data";
439 writeHeader();
440
441 const QRgb *c;
442 unsigned x, y;
443
444 for (y = 0; y < _ysize; y++) {
445 c = reinterpret_cast<const QRgb *>(img.scanLine(_ysize - y - 1));
446 for (x = 0; x < _xsize; x++)
447 _stream << quint8(qRed(*c++));
448 }
449
450 if (_zsize == 1)
451 return;
452
453 if (_zsize != 2) {
454 for (y = 0; y < _ysize; y++) {
455 c = reinterpret_cast<const QRgb *>(img.scanLine(_ysize - y - 1));
456 for (x = 0; x < _xsize; x++)
457 _stream << quint8(qGreen(*c++));
458 }
459
460 for (y = 0; y < _ysize; y++) {
461 c = reinterpret_cast<const QRgb *>(img.scanLine(_ysize - y - 1));
462 for (x = 0; x < _xsize; x++)
463 _stream << quint8(qBlue(*c++));
464 }
465
466 if (_zsize == 3)
467 return;
468 }
469
470 for (y = 0; y < _ysize; y++) {
471 c = reinterpret_cast<const QRgb *>(img.scanLine(_ysize - y - 1));
472 for (x = 0; x < _xsize; x++)
473 _stream << quint8(qAlpha(*c++));
474 }
475}
476
477
478bool SGIImage::writeImage(const QImage& image)
479{
480 kDebug(399) << "writing "; // TODO add filename
481 QImage img = image;
482 if (img.allGray())
483 _dim = 2, _zsize = 1;
484 else
485 _dim = 3, _zsize = 3;
486
487 if (img.format() == QImage::Format_ARGB32)
488 _dim = 3, _zsize++;
489
490 img = img.convertToFormat(QImage::Format_RGB32);
491 if (img.isNull()) {
492 kDebug(399) << "can't convert image to depth 32";
493 return false;
494 }
495
496 _bpc = 1;
497 _xsize = img.width();
498 _ysize = img.height();
499 _pixmin = ~0u;
500 _pixmax = 0;
501 _colormap = NORMAL;
502 _numrows = _ysize * _zsize;
503 _starttab = new quint32[_numrows];
504 _rlemap.setBaseOffset(512 + _numrows * 2 * sizeof(quint32));
505
506 if (!scanData(img)) {
507 kDebug(399) << "this can't happen";
508 return false;
509 }
510
511 _rlevector = _rlemap.vector();
512
513 long verbatim_size = _numrows * _xsize;
514 long rle_size = _numrows * 2 * sizeof(quint32);
515 for (int i = 0; i < _rlevector.size(); i++)
516 rle_size += _rlevector[i]->size();
517
518 kDebug(399) << "minimum intensity: " << _pixmin;
519 kDebug(399) << "maximum intensity: " << _pixmax;
520 kDebug(399) << "saved scanlines: " << _numrows - _rlemap.size();
521 kDebug(399) << "total savings: " << (verbatim_size - rle_size) << " bytes";
522 kDebug(399) << "compression: " << (rle_size * 100.0 / verbatim_size) << '%';
523
524 if (verbatim_size <= rle_size)
525 writeVerbatim(img);
526 else
527 writeRle();
528 return true;
529}
530
531
532///////////////////////////////////////////////////////////////////////////////
533
534
535RGBHandler::RGBHandler()
536{
537}
538
539
540bool RGBHandler::canRead() const
541{
542 if (canRead(device())) {
543 setFormat("rgb");
544 return true;
545 }
546 return false;
547}
548
549
550bool RGBHandler::read(QImage *outImage)
551{
552 SGIImage sgi(device());
553 return sgi.readImage(*outImage);
554}
555
556
557bool RGBHandler::write(const QImage &image)
558{
559 SGIImage sgi(device());
560 return sgi.writeImage(image);
561}
562
563
564QByteArray RGBHandler::name() const
565{
566 return "rgb";
567}
568
569
570bool RGBHandler::canRead(QIODevice *device)
571{
572 if (!device) {
573 qWarning("RGBHandler::canRead() called with no device");
574 return false;
575 }
576
577 qint64 oldPos = device->pos();
578 QByteArray head = device->readLine(64);
579 int readBytes = head.size();
580
581 if (device->isSequential()) {
582 while (readBytes > 0)
583 device->ungetChar(head[readBytes-- - 1]);
584
585 } else {
586 device->seek(oldPos);
587 }
588
589 const QRegExp regexp("^\x01\xda\x01[\x01\x02]");
590 QString data(head);
591
592 return data.contains(regexp);
593}
594
595
596///////////////////////////////////////////////////////////////////////////////
597
598
599class RGBPlugin : public QImageIOPlugin
600{
601public:
602 QStringList keys() const;
603 Capabilities capabilities(QIODevice *device, const QByteArray &format) const;
604 QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const;
605};
606
607
608QStringList RGBPlugin::keys() const
609{
610 return QStringList() << "rgb" << "RGB" << "rgba" << "RGBA" << "bw" << "BW" << "sgi" << "SGI";
611}
612
613
614QImageIOPlugin::Capabilities RGBPlugin::capabilities(QIODevice *device, const QByteArray &format) const
615{
616 if (format == "rgb" || format == "RGB" || format == "rgba" || format == "RGBA"
617 || format == "bw" || format == "BW" || format == "sgi" || format == "SGI")
618 return Capabilities(CanRead|CanWrite);
619
620 if (!format.isEmpty())
621 return 0;
622 if (!device->isOpen())
623 return 0;
624
625 Capabilities cap;
626 if (device->isReadable() && RGBHandler::canRead(device))
627 cap |= CanRead;
628 if (device->isWritable())
629 cap |= CanWrite;
630 return cap;
631}
632
633
634QImageIOHandler *RGBPlugin::create(QIODevice *device, const QByteArray &format) const
635{
636 QImageIOHandler *handler = new RGBHandler;
637 handler->setDevice(device);
638 handler->setFormat(format);
639 return handler;
640}
641
642
643Q_EXPORT_STATIC_PLUGIN(RGBPlugin)
644Q_EXPORT_PLUGIN2(rgb, RGBPlugin)
645
646