// kimgio module for SGI images // // Copyright (C) 2004 Melchior FRANZ // // 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 #include /////////////////////////////////////////////////////////////////////////////// KDE_EXPORT void kimgio_rgb_read(TQImageIO *io) { SGIImage sgi(io); TQImage img; if (!sgi.readImage(img)) { io->setImage(TQImage()); io->setqStatus(-1); return; } io->setImage(img); io->setqStatus(0); } KDE_EXPORT void kimgio_rgb_write(TQImageIO *io) { SGIImage sgi(io); TQImage img = io->image(); if (!sgi.writeImage(img)) io->setqStatus(-1); io->setqStatus(0); } /////////////////////////////////////////////////////////////////////////////// SGIImage::SGIImage(TQImageIO *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(TQImage& img) { QRgb *c; TQ_UINT32 *start = m_starttab; TQByteArray 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 = tqRgb(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 = tqRgb(tqRed(*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 = tqRgb(tqRed(*c), tqGreen(*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 = tqRgba(tqRed(*c), tqGreen(*c), tqBlue(*c), line[x]); } return true; } bool SGIImage::readImage(TQImage& img) { TQ_INT8 u8; TQ_INT16 u16; TQ_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 TQ_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(TQ_UINT32); } m_lengthtab = new TQ_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(TQString desc) const { TQString s = desc + ": "; for (uint i = 0; i < size(); i++) s += TQString::number(at(i)) + ","; kdDebug() << "--- " << s << endl; } void RLEData::write(TQDataStream& 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 TQMap::insert(data, m_counter++).data(); } TQPtrVector RLEMap::vector() { TQPtrVector 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 TQImage& img) { TQ_UINT32 *start = m_starttab; TQCString lineguard(m_xsize * 2); TQCString 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(const_cast(img).scanLine(m_ysize - y - 1)); for (x = 0; x < m_xsize; x++) buf[x] = intensity(tqRed(*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(const_cast(img).scanLine(m_ysize - y - 1)); for (x = 0; x < m_xsize; x++) buf[x] = intensity(tqGreen(*c++)); len = compact(line, buf); *start++ = m_rlemap.insert(line, len); } for (y = 0; y < m_ysize; y++) { c = reinterpret_cast(const_cast(img).scanLine(m_ysize - y - 1)); for (x = 0; x < m_xsize; x++) buf[x] = intensity(tqBlue(*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(const_cast(img).scanLine(m_ysize - y - 1)); for (x = 0; x < m_xsize; x++) buf[x] = intensity(tqAlpha(*c++)); len = compact(line, buf); *start++ = m_rlemap.insert(line, len); } return true; } void SGIImage::writeHeader() { m_stream << TQ_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 << TQ_UINT32(0); uint i; TQString 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 << TQ_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 << TQ_UINT32(m_rlevector[m_starttab[i]]->offset()); // write length table for (i = 0; i < m_numrows; i++) m_stream << TQ_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 TQImage& 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(const_cast(img).scanLine(m_ysize - y - 1)); for (x = 0; x < m_xsize; x++) m_stream << TQ_UINT8(tqRed(*c++)); } if (m_zsize == 1) return; if (m_zsize != 2) { for (y = 0; y < m_ysize; y++) { c = reinterpret_cast(const_cast(img).scanLine(m_ysize - y - 1)); for (x = 0; x < m_xsize; x++) m_stream << TQ_UINT8(tqGreen(*c++)); } for (y = 0; y < m_ysize; y++) { c = reinterpret_cast(const_cast(img).scanLine(m_ysize - y - 1)); for (x = 0; x < m_xsize; x++) m_stream << TQ_UINT8(tqBlue(*c++)); } if (m_zsize == 3) return; } for (y = 0; y < m_ysize; y++) { c = reinterpret_cast(const_cast(img).scanLine(m_ysize - y - 1)); for (x = 0; x < m_xsize; x++) m_stream << TQ_UINT8(tqAlpha(*c++)); } } bool SGIImage::writeImage(TQImage& 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 TQ_UINT32[m_numrows]; m_rlemap.setBaseOffset(512 + m_numrows * 2 * sizeof(TQ_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(TQ_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; }