You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
590 lines
12 KiB
590 lines
12 KiB
// 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 <tqimage.h>
|
|
#include <kdebug.h>
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
KDE_EXPORT void kimgio_rgb_read(TQImageIO *io)
|
|
{
|
|
SGIImage sgi(io);
|
|
TQImage img;
|
|
|
|
if (!sgi.readImage(img)) {
|
|
io->setImage(TQImage());
|
|
io->setStatus(-1);
|
|
return;
|
|
}
|
|
|
|
io->setImage(img);
|
|
io->setStatus(0);
|
|
}
|
|
|
|
|
|
KDE_EXPORT void kimgio_rgb_write(TQImageIO *io)
|
|
{
|
|
SGIImage sgi(io);
|
|
TQImage img = io->image();
|
|
|
|
if (!sgi.writeImage(img))
|
|
io->setStatus(-1);
|
|
|
|
io->setStatus(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 < TQMIN(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<RLEData, uint>::insert(data, m_counter++).data();
|
|
}
|
|
|
|
|
|
TQPtrVector<RLEData> RLEMap::vector()
|
|
{
|
|
TQPtrVector<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 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<QRgb *>(const_cast<TQImage&>(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<QRgb *>(const_cast<TQImage&>(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<QRgb *>(const_cast<TQImage&>(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<QRgb *>(const_cast<TQImage&>(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<QRgb *>(const_cast<TQImage&>(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<QRgb *>(const_cast<TQImage&>(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<QRgb *>(const_cast<TQImage&>(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<QRgb *>(const_cast<TQImage&>(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;
|
|
}
|
|
|
|
|