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"