diff options
Diffstat (limited to 'kimgio/rgb.cpp')
-rw-r--r-- | kimgio/rgb.cpp | 589 |
1 files changed, 589 insertions, 0 deletions
diff --git a/kimgio/rgb.cpp b/kimgio/rgb.cpp new file mode 100644 index 000000000..2e62f4f6f --- /dev/null +++ b/kimgio/rgb.cpp @@ -0,0 +1,589 @@ +// kimgio module for SGI images +// +// Copyright (C) 2004 Melchior FRANZ <mfranz@kde.org> +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the Lesser GNU General Public License as +// published by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version. + + +/* this code supports: + * reading: + * everything, except images with 1 dimension or images with + * mapmode != NORMAL (e.g. dithered); Images with 16 bit + * precision or more than 4 layers are stripped down. + * writing: + * Run Length Encoded (RLE) or Verbatim (uncompressed) + * (whichever is smaller) + * + * Please report if you come across rgb/rgba/sgi/bw files that aren't + * recognized. Also report applications that can't deal with images + * saved by this filter. + */ + + +#include "rgb.h" +#include <qimage.h> +#include <kdebug.h> + + +/////////////////////////////////////////////////////////////////////////////// + + +KDE_EXPORT void kimgio_rgb_read(QImageIO *io) +{ + SGIImage sgi(io); + QImage img; + + if (!sgi.readImage(img)) { + io->setImage(0); + io->setStatus(-1); + return; + } + + io->setImage(img); + io->setStatus(0); +} + + +KDE_EXPORT void kimgio_rgb_write(QImageIO *io) +{ + SGIImage sgi(io); + QImage img = io->image(); + + if (!sgi.writeImage(img)) + io->setStatus(-1); + + io->setStatus(0); +} + + +/////////////////////////////////////////////////////////////////////////////// + + +SGIImage::SGIImage(QImageIO *io) : + m_io(io), + m_starttab(0), + m_lengthtab(0) +{ + m_dev = io->ioDevice(); + m_stream.setDevice(m_dev); +} + + +SGIImage::~SGIImage() +{ + delete[] m_starttab; + delete[] m_lengthtab; +} + + +/////////////////////////////////////////////////////////////////////////////// + + +bool SGIImage::getRow(uchar *dest) +{ + int n, i; + if (!m_rle) { + for (i = 0; i < m_xsize; i++) { + if (m_pos >= m_data.end()) + return false; + dest[i] = uchar(*m_pos); + m_pos += m_bpc; + } + return true; + } + + for (i = 0; i < m_xsize;) { + if (m_bpc == 2) + m_pos++; + n = *m_pos & 0x7f; + if (!n) + break; + + if (*m_pos++ & 0x80) { + for (; i < m_xsize && n--; i++) { + *dest++ = *m_pos; + m_pos += m_bpc; + } + } else { + for (; i < m_xsize && n--; i++) + *dest++ = *m_pos; + + m_pos += m_bpc; + } + } + return i == m_xsize; +} + + +bool SGIImage::readData(QImage& img) +{ + QRgb *c; + Q_UINT32 *start = m_starttab; + QByteArray lguard(m_xsize); + uchar *line = (uchar *)lguard.data(); + unsigned x, y; + + if (!m_rle) + m_pos = m_data.begin(); + + for (y = 0; y < m_ysize; y++) { + if (m_rle) + m_pos = m_data.begin() + *start++; + if (!getRow(line)) + return false; + c = (QRgb *)img.scanLine(m_ysize - y - 1); + for (x = 0; x < m_xsize; x++, c++) + *c = qRgb(line[x], line[x], line[x]); + } + + if (m_zsize == 1) + return true; + + if (m_zsize != 2) { + for (y = 0; y < m_ysize; y++) { + if (m_rle) + m_pos = m_data.begin() + *start++; + if (!getRow(line)) + return false; + c = (QRgb *)img.scanLine(m_ysize - y - 1); + for (x = 0; x < m_xsize; x++, c++) + *c = qRgb(qRed(*c), line[x], line[x]); + } + + for (y = 0; y < m_ysize; y++) { + if (m_rle) + m_pos = m_data.begin() + *start++; + if (!getRow(line)) + return false; + c = (QRgb *)img.scanLine(m_ysize - y - 1); + for (x = 0; x < m_xsize; x++, c++) + *c = qRgb(qRed(*c), qGreen(*c), line[x]); + } + + if (m_zsize == 3) + return true; + } + + for (y = 0; y < m_ysize; y++) { + if (m_rle) + m_pos = m_data.begin() + *start++; + if (!getRow(line)) + return false; + c = (QRgb *)img.scanLine(m_ysize - y - 1); + for (x = 0; x < m_xsize; x++, c++) + *c = qRgba(qRed(*c), qGreen(*c), qBlue(*c), line[x]); + } + + return true; +} + + +bool SGIImage::readImage(QImage& img) +{ + Q_INT8 u8; + Q_INT16 u16; + Q_INT32 u32; + + kdDebug(399) << "reading '" << m_io->fileName() << '\'' << endl; + + // magic + m_stream >> u16; + if (u16 != 0x01da) + return false; + + // verbatim/rle + m_stream >> m_rle; + kdDebug(399) << (m_rle ? "RLE" : "verbatim") << endl; + if (m_rle > 1) + return false; + + // bytes per channel + m_stream >> m_bpc; + kdDebug(399) << "bytes per channel: " << int(m_bpc) << endl; + if (m_bpc == 1) + ; + else if (m_bpc == 2) + kdDebug(399) << "dropping least significant byte" << endl; + else + return false; + + // number of dimensions + m_stream >> m_dim; + kdDebug(399) << "dimensions: " << m_dim << endl; + if (m_dim < 1 || m_dim > 3) + return false; + + m_stream >> m_xsize >> m_ysize >> m_zsize >> m_pixmin >> m_pixmax >> u32; + kdDebug(399) << "x: " << m_xsize << endl; + kdDebug(399) << "y: " << m_ysize << endl; + kdDebug(399) << "z: " << m_zsize << endl; + + // name + m_stream.readRawBytes(m_imagename, 80); + m_imagename[79] = '\0'; + m_io->setDescription(m_imagename); + + m_stream >> m_colormap; + kdDebug(399) << "colormap: " << m_colormap << endl; + if (m_colormap != NORMAL) + return false; // only NORMAL supported + + for (int i = 0; i < 404; i++) + m_stream >> u8; + + if (m_dim == 1) { + kdDebug(399) << "1-dimensional images aren't supported yet" << endl; + return false; + } + + if( m_stream.atEnd()) + return false; + + m_numrows = m_ysize * m_zsize; + + if (!img.create(m_xsize, m_ysize, 32)) { + kdDebug(399) << "cannot create image" << endl; + return false; + } + + if (m_zsize == 2 || m_zsize == 4) + img.setAlphaBuffer(true); + else if (m_zsize > 4) + kdDebug(399) << "using first 4 of " << m_zsize << " channels" << endl; + + if (m_rle) { + uint l; + m_starttab = new Q_UINT32[m_numrows]; + for (l = 0; !m_stream.atEnd() && l < m_numrows; l++) { + m_stream >> m_starttab[l]; + m_starttab[l] -= 512 + m_numrows * 2 * sizeof(Q_UINT32); + } + + m_lengthtab = new Q_UINT32[m_numrows]; + for (l = 0; l < m_numrows; l++) + m_stream >> m_lengthtab[l]; + } + + m_data = m_dev->readAll(); + + // sanity check + if (m_rle) + for (uint o = 0; o < m_numrows; o++) + // don't change to greater-or-equal! + if (m_starttab[o] + m_lengthtab[o] > m_data.size()) { + kdDebug(399) << "image corrupt (sanity check failed)" << endl; + return false; + } + + if (!readData(img)) { + kdDebug(399) << "image corrupt (incomplete scanline)" << endl; + return false; + } + + return true; +} + + +/////////////////////////////////////////////////////////////////////////////// + + +// TODO remove; for debugging purposes only +void RLEData::print(QString desc) const +{ + QString s = desc + ": "; + for (uint i = 0; i < size(); i++) + s += QString::number(at(i)) + ","; + kdDebug() << "--- " << s << endl; +} + + +void RLEData::write(QDataStream& s) +{ + for (unsigned i = 0; i < size(); i++) + s << at(i); +} + + +bool RLEData::operator<(const RLEData& b) const +{ + uchar ac, bc; + for (unsigned i = 0; i < QMIN(size(), b.size()); i++) { + ac = at(i); + bc = b[i]; + if (ac != bc) + return ac < bc; + } + return size() < b.size(); +} + + +uint RLEMap::insert(const uchar *d, uint l) +{ + RLEData data = RLEData(d, l, m_offset); + Iterator it = find(data); + if (it != end()) + return it.data(); + + m_offset += l; + return QMap<RLEData, uint>::insert(data, m_counter++).data(); +} + + +QPtrVector<RLEData> RLEMap::vector() +{ + QPtrVector<RLEData> v(size()); + for (Iterator it = begin(); it != end(); ++it) + v.insert(it.data(), &it.key()); + + return v; +} + + +uchar SGIImage::intensity(uchar c) +{ + if (c < m_pixmin) + m_pixmin = c; + if (c > m_pixmax) + m_pixmax = c; + return c; +} + + +uint SGIImage::compact(uchar *d, uchar *s) +{ + uchar *dest = d, *src = s, patt, *t, *end = s + m_xsize; + int i, n; + while (src < end) { + for (n = 0, t = src; t + 2 < end && !(*t == t[1] && *t == t[2]); t++) + n++; + + while (n) { + i = n > 126 ? 126 : n; + n -= i; + *dest++ = 0x80 | i; + while (i--) + *dest++ = *src++; + } + + if (src == end) + break; + + patt = *src++; + for (n = 1; src < end && *src == patt; src++) + n++; + + while (n) { + i = n > 126 ? 126 : n; + n -= i; + *dest++ = i; + *dest++ = patt; + } + } + *dest++ = 0; + return dest - d; +} + + +bool SGIImage::scanData(const QImage& img) +{ + Q_UINT32 *start = m_starttab; + QCString lineguard(m_xsize * 2); + QCString bufguard(m_xsize); + uchar *line = (uchar *)lineguard.data(); + uchar *buf = (uchar *)bufguard.data(); + QRgb *c; + unsigned x, y; + uint len; + + for (y = 0; y < m_ysize; y++) { + c = reinterpret_cast<QRgb *>(img.scanLine(m_ysize - y - 1)); + for (x = 0; x < m_xsize; x++) + buf[x] = intensity(qRed(*c++)); + len = compact(line, buf); + *start++ = m_rlemap.insert(line, len); + } + + if (m_zsize == 1) + return true; + + if (m_zsize != 2) { + for (y = 0; y < m_ysize; y++) { + c = reinterpret_cast<QRgb *>(img.scanLine(m_ysize - y - 1)); + for (x = 0; x < m_xsize; x++) + buf[x] = intensity(qGreen(*c++)); + len = compact(line, buf); + *start++ = m_rlemap.insert(line, len); + } + + for (y = 0; y < m_ysize; y++) { + c = reinterpret_cast<QRgb *>(img.scanLine(m_ysize - y - 1)); + for (x = 0; x < m_xsize; x++) + buf[x] = intensity(qBlue(*c++)); + len = compact(line, buf); + *start++ = m_rlemap.insert(line, len); + } + + if (m_zsize == 3) + return true; + } + + for (y = 0; y < m_ysize; y++) { + c = reinterpret_cast<QRgb *>(img.scanLine(m_ysize - y - 1)); + for (x = 0; x < m_xsize; x++) + buf[x] = intensity(qAlpha(*c++)); + len = compact(line, buf); + *start++ = m_rlemap.insert(line, len); + } + + return true; +} + + +void SGIImage::writeHeader() +{ + m_stream << Q_UINT16(0x01da); + m_stream << m_rle << m_bpc << m_dim; + m_stream << m_xsize << m_ysize << m_zsize; + m_stream << m_pixmin << m_pixmax; + m_stream << Q_UINT32(0); + + uint i; + QString desc = m_io->description(); + kdDebug(399) << "Description: " << desc << endl; + desc.truncate(79); + + for (i = 0; i < desc.length(); i++) + m_imagename[i] = desc.latin1()[i]; + for (; i < 80; i++) + m_imagename[i] = '\0'; + m_stream.writeRawBytes(m_imagename, 80); + + m_stream << m_colormap; + for (i = 0; i < 404; i++) + m_stream << Q_UINT8(0); +} + + +void SGIImage::writeRle() +{ + m_rle = 1; + kdDebug(399) << "writing RLE data" << endl; + writeHeader(); + uint i; + + // write start table + for (i = 0; i < m_numrows; i++) + m_stream << Q_UINT32(m_rlevector[m_starttab[i]]->offset()); + + // write length table + for (i = 0; i < m_numrows; i++) + m_stream << Q_UINT32(m_rlevector[m_starttab[i]]->size()); + + // write data + for (i = 0; i < m_rlevector.size(); i++) + m_rlevector[i]->write(m_stream); +} + + +void SGIImage::writeVerbatim(const QImage& img) +{ + m_rle = 0; + kdDebug(399) << "writing verbatim data" << endl; + writeHeader(); + + QRgb *c; + unsigned x, y; + + for (y = 0; y < m_ysize; y++) { + c = reinterpret_cast<QRgb *>(img.scanLine(m_ysize - y - 1)); + for (x = 0; x < m_xsize; x++) + m_stream << Q_UINT8(qRed(*c++)); + } + + if (m_zsize == 1) + return; + + if (m_zsize != 2) { + for (y = 0; y < m_ysize; y++) { + c = reinterpret_cast<QRgb *>(img.scanLine(m_ysize - y - 1)); + for (x = 0; x < m_xsize; x++) + m_stream << Q_UINT8(qGreen(*c++)); + } + + for (y = 0; y < m_ysize; y++) { + c = reinterpret_cast<QRgb *>(img.scanLine(m_ysize - y - 1)); + for (x = 0; x < m_xsize; x++) + m_stream << Q_UINT8(qBlue(*c++)); + } + + if (m_zsize == 3) + return; + } + + for (y = 0; y < m_ysize; y++) { + c = reinterpret_cast<QRgb *>(img.scanLine(m_ysize - y - 1)); + for (x = 0; x < m_xsize; x++) + m_stream << Q_UINT8(qAlpha(*c++)); + } +} + + +bool SGIImage::writeImage(QImage& img) +{ + kdDebug(399) << "writing '" << m_io->fileName() << '\'' << endl; + + if (img.allGray()) + m_dim = 2, m_zsize = 1; + else + m_dim = 3, m_zsize = 3; + + if (img.hasAlphaBuffer()) + m_dim = 3, m_zsize++; + + img = img.convertDepth(32); + if (img.isNull()) { + kdDebug(399) << "can't convert image to depth 32" << endl; + return false; + } + + m_bpc = 1; + m_xsize = img.width(); + m_ysize = img.height(); + m_pixmin = ~0; + m_pixmax = 0; + m_colormap = NORMAL; + + m_numrows = m_ysize * m_zsize; + + m_starttab = new Q_UINT32[m_numrows]; + m_rlemap.setBaseOffset(512 + m_numrows * 2 * sizeof(Q_UINT32)); + + if (!scanData(img)) { + kdDebug(399) << "this can't happen" << endl; + return false; + } + + m_rlevector = m_rlemap.vector(); + + long verbatim_size = m_numrows * m_xsize; + long rle_size = m_numrows * 2 * sizeof(Q_UINT32); + for (uint i = 0; i < m_rlevector.size(); i++) + rle_size += m_rlevector[i]->size(); + + kdDebug(399) << "minimum intensity: " << m_pixmin << endl; + kdDebug(399) << "maximum intensity: " << m_pixmax << endl; + kdDebug(399) << "saved scanlines: " << m_numrows - m_rlemap.size() << endl; + kdDebug(399) << "total savings: " << (verbatim_size - rle_size) << " bytes" << endl; + kdDebug(399) << "compression: " << (rle_size * 100.0 / verbatim_size) << '%' << endl; + + if (verbatim_size <= rle_size || m_io->quality() > 50) + writeVerbatim(img); + else + writeRle(); + return true; +} + + |