rgb.cpp
// 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;
}

KDE Home | KDE Accessibility Home | Description of Access Keys