adapt patch by Alexey Proskuryakov <ap@nypop.com>
[kdelibs.git] / kimgio / rgb.cpp
blobdd41b2c320180708fb6ade53a91141f754eae771
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.
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)
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.
26 #include "rgb.h"
27 #include <QtGui/QImage>
28 #include <kdebug.h>
31 SGIImage::SGIImage(QIODevice *io) :
32 m_starttab(0),
33 m_lengthtab(0)
35 m_dev = io;
36 m_stream.setDevice(m_dev);
40 SGIImage::~SGIImage()
42 delete[] m_starttab;
43 delete[] m_lengthtab;
47 ///////////////////////////////////////////////////////////////////////////////
50 bool SGIImage::getRow(uchar *dest)
52 int n, i;
53 if (!m_rle) {
54 for (i = 0; i < m_xsize; i++) {
55 if (m_pos >= m_data.end())
56 return false;
57 dest[i] = uchar(*m_pos);
58 m_pos += m_bpc;
60 return true;
63 for (i = 0; i < m_xsize;) {
64 if (m_bpc == 2)
65 m_pos++;
66 n = *m_pos & 0x7f;
67 if (!n)
68 break;
70 if (*m_pos++ & 0x80) {
71 for (; i < m_xsize && n--; i++) {
72 *dest++ = *m_pos;
73 m_pos += m_bpc;
75 } else {
76 for (; i < m_xsize && n--; i++)
77 *dest++ = *m_pos;
79 m_pos += m_bpc;
82 return i == m_xsize;
86 bool SGIImage::readData(QImage& img)
88 QRgb *c;
89 quint32 *start = m_starttab;
90 QByteArray lguard(m_xsize, 0);
91 uchar *line = (uchar *)lguard.data();
92 unsigned x, y;
94 if (!m_rle)
95 m_pos = m_data.begin();
97 for (y = 0; y < m_ysize; y++) {
98 if (m_rle)
99 m_pos = m_data.begin() + *start++;
100 if (!getRow(line))
101 return false;
102 c = (QRgb *)img.scanLine(m_ysize - y - 1);
103 for (x = 0; x < m_xsize; x++, c++)
104 *c = qRgb(line[x], line[x], line[x]);
107 if (m_zsize == 1)
108 return true;
110 if (m_zsize != 2) {
111 for (y = 0; y < m_ysize; y++) {
112 if (m_rle)
113 m_pos = m_data.begin() + *start++;
114 if (!getRow(line))
115 return false;
116 c = (QRgb *)img.scanLine(m_ysize - y - 1);
117 for (x = 0; x < m_xsize; x++, c++)
118 *c = qRgb(qRed(*c), line[x], line[x]);
121 for (y = 0; y < m_ysize; y++) {
122 if (m_rle)
123 m_pos = m_data.begin() + *start++;
124 if (!getRow(line))
125 return false;
126 c = (QRgb *)img.scanLine(m_ysize - y - 1);
127 for (x = 0; x < m_xsize; x++, c++)
128 *c = qRgb(qRed(*c), qGreen(*c), line[x]);
131 if (m_zsize == 3)
132 return true;
135 for (y = 0; y < m_ysize; y++) {
136 if (m_rle)
137 m_pos = m_data.begin() + *start++;
138 if (!getRow(line))
139 return false;
140 c = (QRgb *)img.scanLine(m_ysize - y - 1);
141 for (x = 0; x < m_xsize; x++, c++)
142 *c = qRgba(qRed(*c), qGreen(*c), qBlue(*c), line[x]);
145 return true;
149 bool SGIImage::readImage(QImage& img)
151 qint8 u8;
152 qint16 u16;
153 qint32 u32;
155 kDebug(399) << "reading rgb ";
157 // magic
158 m_stream >> u16;
159 if (u16 != 0x01da)
160 return false;
162 // verbatim/rle
163 m_stream >> m_rle;
164 kDebug(399) << (m_rle ? "RLE" : "verbatim");
165 if (m_rle > 1)
166 return false;
168 // bytes per channel
169 m_stream >> m_bpc;
170 kDebug(399) << "bytes per channel: " << int(m_bpc);
171 if (m_bpc == 1)
173 else if (m_bpc == 2)
174 kDebug(399) << "dropping least significant byte";
175 else
176 return false;
178 // number of dimensions
179 m_stream >> m_dim;
180 kDebug(399) << "dimensions: " << m_dim;
181 if (m_dim < 1 || m_dim > 3)
182 return false;
184 m_stream >> m_xsize >> m_ysize >> m_zsize >> m_pixmin >> m_pixmax >> u32;
185 kDebug(399) << "x: " << m_xsize;
186 kDebug(399) << "y: " << m_ysize;
187 kDebug(399) << "z: " << m_zsize;
189 // name
190 m_stream.readRawData(m_imagename, 80);
191 m_imagename[79] = '\0';
193 m_stream >> m_colormap;
194 kDebug(399) << "colormap: " << m_colormap;
195 if (m_colormap != NORMAL)
196 return false; // only NORMAL supported
198 for (int i = 0; i < 404; i++)
199 m_stream >> u8;
201 if (m_dim == 1) {
202 kDebug(399) << "1-dimensional images aren't supported yet";
203 return false;
206 if( m_stream.atEnd())
207 return false;
209 m_numrows = m_ysize * m_zsize;
211 img = QImage( m_xsize, m_ysize, QImage::Format_RGB32 );
213 if (m_zsize == 2 || m_zsize == 4)
214 img = img.convertToFormat(QImage::Format_ARGB32);
215 else if (m_zsize > 4)
216 kDebug(399) << "using first 4 of " << m_zsize << " channels";
218 if (m_rle) {
219 uint l;
220 m_starttab = new quint32[m_numrows];
221 for (l = 0; !m_stream.atEnd() && l < m_numrows; l++) {
222 m_stream >> m_starttab[l];
223 m_starttab[l] -= 512 + m_numrows * 2 * sizeof(quint32);
226 m_lengthtab = new quint32[m_numrows];
227 for (l = 0; l < m_numrows; l++)
228 m_stream >> m_lengthtab[l];
231 m_data = m_dev->readAll();
233 // sanity check
234 if (m_rle)
235 for (uint o = 0; o < m_numrows; o++)
236 // don't change to greater-or-equal!
237 if (m_starttab[o] + m_lengthtab[o] > (uint)m_data.size()) {
238 kDebug(399) << "image corrupt (sanity check failed)";
239 return false;
242 if (!readData(img)) {
243 kDebug(399) << "image corrupt (incomplete scanline)";
244 return false;
247 return true;
251 ///////////////////////////////////////////////////////////////////////////////
253 void RLEData::write(QDataStream& s)
255 for (int i = 0; i < size(); i++)
256 s << at(i);
260 bool RLEData::operator<(const RLEData& b) const
262 uchar ac, bc;
263 for (int i = 0; i < qMin(size(), b.size()); i++) {
264 ac = at(i);
265 bc = b[i];
266 if (ac != bc)
267 return ac < bc;
269 return size() < b.size();
273 uint RLEMap::insert(const uchar *d, uint l)
275 RLEData data = RLEData(d, l, m_offset);
276 Iterator it = find(data);
277 if (it != end())
278 return it.value();
280 m_offset += l;
281 return QMap<RLEData, uint>::insert(data, m_counter++).value();
285 QVector<const RLEData*> RLEMap::vector()
287 QVector<const RLEData*> v(size());
288 for (Iterator it = begin(); it != end(); ++it)
289 v.insert(it.value(), &it.key());
291 return v;
295 uchar SGIImage::intensity(uchar c)
297 if (c < m_pixmin)
298 m_pixmin = c;
299 if (c > m_pixmax)
300 m_pixmax = c;
301 return c;
305 uint SGIImage::compact(uchar *d, uchar *s)
307 uchar *dest = d, *src = s, patt, *t, *end = s + m_xsize;
308 int i, n;
309 while (src < end) {
310 for (n = 0, t = src; t + 2 < end && !(*t == t[1] && *t == t[2]); t++)
311 n++;
313 while (n) {
314 i = n > 126 ? 126 : n;
315 n -= i;
316 *dest++ = 0x80 | i;
317 while (i--)
318 *dest++ = *src++;
321 if (src == end)
322 break;
324 patt = *src++;
325 for (n = 1; src < end && *src == patt; src++)
326 n++;
328 while (n) {
329 i = n > 126 ? 126 : n;
330 n -= i;
331 *dest++ = i;
332 *dest++ = patt;
335 *dest++ = 0;
336 return dest - d;
340 bool SGIImage::scanData(const QImage& img)
342 quint32 *start = m_starttab;
343 QByteArray lineguard(m_xsize * 2, 0);
344 QByteArray bufguard(m_xsize, 0);
345 uchar *line = (uchar *)lineguard.data();
346 uchar *buf = (uchar *)bufguard.data();
347 const QRgb *c;
348 unsigned x, y;
349 uint len;
351 for (y = 0; y < m_ysize; y++) {
352 c = reinterpret_cast<const QRgb *>(img.scanLine(m_ysize - y - 1));
353 for (x = 0; x < m_xsize; x++)
354 buf[x] = intensity(qRed(*c++));
355 len = compact(line, buf);
356 *start++ = m_rlemap.insert(line, len);
359 if (m_zsize == 1)
360 return true;
362 if (m_zsize != 2) {
363 for (y = 0; y < m_ysize; y++) {
364 c = reinterpret_cast<const QRgb *>(img.scanLine(m_ysize - y - 1));
365 for (x = 0; x < m_xsize; x++)
366 buf[x] = intensity(qGreen(*c++));
367 len = compact(line, buf);
368 *start++ = m_rlemap.insert(line, len);
371 for (y = 0; y < m_ysize; y++) {
372 c = reinterpret_cast<const QRgb *>(img.scanLine(m_ysize - y - 1));
373 for (x = 0; x < m_xsize; x++)
374 buf[x] = intensity(qBlue(*c++));
375 len = compact(line, buf);
376 *start++ = m_rlemap.insert(line, len);
379 if (m_zsize == 3)
380 return true;
383 for (y = 0; y < m_ysize; y++) {
384 c = reinterpret_cast<const QRgb *>(img.scanLine(m_ysize - y - 1));
385 for (x = 0; x < m_xsize; x++)
386 buf[x] = intensity(qAlpha(*c++));
387 len = compact(line, buf);
388 *start++ = m_rlemap.insert(line, len);
391 return true;
395 void SGIImage::writeHeader()
397 m_stream << quint16(0x01da);
398 m_stream << m_rle << m_bpc << m_dim;
399 m_stream << m_xsize << m_ysize << m_zsize;
400 m_stream << m_pixmin << m_pixmax;
401 m_stream << quint32(0);
403 m_stream << m_colormap;
404 for (int i = 0; i < 404; i++)
405 m_stream << quint8(0);
409 void SGIImage::writeRle()
411 m_rle = 1;
412 kDebug(399) << "writing RLE data";
413 writeHeader();
414 uint i;
416 // write start table
417 for (i = 0; i < m_numrows; i++)
418 m_stream << quint32(m_rlevector[m_starttab[i]]->offset());
420 // write length table
421 for (i = 0; i < m_numrows; i++)
422 m_stream << quint32(m_rlevector[m_starttab[i]]->size());
424 // write data
425 for (i = 0; (int)i < m_rlevector.size(); i++)
426 const_cast<RLEData*>(m_rlevector[i])->write(m_stream);
430 void SGIImage::writeVerbatim(const QImage& img)
432 m_rle = 0;
433 kDebug(399) << "writing verbatim data";
434 writeHeader();
436 const QRgb *c;
437 unsigned x, y;
439 for (y = 0; y < m_ysize; y++) {
440 c = reinterpret_cast<const QRgb *>(img.scanLine(m_ysize - y - 1));
441 for (x = 0; x < m_xsize; x++)
442 m_stream << quint8(qRed(*c++));
445 if (m_zsize == 1)
446 return;
448 if (m_zsize != 2) {
449 for (y = 0; y < m_ysize; y++) {
450 c = reinterpret_cast<const QRgb *>(img.scanLine(m_ysize - y - 1));
451 for (x = 0; x < m_xsize; x++)
452 m_stream << quint8(qGreen(*c++));
455 for (y = 0; y < m_ysize; y++) {
456 c = reinterpret_cast<const QRgb *>(img.scanLine(m_ysize - y - 1));
457 for (x = 0; x < m_xsize; x++)
458 m_stream << quint8(qBlue(*c++));
461 if (m_zsize == 3)
462 return;
465 for (y = 0; y < m_ysize; y++) {
466 c = reinterpret_cast<const QRgb *>(img.scanLine(m_ysize - y - 1));
467 for (x = 0; x < m_xsize; x++)
468 m_stream << quint8(qAlpha(*c++));
473 bool SGIImage::writeImage(const QImage& image)
475 kDebug(399) << "writing ";
476 QImage img = image;
477 if (img.allGray())
478 m_dim = 2, m_zsize = 1;
479 else
480 m_dim = 3, m_zsize = 3;
482 if (img.format() == QImage::Format_ARGB32)
483 m_dim = 3, m_zsize++;
485 img = img.convertToFormat(QImage::Format_RGB32);
486 if (img.isNull()) {
487 kDebug(399) << "can't convert image to depth 32";
488 return false;
491 m_bpc = 1;
492 m_xsize = img.width();
493 m_ysize = img.height();
494 m_pixmin = ~0u;
495 m_pixmax = 0;
496 m_colormap = NORMAL;
498 m_numrows = m_ysize * m_zsize;
500 m_starttab = new quint32[m_numrows];
501 m_rlemap.setBaseOffset(512 + m_numrows * 2 * sizeof(quint32));
503 if (!scanData(img)) {
504 kDebug(399) << "this can't happen";
505 return false;
508 m_rlevector = m_rlemap.vector();
510 long verbatim_size = m_numrows * m_xsize;
511 long rle_size = m_numrows * 2 * sizeof(quint32);
512 for (int i = 0; i < m_rlevector.size(); i++)
513 rle_size += m_rlevector[i]->size();
515 kDebug(399) << "minimum intensity: " << m_pixmin;
516 kDebug(399) << "maximum intensity: " << m_pixmax;
517 kDebug(399) << "saved scanlines: " << m_numrows - m_rlemap.size();
518 kDebug(399) << "total savings: " << (verbatim_size - rle_size) << " bytes";
519 kDebug(399) << "compression: " << (rle_size * 100.0 / verbatim_size) << '%';
521 if (verbatim_size <= rle_size)
522 writeVerbatim(img);
523 else
524 writeRle();
525 return true;
529 RGBHandler::RGBHandler()
533 bool RGBHandler::canRead() const
535 return canRead(device());
538 bool RGBHandler::read(QImage *outImage)
540 SGIImage sgi(device());
542 if (!sgi.readImage(*outImage)) {
543 return false;
546 return true;
549 bool RGBHandler::write(const QImage &image)
551 SGIImage sgi(device());
553 if (!sgi.writeImage(image))
554 return false;
556 return true;
559 QByteArray RGBHandler::name() const
561 return "rgb";
564 bool RGBHandler::canRead(QIODevice *device)
566 if (!device) {
567 qWarning("RGBHandler::canRead() called with no device");
568 return false;
571 qint64 oldPos = device->pos();
572 QByteArray head = device->readLine(64);
573 int readBytes = head.size();
575 if (device->isSequential()) {
576 while (readBytes > 0)
577 device->ungetChar(head[readBytes-- - 1]);
578 } else {
579 device->seek(oldPos);
582 const QRegExp regexp("^\x01\xda\x01[\x01\x02]");
583 QString data(head);
585 return data.contains(regexp);
589 class RGBPlugin : public QImageIOPlugin
591 public:
592 QStringList keys() const;
593 Capabilities capabilities(QIODevice *device, const QByteArray &format) const;
594 QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const;
597 QStringList RGBPlugin::keys() const
599 return QStringList() << "rgb"<<"RGB"<<"rgba"<<"RGBA"
600 <<"bw"<<"BW"<<"sgi"<<"SGI";
603 QImageIOPlugin::Capabilities RGBPlugin::capabilities(QIODevice *device, const QByteArray &format) const
605 if (format == "rgb" || format == "rgb" || format == "RGB" ||
606 format == "rgba" || format == "RGBA" || format == "bw" ||
607 format == "BW" || format == "sgi" || format == "SGI")
608 return Capabilities(CanRead | CanWrite);
609 if (!format.isEmpty())
610 return 0;
611 if (!device->isOpen())
612 return 0;
614 Capabilities cap;
615 if (device->isReadable() && RGBHandler::canRead(device))
616 cap |= CanRead;
617 if (device->isWritable())
618 cap |= CanWrite;
619 return cap;
622 QImageIOHandler *RGBPlugin::create(QIODevice *device, const QByteArray &format) const
624 QImageIOHandler *handler = new RGBHandler;
625 handler->setDevice(device);
626 handler->setFormat(format);
627 return handler;
630 Q_EXPORT_STATIC_PLUGIN(RGBPlugin)
631 Q_EXPORT_PLUGIN2(rgb, RGBPlugin)