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

0001 /*
0002     This file is part of the KDE project
0003     SPDX-FileCopyrightText: 2003 Dominik Seichter <domseichter@web.de>
0004     SPDX-FileCopyrightText: 2004 Ignacio CastaƱo <castano@ludicon.com>
0005     SPDX-FileCopyrightText: 2010 Troy Unrau <troy@kde.org>
0006 
0007     SPDX-License-Identifier: LGPL-2.0-or-later
0008 */
0009 
0010 #include "ras_p.h"
0011 #include "util_p.h"
0012 
0013 #include <QDataStream>
0014 #include <QDebug>
0015 #include <QImage>
0016 
0017 namespace // Private.
0018 {
0019 // format info from http://www.fileformat.info/format/sunraster/egff.htm
0020 
0021 // Header format of saved files.
0022 quint32 rasMagicBigEndian = 0x59a66a95;
0023 // quint32 rasMagicLittleEndian = 0x956aa659; # used to support wrong encoded files
0024 
0025 enum RASType {
0026     RAS_TYPE_OLD = 0x0,
0027     RAS_TYPE_STANDARD = 0x1,
0028     RAS_TYPE_BYTE_ENCODED = 0x2,
0029     RAS_TYPE_RGB_FORMAT = 0x3,
0030     RAS_TYPE_TIFF_FORMAT = 0x4,
0031     RAS_TYPE_IFF_FORMAT = 0x5,
0032     RAS_TYPE_EXPERIMENTAL = 0xFFFF,
0033 };
0034 
0035 enum RASColorMapType {
0036     RAS_COLOR_MAP_TYPE_NONE = 0x0,
0037     RAS_COLOR_MAP_TYPE_RGB = 0x1,
0038     RAS_COLOR_MAP_TYPE_RAW = 0x2,
0039 };
0040 
0041 struct RasHeader {
0042     quint32 MagicNumber;
0043     quint32 Width;
0044     quint32 Height;
0045     quint32 Depth;
0046     quint32 Length;
0047     quint32 Type;
0048     quint32 ColorMapType;
0049     quint32 ColorMapLength;
0050     enum {
0051         SIZE = 32,
0052     }; // 8 fields of four bytes each
0053 };
0054 
0055 static QDataStream &operator>>(QDataStream &s, RasHeader &head)
0056 {
0057     s >> head.MagicNumber;
0058     s >> head.Width;
0059     s >> head.Height;
0060     s >> head.Depth;
0061     s >> head.Length;
0062     s >> head.Type;
0063     s >> head.ColorMapType;
0064     s >> head.ColorMapLength;
0065     /*qDebug() << "MagicNumber: " << head.MagicNumber
0066              << "Width: " << head.Width
0067              << "Height: " << head.Height
0068              << "Depth: " << head.Depth
0069              << "Length: " << head.Length
0070              << "Type: " << head.Type
0071              << "ColorMapType: " << head.ColorMapType
0072              << "ColorMapLength: " << head.ColorMapLength;*/
0073     return s;
0074 }
0075 
0076 static bool IsSupported(const RasHeader &head)
0077 {
0078     // check magic number
0079     if (head.MagicNumber != rasMagicBigEndian) {
0080         return false;
0081     }
0082     // check for an appropriate depth
0083     // we support 8bit+palette, 24bit and 32bit ONLY!
0084     // TODO: add support for 1bit
0085     if (!((head.Depth == 8 && head.ColorMapType == 1) || head.Depth == 24 || head.Depth == 32)) {
0086         return false;
0087     }
0088     // the Type field adds support for RLE(BGR), RGB and other encodings
0089     // we support Type 1: Normal(BGR) and Type 3: Normal(RGB) ONLY!
0090     // TODO: add support for Type 2: RLE(BGR) & Type 4,5: TIFF/IFF
0091     if (!(head.Type == 1 || head.Type == 3)) {
0092         return false;
0093     }
0094     // Old files didn't have Length set - reject them for now
0095     // TODO: add length recalculation to support old files
0096     if (!head.Length) {
0097         return false;
0098     }
0099     return true;
0100 }
0101 
0102 static bool LoadRAS(QDataStream &s, const RasHeader &ras, QImage &img)
0103 {
0104     s.device()->seek(RasHeader::SIZE);
0105 
0106     if (ras.ColorMapLength > kMaxQVectorSize) {
0107         qWarning() << "LoadRAS() unsupported image color map length in file header" << ras.ColorMapLength;
0108         return false;
0109     }
0110 
0111     // Read palette if needed.
0112     QVector<quint8> palette(ras.ColorMapLength);
0113     if (ras.ColorMapType == 1) {
0114         for (quint32 i = 0; i < ras.ColorMapLength; ++i) {
0115             s >> palette[i];
0116         }
0117     }
0118 
0119     const int bpp = ras.Depth / 8;
0120     if (ras.Height == 0) {
0121         return false;
0122     }
0123     if (bpp == 0) {
0124         return false;
0125     }
0126     if (ras.Length / ras.Height / bpp < ras.Width) {
0127         qWarning() << "LoadRAS() mistmatch between height and width" << ras.Width << ras.Height << ras.Length << ras.Depth;
0128         return false;
0129     }
0130     if (ras.Length > kMaxQVectorSize) {
0131         qWarning() << "LoadRAS() unsupported image length in file header" << ras.Length;
0132         return false;
0133     }
0134 
0135     // each line must be a factor of 16 bits, so they may contain padding
0136     // this will be 1 if padding required, 0 otherwise
0137     const int paddingrequired = (ras.Width * bpp % 2);
0138 
0139     // qDebug() << "paddingrequired: " << paddingrequired;
0140     // don't trust ras.Length
0141     QVector<quint8> input(ras.Length);
0142 
0143     int i = 0;
0144     while (!s.atEnd() && i < input.size()) {
0145         s >> input[i];
0146         // I guess we need to find out if we're at the end of a line
0147         if (paddingrequired && i != 0 && !(i % (ras.Width * bpp))) {
0148             s >> input[i];
0149         }
0150         i++;
0151     }
0152 
0153     // Allocate image
0154     img = imageAlloc(ras.Width, ras.Height, QImage::Format_ARGB32);
0155     if (img.isNull()) {
0156         return false;
0157     }
0158 
0159     // Reconstruct image from RGB palette if we have a palette
0160     // TODO: make generic so it works with 24bit or 32bit palettes
0161     if (ras.ColorMapType == 1 && ras.Depth == 8) {
0162         quint8 red;
0163         quint8 green;
0164         quint8 blue;
0165         for (quint32 y = 0; y < ras.Height; y++) {
0166             for (quint32 x = 0; x < ras.Width; x++) {
0167                 red = palette.value((int)input[y * ras.Width + x]);
0168                 green = palette.value((int)input[y * ras.Width + x] + (ras.ColorMapLength / 3));
0169                 blue = palette.value((int)input[y * ras.Width + x] + 2 * (ras.ColorMapLength / 3));
0170                 img.setPixel(x, y, qRgb(red, green, blue));
0171             }
0172         }
0173     }
0174 
0175     if (ras.ColorMapType == 0 && ras.Depth == 24 && (ras.Type == 1 || ras.Type == 2)) {
0176         quint8 red;
0177         quint8 green;
0178         quint8 blue;
0179         for (quint32 y = 0; y < ras.Height; y++) {
0180             for (quint32 x = 0; x < ras.Width; x++) {
0181                 red = input[y * 3 * ras.Width + x * 3 + 2];
0182                 green = input[y * 3 * ras.Width + x * 3 + 1];
0183                 blue = input[y * 3 * ras.Width + x * 3];
0184                 img.setPixel(x, y, qRgb(red, green, blue));
0185             }
0186         }
0187     }
0188 
0189     if (ras.ColorMapType == 0 && ras.Depth == 24 && ras.Type == 3) {
0190         quint8 red;
0191         quint8 green;
0192         quint8 blue;
0193         for (quint32 y = 0; y < ras.Height; y++) {
0194             for (quint32 x = 0; x < ras.Width; x++) {
0195                 red = input[y * 3 * ras.Width + x * 3];
0196                 green = input[y * 3 * ras.Width + x * 3 + 1];
0197                 blue = input[y * 3 * ras.Width + x * 3 + 2];
0198                 img.setPixel(x, y, qRgb(red, green, blue));
0199             }
0200         }
0201     }
0202 
0203     if (ras.ColorMapType == 0 && ras.Depth == 32 && (ras.Type == 1 || ras.Type == 2)) {
0204         quint8 red;
0205         quint8 green;
0206         quint8 blue;
0207         for (quint32 y = 0; y < ras.Height; y++) {
0208             for (quint32 x = 0; x < ras.Width; x++) {
0209                 red = input[y * 4 * ras.Width + x * 4 + 3];
0210                 green = input[y * 4 * ras.Width + x * 4 + 2];
0211                 blue = input[y * 4 * ras.Width + x * 4 + 1];
0212                 img.setPixel(x, y, qRgb(red, green, blue));
0213             }
0214         }
0215     }
0216 
0217     if (ras.ColorMapType == 0 && ras.Depth == 32 && ras.Type == 3) {
0218         quint8 red;
0219         quint8 green;
0220         quint8 blue;
0221         for (quint32 y = 0; y < ras.Height; y++) {
0222             for (quint32 x = 0; x < ras.Width; x++) {
0223                 red = input[y * 4 * ras.Width + x * 4 + 1];
0224                 green = input[y * 4 * ras.Width + x * 4 + 2];
0225                 blue = input[y * 4 * ras.Width + x * 4 + 3];
0226                 img.setPixel(x, y, qRgb(red, green, blue));
0227             }
0228         }
0229     }
0230 
0231     return true;
0232 }
0233 } // namespace
0234 
0235 RASHandler::RASHandler()
0236 {
0237 }
0238 
0239 bool RASHandler::canRead() const
0240 {
0241     if (canRead(device())) {
0242         setFormat("ras");
0243         return true;
0244     }
0245     return false;
0246 }
0247 
0248 bool RASHandler::canRead(QIODevice *device)
0249 {
0250     if (!device) {
0251         qWarning("RASHandler::canRead() called with no device");
0252         return false;
0253     }
0254 
0255     if (device->isSequential()) {
0256         // qWarning("Reading ras files from sequential devices not supported");
0257         return false;
0258     }
0259 
0260     qint64 oldPos = device->pos();
0261     QByteArray head = device->read(RasHeader::SIZE); // header is exactly 32 bytes, always FIXME
0262     int readBytes = head.size(); // this should always be 32 bytes
0263 
0264     device->seek(oldPos);
0265 
0266     if (readBytes < RasHeader::SIZE) {
0267         return false;
0268     }
0269 
0270     QDataStream stream(head);
0271     stream.setByteOrder(QDataStream::BigEndian);
0272     RasHeader ras;
0273     stream >> ras;
0274     return IsSupported(ras);
0275 }
0276 
0277 bool RASHandler::read(QImage *outImage)
0278 {
0279     QDataStream s(device());
0280     s.setByteOrder(QDataStream::BigEndian);
0281 
0282     // Read image header.
0283     RasHeader ras;
0284     s >> ras;
0285 
0286     if (ras.ColorMapLength > std::numeric_limits<int>::max()) {
0287         return false;
0288     }
0289 
0290     // TODO: add support for old versions of RAS where Length may be zero in header
0291     s.device()->seek(RasHeader::SIZE + ras.Length + ras.ColorMapLength);
0292 
0293     // Check image file format. Type 2 is RLE, which causing seeking to be silly.
0294     if (!s.atEnd() && ras.Type != 2) {
0295         //         qDebug() << "This RAS file is not valid, or an older version of the format.";
0296         return false;
0297     }
0298 
0299     // Check supported file types.
0300     if (!IsSupported(ras)) {
0301         //         qDebug() << "This RAS file is not supported.";
0302         return false;
0303     }
0304 
0305     QImage img;
0306     bool result = LoadRAS(s, ras, img);
0307 
0308     if (result == false) {
0309         //         qDebug() << "Error loading RAS file.";
0310         return false;
0311     }
0312 
0313     *outImage = img;
0314     return true;
0315 }
0316 
0317 QImageIOPlugin::Capabilities RASPlugin::capabilities(QIODevice *device, const QByteArray &format) const
0318 {
0319     if (format == "ras") {
0320         return Capabilities(CanRead);
0321     }
0322     if (!format.isEmpty()) {
0323         return {};
0324     }
0325     if (!device->isOpen()) {
0326         return {};
0327     }
0328 
0329     Capabilities cap;
0330     if (device->isReadable() && RASHandler::canRead(device)) {
0331         cap |= CanRead;
0332     }
0333     return cap;
0334 }
0335 
0336 QImageIOHandler *RASPlugin::create(QIODevice *device, const QByteArray &format) const
0337 {
0338     QImageIOHandler *handler = new RASHandler;
0339     handler->setDevice(device);
0340     handler->setFormat(format);
0341     return handler;
0342 }
0343 
0344 #include "moc_ras_p.cpp"