diff --git a/README.md b/README.md index 4d2bbbf..594fe51 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ The following image formats have read-only support: - OpenRaster (ora) - Photoshop documents (psd, psb, pdd, psdt) - Radiance HDR (hdr) -- Sun Raster (ras) +- Sun Raster (im1, im8, im24, im32, ras, sun) The following image formats have read and write support: diff --git a/autotests/read/ras/rle_1bit.png b/autotests/read/ras/rle_1bit.png new file mode 100644 index 0000000..2be59ad Binary files /dev/null and b/autotests/read/ras/rle_1bit.png differ diff --git a/autotests/read/ras/rle_1bit.ras b/autotests/read/ras/rle_1bit.ras new file mode 100644 index 0000000..5bec121 Binary files /dev/null and b/autotests/read/ras/rle_1bit.ras differ diff --git a/autotests/read/ras/rle_24bit.png b/autotests/read/ras/rle_24bit.png new file mode 100644 index 0000000..70b379b Binary files /dev/null and b/autotests/read/ras/rle_24bit.png differ diff --git a/autotests/read/ras/rle_24bit.ras b/autotests/read/ras/rle_24bit.ras new file mode 100644 index 0000000..e1db6c5 Binary files /dev/null and b/autotests/read/ras/rle_24bit.ras differ diff --git a/autotests/read/ras/rle_pal8bit.png b/autotests/read/ras/rle_pal8bit.png new file mode 100644 index 0000000..a92cd81 Binary files /dev/null and b/autotests/read/ras/rle_pal8bit.png differ diff --git a/autotests/read/ras/rle_pal8bit.ras b/autotests/read/ras/rle_pal8bit.ras new file mode 100644 index 0000000..fdc11b6 Binary files /dev/null and b/autotests/read/ras/rle_pal8bit.ras differ diff --git a/src/imageformats/ras.cpp b/src/imageformats/ras.cpp index 91a259d..b149909 100644 --- a/src/imageformats/ras.cpp +++ b/src/imageformats/ras.cpp @@ -40,14 +40,14 @@ enum RASColorMapType { }; struct RasHeader { - quint32 MagicNumber; - quint32 Width; - quint32 Height; - quint32 Depth; - quint32 Length; - quint32 Type; - quint32 ColorMapType; - quint32 ColorMapLength; + quint32 MagicNumber = 0; + quint32 Width = 0; + quint32 Height = 0; + quint32 Depth = 0; + quint32 Length = 0; + quint32 Type = 0; + quint32 ColorMapType = 0; + quint32 ColorMapLength = 0; enum { SIZE = 32, }; // 8 fields of four bytes each @@ -88,9 +88,9 @@ static bool IsSupported(const RasHeader &head) return false; } // the Type field adds support for RLE(BGR), RGB and other encodings - // we support Type 1: Normal(BGR) and Type 3: Normal(RGB) ONLY! - // TODO: add support for Type 2: RLE(BGR) & Type 4,5: TIFF/IFF - if (!(head.Type == RAS_TYPE_STANDARD || head.Type == RAS_TYPE_RGB_FORMAT)) { + // we support Type 1: Normal(BGR), Type 2: RLE(BGR) and Type 3: Normal(RGB) ONLY! + // TODO: add support for Type 4,5: TIFF/IFF + if (!(head.Type == RAS_TYPE_STANDARD || head.Type == RAS_TYPE_RGB_FORMAT || head.Type == RAS_TYPE_BYTE_ENCODED)) { return false; } return true; @@ -110,6 +110,96 @@ static QImage::Format imageFormat(const RasHeader &header) return QImage::Format_RGB32; } +class LineDecoder +{ +public: + LineDecoder(QIODevice *d, const RasHeader &ras) + : device(d) + , header(ras) + { + } + + QByteArray readLine(qint64 size) + { + /* *** uncompressed + */ + if (header.Type != RAS_TYPE_BYTE_ENCODED) { + return device->read(size); + } + + /* *** rle compressed + * The Run-length encoding (RLE) scheme optionally used in Sun Raster + * files (Type = 0002h) is used to encode bytes of image data + * separately. RLE encoding may be found in any Sun Raster file + * regardless of the type of image data it contains. + * + * The RLE packets are typically three bytes in size: + * - The first byte is a Flag Value indicating the type of RLE packet. + * - The second byte is the Run Count. + * - The third byte is the Run Value. + * + * A Flag Value of 80h is followed by a Run Count in the range of 01h + * to FFh. The Run Value follows the Run count and is in the range of + * 00h to FFh. The pixel run is the Run Value repeated Run Count times. + * There are two exceptions to this algorithm. First, if the Run Count + * following the Flag Value is 00h, this is an indication that the run + * is a single byte in length and has a value of 80h. And second, if + * the Flag Value is not 80h, then it is assumed that the data is + * unencoded pixel data and is written directly to the output stream. + * + * source: http://www.fileformat.info/format/sunraster/egff.htm + */ + for (qsizetype psz = 0, ptr = 0; uncBuffer.size() < size;) { + rleBuffer.append(device->read(std::min(qint64(32768), size))); + qsizetype sz = rleBuffer.size(); + if (psz == sz) { + break; // avoid infinite loop (data corrupted?!) + } + auto data = reinterpret_cast(rleBuffer.data()); + for (; ptr < sz;) { + auto flag = data[ptr++]; + if (flag == 0x80) { + if (ptr >= sz) { + ptr -= 1; + break; + } + auto cnt = data[ptr++]; + if (cnt == 0) { + uncBuffer.append(char(0x80)); + continue; + } else if (ptr >= sz) { + ptr -= 2; + break; + } + auto val = data[ptr++]; + uncBuffer.append(QByteArray(1 + cnt, char(val))); + } else { + uncBuffer.append(char(flag)); + } + } + if (ptr) { // remove consumed data + rleBuffer.remove(0, ptr); + ptr = 0; + } + psz = rleBuffer.size(); + } + if (uncBuffer.size() < size) { + return QByteArray(); // something wrong + } + auto line = uncBuffer.mid(0, size); + uncBuffer.remove(0, line.size()); // remove consumed data + return line; + } + +private: + QIODevice *device; + RasHeader header; + + // RLE decoding buffers + QByteArray rleBuffer; + QByteArray uncBuffer; +}; + static bool LoadRAS(QDataStream &s, const RasHeader &ras, QImage &img) { s.device()->seek(RasHeader::SIZE); @@ -148,11 +238,11 @@ static bool LoadRAS(QDataStream &s, const RasHeader &ras, QImage &img) } } - QByteArray rasLine(rasLineSize, 0); - auto bytesPerLine = std::min(img.bytesPerLine(), rasLine.size()); + LineDecoder dec(s.device(), ras); + auto bytesPerLine = std::min(img.bytesPerLine(), qsizetype(rasLineSize)); for (quint32 y = 0; y < ras.Height; ++y) { - auto read = s.readRawData(rasLine.data(), rasLine.size()); - if (read != rasLine.size()) { + auto rasLine = dec.readLine(rasLineSize); + if (rasLine.size() != rasLineSize) { qWarning() << "LoadRAS() unable to read line" << y << ": the seems corrupted!"; return false; }