File indexing completed on 2024-04-28 15:25:45

0001 /*
0002     kimgio module for SGI images
0003     SPDX-FileCopyrightText: 2004 Melchior FRANZ <mfranz@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 /* this code supports:
0009  * reading:
0010  *     everything, except images with 1 dimension or images with
0011  *     mapmode != NORMAL (e.g. dithered); Images with 16 bit
0012  *     precision or more than 4 layers are stripped down.
0013  * writing:
0014  *     Run Length Encoded (RLE) or Verbatim (uncompressed)
0015  *     (whichever is smaller)
0016  *
0017  * Please report if you come across rgb/rgba/sgi/bw files that aren't
0018  * recognized. Also report applications that can't deal with images
0019  * saved by this filter.
0020  */
0021 
0022 #include "rgb_p.h"
0023 #include "util_p.h"
0024 
0025 #include <QMap>
0026 #include <QVector>
0027 
0028 #include <QDebug>
0029 #include <QImage>
0030 
0031 class RLEData : public QVector<uchar>
0032 {
0033 public:
0034     RLEData()
0035     {
0036     }
0037     RLEData(const uchar *d, uint l, uint o)
0038         : _offset(o)
0039     {
0040         for (uint i = 0; i < l; i++) {
0041             append(d[i]);
0042         }
0043     }
0044     bool operator<(const RLEData &) const;
0045     void write(QDataStream &s);
0046     uint offset() const
0047     {
0048         return _offset;
0049     }
0050 
0051 private:
0052     uint _offset;
0053 };
0054 
0055 class RLEMap : public QMap<RLEData, uint>
0056 {
0057 public:
0058     RLEMap()
0059         : _counter(0)
0060         , _offset(0)
0061     {
0062     }
0063     uint insert(const uchar *d, uint l);
0064     QVector<const RLEData *> vector();
0065     void setBaseOffset(uint o)
0066     {
0067         _offset = o;
0068     }
0069 
0070 private:
0071     uint _counter;
0072     uint _offset;
0073 };
0074 
0075 class SGIImage
0076 {
0077 public:
0078     SGIImage(QIODevice *device);
0079     ~SGIImage();
0080 
0081     bool readImage(QImage &);
0082     bool writeImage(const QImage &);
0083 
0084 private:
0085     enum {
0086         NORMAL,
0087         DITHERED,
0088         SCREEN,
0089         COLORMAP,
0090     }; // colormap
0091     QIODevice *_dev;
0092     QDataStream _stream;
0093 
0094     quint8 _rle;
0095     quint8 _bpc;
0096     quint16 _dim;
0097     quint16 _xsize;
0098     quint16 _ysize;
0099     quint16 _zsize;
0100     quint32 _pixmin;
0101     quint32 _pixmax;
0102     char _imagename[80];
0103     quint32 _colormap;
0104 
0105     quint32 *_starttab;
0106     quint32 *_lengthtab;
0107     QByteArray _data;
0108     QByteArray::Iterator _pos;
0109     RLEMap _rlemap;
0110     QVector<const RLEData *> _rlevector;
0111     uint _numrows;
0112 
0113     bool readData(QImage &);
0114     bool getRow(uchar *dest);
0115 
0116     void writeHeader();
0117     void writeRle();
0118     void writeVerbatim(const QImage &);
0119     bool scanData(const QImage &);
0120     uint compact(uchar *, uchar *);
0121     uchar intensity(uchar);
0122 };
0123 
0124 SGIImage::SGIImage(QIODevice *io)
0125     : _starttab(nullptr)
0126     , _lengthtab(nullptr)
0127 {
0128     _dev = io;
0129     _stream.setDevice(_dev);
0130 }
0131 
0132 SGIImage::~SGIImage()
0133 {
0134     delete[] _starttab;
0135     delete[] _lengthtab;
0136 }
0137 
0138 ///////////////////////////////////////////////////////////////////////////////
0139 
0140 bool SGIImage::getRow(uchar *dest)
0141 {
0142     int n;
0143     int i;
0144     if (!_rle) {
0145         for (i = 0; i < _xsize; i++) {
0146             if (_pos >= _data.end()) {
0147                 return false;
0148             }
0149             dest[i] = uchar(*_pos);
0150             _pos += _bpc;
0151         }
0152         return true;
0153     }
0154 
0155     for (i = 0; i < _xsize;) {
0156         if (_bpc == 2) {
0157             _pos++;
0158         }
0159         if (_pos >= _data.end()) {
0160             return false;
0161         }
0162         n = *_pos & 0x7f;
0163         if (!n) {
0164             break;
0165         }
0166 
0167         if (*_pos++ & 0x80) {
0168             for (; i < _xsize && _pos < _data.end() && n--; i++) {
0169                 *dest++ = *_pos;
0170                 _pos += _bpc;
0171             }
0172         } else {
0173             for (; i < _xsize && n--; i++) {
0174                 *dest++ = *_pos;
0175             }
0176 
0177             _pos += _bpc;
0178         }
0179     }
0180     return i == _xsize;
0181 }
0182 
0183 bool SGIImage::readData(QImage &img)
0184 {
0185     QRgb *c;
0186     quint32 *start = _starttab;
0187     QByteArray lguard(_xsize, 0);
0188     uchar *line = (uchar *)lguard.data();
0189     unsigned x;
0190     unsigned y;
0191 
0192     if (!_rle) {
0193         _pos = _data.begin();
0194     }
0195 
0196     for (y = 0; y < _ysize; y++) {
0197         if (_rle) {
0198             _pos = _data.begin() + *start++;
0199         }
0200         if (!getRow(line)) {
0201             return false;
0202         }
0203         c = (QRgb *)img.scanLine(_ysize - y - 1);
0204         for (x = 0; x < _xsize; x++, c++) {
0205             *c = qRgb(line[x], line[x], line[x]);
0206         }
0207     }
0208 
0209     if (_zsize == 1) {
0210         return true;
0211     }
0212 
0213     if (_zsize != 2) {
0214         for (y = 0; y < _ysize; y++) {
0215             if (_rle) {
0216                 _pos = _data.begin() + *start++;
0217             }
0218             if (!getRow(line)) {
0219                 return false;
0220             }
0221             c = (QRgb *)img.scanLine(_ysize - y - 1);
0222             for (x = 0; x < _xsize; x++, c++) {
0223                 *c = qRgb(qRed(*c), line[x], line[x]);
0224             }
0225         }
0226 
0227         for (y = 0; y < _ysize; y++) {
0228             if (_rle) {
0229                 _pos = _data.begin() + *start++;
0230             }
0231             if (!getRow(line)) {
0232                 return false;
0233             }
0234             c = (QRgb *)img.scanLine(_ysize - y - 1);
0235             for (x = 0; x < _xsize; x++, c++) {
0236                 *c = qRgb(qRed(*c), qGreen(*c), line[x]);
0237             }
0238         }
0239 
0240         if (_zsize == 3) {
0241             return true;
0242         }
0243     }
0244 
0245     for (y = 0; y < _ysize; y++) {
0246         if (_rle) {
0247             _pos = _data.begin() + *start++;
0248         }
0249         if (!getRow(line)) {
0250             return false;
0251         }
0252         c = (QRgb *)img.scanLine(_ysize - y - 1);
0253         for (x = 0; x < _xsize; x++, c++) {
0254             *c = qRgba(qRed(*c), qGreen(*c), qBlue(*c), line[x]);
0255         }
0256     }
0257 
0258     return true;
0259 }
0260 
0261 bool SGIImage::readImage(QImage &img)
0262 {
0263     qint8 u8;
0264     qint16 u16;
0265     qint32 u32;
0266 
0267     //     qDebug() << "reading rgb ";
0268 
0269     // magic
0270     _stream >> u16;
0271     if (u16 != 0x01da) {
0272         return false;
0273     }
0274 
0275     // verbatim/rle
0276     _stream >> _rle;
0277     //     qDebug() << (_rle ? "RLE" : "verbatim");
0278     if (_rle > 1) {
0279         return false;
0280     }
0281 
0282     // bytes per channel
0283     _stream >> _bpc;
0284     //     qDebug() << "bytes per channel: " << int(_bpc);
0285     if (_bpc == 1) {
0286         ;
0287     } else if (_bpc == 2) {
0288         //         qDebug() << "dropping least significant byte";
0289     } else {
0290         return false;
0291     }
0292 
0293     // number of dimensions
0294     _stream >> _dim;
0295     //     qDebug() << "dimensions: " << _dim;
0296     if (_dim < 1 || _dim > 3) {
0297         return false;
0298     }
0299 
0300     _stream >> _xsize >> _ysize >> _zsize >> _pixmin >> _pixmax >> u32;
0301     //     qDebug() << "x: " << _xsize;
0302     //     qDebug() << "y: " << _ysize;
0303     //     qDebug() << "z: " << _zsize;
0304 
0305     // name
0306     _stream.readRawData(_imagename, 80);
0307     _imagename[79] = '\0';
0308 
0309     _stream >> _colormap;
0310     //     qDebug() << "colormap: " << _colormap;
0311     if (_colormap != NORMAL) {
0312         return false; // only NORMAL supported
0313     }
0314 
0315     for (int i = 0; i < 404; i++) {
0316         _stream >> u8;
0317     }
0318 
0319     if (_dim == 1) {
0320         //         qDebug() << "1-dimensional images aren't supported yet";
0321         return false;
0322     }
0323 
0324     if (_stream.atEnd()) {
0325         return false;
0326     }
0327 
0328     img = imageAlloc(_xsize, _ysize, QImage::Format_RGB32);
0329     if (img.isNull()) {
0330         qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(_xsize, _ysize);
0331         return false;
0332     }
0333 
0334     if (_zsize == 0) {
0335         return false;
0336     }
0337 
0338     if (_zsize == 2 || _zsize == 4) {
0339         img = img.convertToFormat(QImage::Format_ARGB32);
0340     } else if (_zsize > 4) {
0341         //         qDebug() << "using first 4 of " << _zsize << " channels";
0342         // Only let this continue if it won't cause a int overflow later
0343         // this is most likely a broken file anyway
0344         if (_ysize > std::numeric_limits<int>::max() / _zsize) {
0345             return false;
0346         }
0347     }
0348 
0349     _numrows = _ysize * _zsize;
0350 
0351     if (_rle) {
0352         uint l;
0353         _starttab = new quint32[_numrows];
0354         for (l = 0; !_stream.atEnd() && l < _numrows; l++) {
0355             _stream >> _starttab[l];
0356             _starttab[l] -= 512 + _numrows * 2 * sizeof(quint32);
0357         }
0358         for (; l < _numrows; l++) {
0359             _starttab[l] = 0;
0360         }
0361 
0362         _lengthtab = new quint32[_numrows];
0363         for (l = 0; l < _numrows; l++) {
0364             _stream >> _lengthtab[l];
0365         }
0366     }
0367 
0368     _data = _dev->readAll();
0369 
0370     // sanity check
0371     if (_rle) {
0372         for (uint o = 0; o < _numrows; o++) {
0373             // don't change to greater-or-equal!
0374             if (_starttab[o] + _lengthtab[o] > (uint)_data.size()) {
0375                 //                 qDebug() << "image corrupt (sanity check failed)";
0376                 return false;
0377             }
0378         }
0379     }
0380 
0381     if (!readData(img)) {
0382         //         qDebug() << "image corrupt (incomplete scanline)";
0383         return false;
0384     }
0385 
0386     return true;
0387 }
0388 
0389 ///////////////////////////////////////////////////////////////////////////////
0390 
0391 void RLEData::write(QDataStream &s)
0392 {
0393     for (int i = 0; i < size(); i++) {
0394         s << at(i);
0395     }
0396 }
0397 
0398 bool RLEData::operator<(const RLEData &b) const
0399 {
0400     uchar ac;
0401     uchar bc;
0402     for (int i = 0; i < qMin(size(), b.size()); i++) {
0403         ac = at(i);
0404         bc = b[i];
0405         if (ac != bc) {
0406             return ac < bc;
0407         }
0408     }
0409     return size() < b.size();
0410 }
0411 
0412 uint RLEMap::insert(const uchar *d, uint l)
0413 {
0414     RLEData data = RLEData(d, l, _offset);
0415     Iterator it = find(data);
0416     if (it != end()) {
0417         return it.value();
0418     }
0419 
0420     _offset += l;
0421     return QMap<RLEData, uint>::insert(data, _counter++).value();
0422 }
0423 
0424 QVector<const RLEData *> RLEMap::vector()
0425 {
0426     QVector<const RLEData *> v(size());
0427     for (Iterator it = begin(); it != end(); ++it) {
0428         v.replace(it.value(), &it.key());
0429     }
0430 
0431     return v;
0432 }
0433 
0434 uchar SGIImage::intensity(uchar c)
0435 {
0436     if (c < _pixmin) {
0437         _pixmin = c;
0438     }
0439     if (c > _pixmax) {
0440         _pixmax = c;
0441     }
0442     return c;
0443 }
0444 
0445 uint SGIImage::compact(uchar *d, uchar *s)
0446 {
0447     uchar *dest = d;
0448     uchar *src = s;
0449     uchar patt;
0450     uchar *t;
0451     uchar *end = s + _xsize;
0452     int i;
0453     int n;
0454     while (src < end) {
0455         for (n = 0, t = src; t + 2 < end && !(*t == t[1] && *t == t[2]); t++) {
0456             n++;
0457         }
0458 
0459         while (n) {
0460             i = n > 126 ? 126 : n;
0461             n -= i;
0462             *dest++ = 0x80 | i;
0463             while (i--) {
0464                 *dest++ = *src++;
0465             }
0466         }
0467 
0468         if (src == end) {
0469             break;
0470         }
0471 
0472         patt = *src++;
0473         for (n = 1; src < end && *src == patt; src++) {
0474             n++;
0475         }
0476 
0477         while (n) {
0478             i = n > 126 ? 126 : n;
0479             n -= i;
0480             *dest++ = i;
0481             *dest++ = patt;
0482         }
0483     }
0484     *dest++ = 0;
0485     return dest - d;
0486 }
0487 
0488 bool SGIImage::scanData(const QImage &img)
0489 {
0490     quint32 *start = _starttab;
0491     QByteArray lineguard(_xsize * 2, 0);
0492     QByteArray bufguard(_xsize, 0);
0493     uchar *line = (uchar *)lineguard.data();
0494     uchar *buf = (uchar *)bufguard.data();
0495     const QRgb *c;
0496     unsigned x;
0497     unsigned y;
0498     uint len;
0499 
0500     for (y = 0; y < _ysize; y++) {
0501         const int yPos = _ysize - y - 1; // scanline doesn't do any sanity checking
0502         if (yPos >= img.height()) {
0503             qWarning() << "Failed to get scanline for" << yPos;
0504             return false;
0505         }
0506 
0507         c = reinterpret_cast<const QRgb *>(img.scanLine(yPos));
0508 
0509         for (x = 0; x < _xsize; x++) {
0510             buf[x] = intensity(qRed(*c++));
0511         }
0512         len = compact(line, buf);
0513         *start++ = _rlemap.insert(line, len);
0514     }
0515 
0516     if (_zsize == 1) {
0517         return true;
0518     }
0519 
0520     if (_zsize != 2) {
0521         for (y = 0; y < _ysize; y++) {
0522             const int yPos = _ysize - y - 1;
0523             if (yPos >= img.height()) {
0524                 qWarning() << "Failed to get scanline for" << yPos;
0525                 return false;
0526             }
0527 
0528             c = reinterpret_cast<const QRgb *>(img.scanLine(yPos));
0529             for (x = 0; x < _xsize; x++) {
0530                 buf[x] = intensity(qGreen(*c++));
0531             }
0532             len = compact(line, buf);
0533             *start++ = _rlemap.insert(line, len);
0534         }
0535 
0536         for (y = 0; y < _ysize; y++) {
0537             const int yPos = _ysize - y - 1;
0538             if (yPos >= img.height()) {
0539                 qWarning() << "Failed to get scanline for" << yPos;
0540                 return false;
0541             }
0542 
0543             c = reinterpret_cast<const QRgb *>(img.scanLine(yPos));
0544             for (x = 0; x < _xsize; x++) {
0545                 buf[x] = intensity(qBlue(*c++));
0546             }
0547             len = compact(line, buf);
0548             *start++ = _rlemap.insert(line, len);
0549         }
0550 
0551         if (_zsize == 3) {
0552             return true;
0553         }
0554     }
0555 
0556     for (y = 0; y < _ysize; y++) {
0557         const int yPos = _ysize - y - 1;
0558         if (yPos >= img.height()) {
0559             qWarning() << "Failed to get scanline for" << yPos;
0560             return false;
0561         }
0562 
0563         c = reinterpret_cast<const QRgb *>(img.scanLine(yPos));
0564         for (x = 0; x < _xsize; x++) {
0565             buf[x] = intensity(qAlpha(*c++));
0566         }
0567         len = compact(line, buf);
0568         *start++ = _rlemap.insert(line, len);
0569     }
0570 
0571     return true;
0572 }
0573 
0574 void SGIImage::writeHeader()
0575 {
0576     _stream << quint16(0x01da);
0577     _stream << _rle << _bpc << _dim;
0578     _stream << _xsize << _ysize << _zsize;
0579     _stream << _pixmin << _pixmax;
0580     _stream << quint32(0);
0581 
0582     for (int i = 0; i < 80; i++) {
0583         _imagename[i] = '\0';
0584     }
0585     _stream.writeRawData(_imagename, 80);
0586 
0587     _stream << _colormap;
0588     for (int i = 0; i < 404; i++) {
0589         _stream << quint8(0);
0590     }
0591 }
0592 
0593 void SGIImage::writeRle()
0594 {
0595     _rle = 1;
0596     //     qDebug() << "writing RLE data";
0597     writeHeader();
0598     uint i;
0599 
0600     // write start table
0601     for (i = 0; i < _numrows; i++) {
0602         _stream << quint32(_rlevector[_starttab[i]]->offset());
0603     }
0604 
0605     // write length table
0606     for (i = 0; i < _numrows; i++) {
0607         _stream << quint32(_rlevector[_starttab[i]]->size());
0608     }
0609 
0610     // write data
0611     for (i = 0; (int)i < _rlevector.size(); i++) {
0612         const_cast<RLEData *>(_rlevector[i])->write(_stream);
0613     }
0614 }
0615 
0616 void SGIImage::writeVerbatim(const QImage &img)
0617 {
0618     _rle = 0;
0619     //     qDebug() << "writing verbatim data";
0620     writeHeader();
0621 
0622     const QRgb *c;
0623     unsigned x;
0624     unsigned y;
0625 
0626     for (y = 0; y < _ysize; y++) {
0627         c = reinterpret_cast<const QRgb *>(img.scanLine(_ysize - y - 1));
0628         for (x = 0; x < _xsize; x++) {
0629             _stream << quint8(qRed(*c++));
0630         }
0631     }
0632 
0633     if (_zsize == 1) {
0634         return;
0635     }
0636 
0637     if (_zsize != 2) {
0638         for (y = 0; y < _ysize; y++) {
0639             c = reinterpret_cast<const QRgb *>(img.scanLine(_ysize - y - 1));
0640             for (x = 0; x < _xsize; x++) {
0641                 _stream << quint8(qGreen(*c++));
0642             }
0643         }
0644 
0645         for (y = 0; y < _ysize; y++) {
0646             c = reinterpret_cast<const QRgb *>(img.scanLine(_ysize - y - 1));
0647             for (x = 0; x < _xsize; x++) {
0648                 _stream << quint8(qBlue(*c++));
0649             }
0650         }
0651 
0652         if (_zsize == 3) {
0653             return;
0654         }
0655     }
0656 
0657     for (y = 0; y < _ysize; y++) {
0658         c = reinterpret_cast<const QRgb *>(img.scanLine(_ysize - y - 1));
0659         for (x = 0; x < _xsize; x++) {
0660             _stream << quint8(qAlpha(*c++));
0661         }
0662     }
0663 }
0664 
0665 bool SGIImage::writeImage(const QImage &image)
0666 {
0667     //     qDebug() << "writing "; // TODO add filename
0668     QImage img = image;
0669     if (img.allGray()) {
0670         _dim = 2, _zsize = 1;
0671     } else {
0672         _dim = 3, _zsize = 3;
0673     }
0674 
0675     auto hasAlpha = img.hasAlphaChannel();
0676     if (hasAlpha) {
0677         _dim = 3, _zsize++;
0678     }
0679 
0680     if (hasAlpha && img.format() != QImage::Format_ARGB32) {
0681         img = img.convertToFormat(QImage::Format_ARGB32);
0682     } else if (!hasAlpha && img.format() != QImage::Format_RGB32) {
0683         img = img.convertToFormat(QImage::Format_RGB32);
0684     }
0685     if (img.isNull()) {
0686         //         qDebug() << "can't convert image to depth 32";
0687         return false;
0688     }
0689 
0690     const int w = img.width();
0691     const int h = img.height();
0692 
0693     if (w > 65535 || h > 65535) {
0694         return false;
0695     }
0696 
0697     _bpc = 1;
0698     _xsize = w;
0699     _ysize = h;
0700     _pixmin = ~0u;
0701     _pixmax = 0;
0702     _colormap = NORMAL;
0703     _numrows = _ysize * _zsize;
0704     _starttab = new quint32[_numrows];
0705     _rlemap.setBaseOffset(512 + _numrows * 2 * sizeof(quint32));
0706 
0707     if (!scanData(img)) {
0708         //         qDebug() << "this can't happen";
0709         return false;
0710     }
0711 
0712     _rlevector = _rlemap.vector();
0713 
0714     long verbatim_size = _numrows * _xsize;
0715     long rle_size = _numrows * 2 * sizeof(quint32);
0716     for (int i = 0; i < _rlevector.size(); i++) {
0717         rle_size += _rlevector[i]->size();
0718     }
0719 
0720     if (verbatim_size <= rle_size) {
0721         writeVerbatim(img);
0722     } else {
0723         writeRle();
0724     }
0725     return true;
0726 }
0727 
0728 ///////////////////////////////////////////////////////////////////////////////
0729 
0730 RGBHandler::RGBHandler()
0731 {
0732 }
0733 
0734 bool RGBHandler::canRead() const
0735 {
0736     if (canRead(device())) {
0737         setFormat("rgb");
0738         return true;
0739     }
0740     return false;
0741 }
0742 
0743 bool RGBHandler::read(QImage *outImage)
0744 {
0745     SGIImage sgi(device());
0746     return sgi.readImage(*outImage);
0747 }
0748 
0749 bool RGBHandler::write(const QImage &image)
0750 {
0751     SGIImage sgi(device());
0752     return sgi.writeImage(image);
0753 }
0754 
0755 bool RGBHandler::canRead(QIODevice *device)
0756 {
0757     if (!device) {
0758         qWarning("RGBHandler::canRead() called with no device");
0759         return false;
0760     }
0761 
0762     const qint64 oldPos = device->pos();
0763     const QByteArray head = device->readLine(64);
0764     int readBytes = head.size();
0765 
0766     if (device->isSequential()) {
0767         while (readBytes > 0) {
0768             device->ungetChar(head[readBytes-- - 1]);
0769         }
0770 
0771     } else {
0772         device->seek(oldPos);
0773     }
0774 
0775     return head.size() >= 4 && head.startsWith("\x01\xda") && (head[2] == 0 || head[2] == 1) && (head[3] == 1 || head[3] == 2);
0776 }
0777 
0778 ///////////////////////////////////////////////////////////////////////////////
0779 
0780 QImageIOPlugin::Capabilities RGBPlugin::capabilities(QIODevice *device, const QByteArray &format) const
0781 {
0782     if (format == "rgb" || format == "rgba" || format == "bw" || format == "sgi") {
0783         return Capabilities(CanRead | CanWrite);
0784     }
0785     if (!format.isEmpty()) {
0786         return {};
0787     }
0788     if (!device->isOpen()) {
0789         return {};
0790     }
0791 
0792     Capabilities cap;
0793     if (device->isReadable() && RGBHandler::canRead(device)) {
0794         cap |= CanRead;
0795     }
0796     if (device->isWritable()) {
0797         cap |= CanWrite;
0798     }
0799     return cap;
0800 }
0801 
0802 QImageIOHandler *RGBPlugin::create(QIODevice *device, const QByteArray &format) const
0803 {
0804     QImageIOHandler *handler = new RGBHandler;
0805     handler->setDevice(device);
0806     handler->setFormat(format);
0807     return handler;
0808 }
0809 
0810 #include "moc_rgb_p.cpp"