| 1 | /* |
| 2 | This file is part of the KDE project |
| 3 | SPDX-FileCopyrightText: 2003 Dominik Seichter <domseichter@web.de> |
| 4 | SPDX-FileCopyrightText: 2004 Ignacio CastaƱo <castano@ludicon.com> |
| 5 | SPDX-FileCopyrightText: 2010 Troy Unrau <troy@kde.org> |
| 6 | SPDX-FileCopyrightText: 2023 Mirco Miranda <mircomir@outlook.com> |
| 7 | |
| 8 | SPDX-License-Identifier: LGPL-2.0-or-later |
| 9 | */ |
| 10 | |
| 11 | #include "ras_p.h" |
| 12 | #include "util_p.h" |
| 13 | |
| 14 | #include <QDataStream> |
| 15 | #include <QDebug> |
| 16 | #include <QImage> |
| 17 | |
| 18 | namespace // Private. |
| 19 | { |
| 20 | // format info from http://www.fileformat.info/format/sunraster/egff.htm |
| 21 | |
| 22 | // Header format of saved files. |
| 23 | quint32 rasMagicBigEndian = 0x59a66a95; |
| 24 | // quint32 rasMagicLittleEndian = 0x956aa659; # used to support wrong encoded files |
| 25 | |
| 26 | enum RASType { |
| 27 | RAS_TYPE_OLD = 0x0, |
| 28 | RAS_TYPE_STANDARD = 0x1, |
| 29 | RAS_TYPE_BYTE_ENCODED = 0x2, |
| 30 | RAS_TYPE_RGB_FORMAT = 0x3, |
| 31 | RAS_TYPE_TIFF_FORMAT = 0x4, |
| 32 | RAS_TYPE_IFF_FORMAT = 0x5, |
| 33 | RAS_TYPE_EXPERIMENTAL = 0xFFFF, |
| 34 | }; |
| 35 | |
| 36 | enum RASColorMapType { |
| 37 | RAS_COLOR_MAP_TYPE_NONE = 0x0, |
| 38 | RAS_COLOR_MAP_TYPE_RGB = 0x1, |
| 39 | RAS_COLOR_MAP_TYPE_RAW = 0x2, |
| 40 | }; |
| 41 | |
| 42 | struct { |
| 43 | quint32 = 0; |
| 44 | quint32 = 0; |
| 45 | quint32 = 0; |
| 46 | quint32 = 0; |
| 47 | quint32 = 0; |
| 48 | quint32 = 0; |
| 49 | quint32 = 0; |
| 50 | quint32 = 0; |
| 51 | enum { |
| 52 | = 32, |
| 53 | }; // 8 fields of four bytes each |
| 54 | }; |
| 55 | |
| 56 | static QDataStream &(QDataStream &s, RasHeader &head) |
| 57 | { |
| 58 | s >> head.MagicNumber; |
| 59 | s >> head.Width; |
| 60 | s >> head.Height; |
| 61 | s >> head.Depth; |
| 62 | s >> head.Length; |
| 63 | s >> head.Type; |
| 64 | s >> head.ColorMapType; |
| 65 | s >> head.ColorMapLength; |
| 66 | /*qDebug() << "MagicNumber: " << head.MagicNumber |
| 67 | << "Width: " << head.Width |
| 68 | << "Height: " << head.Height |
| 69 | << "Depth: " << head.Depth |
| 70 | << "Length: " << head.Length |
| 71 | << "Type: " << head.Type |
| 72 | << "ColorMapType: " << head.ColorMapType |
| 73 | << "ColorMapLength: " << head.ColorMapLength;*/ |
| 74 | return s; |
| 75 | } |
| 76 | |
| 77 | static bool (const RasHeader &head) |
| 78 | { |
| 79 | // check magic number |
| 80 | if (head.MagicNumber != rasMagicBigEndian) { |
| 81 | return false; |
| 82 | } |
| 83 | // check for an appropriate depth |
| 84 | if (head.Depth != 1 && head.Depth != 8 && head.Depth != 24 && head.Depth != 32) { |
| 85 | return false; |
| 86 | } |
| 87 | if (head.Width == 0 || head.Height == 0) { |
| 88 | return false; |
| 89 | } |
| 90 | // the Type field adds support for RLE(BGR), RGB and other encodings |
| 91 | // we support Type 1: Normal(BGR), Type 2: RLE(BGR) and Type 3: Normal(RGB) ONLY! |
| 92 | // TODO: add support for Type 4,5: TIFF/IFF |
| 93 | if (!(head.Type == RAS_TYPE_STANDARD || head.Type == RAS_TYPE_RGB_FORMAT || head.Type == RAS_TYPE_BYTE_ENCODED)) { |
| 94 | return false; |
| 95 | } |
| 96 | return true; |
| 97 | } |
| 98 | |
| 99 | static QImage::Format (const RasHeader &) |
| 100 | { |
| 101 | if (header.ColorMapType == RAS_COLOR_MAP_TYPE_RGB) { |
| 102 | return QImage::Format_Indexed8; |
| 103 | } |
| 104 | if (header.Depth == 8 && header.ColorMapType == RAS_COLOR_MAP_TYPE_NONE) { |
| 105 | return QImage::Format_Grayscale8; |
| 106 | } |
| 107 | if (header.Depth == 1) { |
| 108 | return QImage::Format_Mono; |
| 109 | } |
| 110 | return QImage::Format_RGB32; |
| 111 | } |
| 112 | |
| 113 | class LineDecoder |
| 114 | { |
| 115 | public: |
| 116 | (QIODevice *d, const RasHeader &ras) |
| 117 | : device(d) |
| 118 | , header(ras) |
| 119 | { |
| 120 | } |
| 121 | |
| 122 | QByteArray readLine(qint64 size) |
| 123 | { |
| 124 | /* *** uncompressed |
| 125 | */ |
| 126 | if (header.Type != RAS_TYPE_BYTE_ENCODED) { |
| 127 | return device->read(maxlen: size); |
| 128 | } |
| 129 | |
| 130 | /* *** rle compressed |
| 131 | * The Run-length encoding (RLE) scheme optionally used in Sun Raster |
| 132 | * files (Type = 0002h) is used to encode bytes of image data |
| 133 | * separately. RLE encoding may be found in any Sun Raster file |
| 134 | * regardless of the type of image data it contains. |
| 135 | * |
| 136 | * The RLE packets are typically three bytes in size: |
| 137 | * - The first byte is a Flag Value indicating the type of RLE packet. |
| 138 | * - The second byte is the Run Count. |
| 139 | * - The third byte is the Run Value. |
| 140 | * |
| 141 | * A Flag Value of 80h is followed by a Run Count in the range of 01h |
| 142 | * to FFh. The Run Value follows the Run count and is in the range of |
| 143 | * 00h to FFh. The pixel run is the Run Value repeated Run Count times. |
| 144 | * There are two exceptions to this algorithm. First, if the Run Count |
| 145 | * following the Flag Value is 00h, this is an indication that the run |
| 146 | * is a single byte in length and has a value of 80h. And second, if |
| 147 | * the Flag Value is not 80h, then it is assumed that the data is |
| 148 | * unencoded pixel data and is written directly to the output stream. |
| 149 | * |
| 150 | * source: http://www.fileformat.info/format/sunraster/egff.htm |
| 151 | */ |
| 152 | for (qsizetype psz = 0, ptr = 0; uncBuffer.size() < size;) { |
| 153 | rleBuffer.append(a: device->read(maxlen: std::min(a: qint64(32768), b: size))); |
| 154 | qsizetype sz = rleBuffer.size(); |
| 155 | if (psz == sz) { |
| 156 | break; // avoid infinite loop (data corrupted?!) |
| 157 | } |
| 158 | auto data = reinterpret_cast<uchar *>(rleBuffer.data()); |
| 159 | for (; ptr < sz;) { |
| 160 | auto flag = data[ptr++]; |
| 161 | if (flag == 0x80) { |
| 162 | if (ptr >= sz) { |
| 163 | ptr -= 1; |
| 164 | break; |
| 165 | } |
| 166 | auto cnt = data[ptr++]; |
| 167 | if (cnt == 0) { |
| 168 | uncBuffer.append(c: char(0x80)); |
| 169 | continue; |
| 170 | } else if (ptr >= sz) { |
| 171 | ptr -= 2; |
| 172 | break; |
| 173 | } |
| 174 | auto val = data[ptr++]; |
| 175 | uncBuffer.append(a: QByteArray(1 + cnt, char(val))); |
| 176 | } else { |
| 177 | uncBuffer.append(c: char(flag)); |
| 178 | } |
| 179 | } |
| 180 | if (ptr) { // remove consumed data |
| 181 | rleBuffer.remove(index: 0, len: ptr); |
| 182 | ptr = 0; |
| 183 | } |
| 184 | psz = rleBuffer.size(); |
| 185 | } |
| 186 | if (uncBuffer.size() < size) { |
| 187 | return QByteArray(); // something wrong |
| 188 | } |
| 189 | auto line = uncBuffer.mid(index: 0, len: size); |
| 190 | uncBuffer.remove(index: 0, len: line.size()); // remove consumed data |
| 191 | return line; |
| 192 | } |
| 193 | |
| 194 | private: |
| 195 | QIODevice *device; |
| 196 | RasHeader ; |
| 197 | |
| 198 | // RLE decoding buffers |
| 199 | QByteArray rleBuffer; |
| 200 | QByteArray uncBuffer; |
| 201 | }; |
| 202 | |
| 203 | static bool (QDataStream &s, const RasHeader &ras, QImage &img) |
| 204 | { |
| 205 | // The width of a scan line is always a multiple of 16 bits, padded when necessary. |
| 206 | auto rasLineSize = (qint64(ras.Width) * ras.Depth + 7) / 8; |
| 207 | if (rasLineSize & 1) |
| 208 | ++rasLineSize; |
| 209 | if (rasLineSize > kMaxQVectorSize) { |
| 210 | qWarning() << "LoadRAS() unsupported line size" << rasLineSize; |
| 211 | return false; |
| 212 | } |
| 213 | |
| 214 | // Allocate image |
| 215 | img = imageAlloc(width: ras.Width, height: ras.Height, format: imageFormat(header: ras)); |
| 216 | if (img.isNull()) { |
| 217 | return false; |
| 218 | } |
| 219 | |
| 220 | // Read palette if needed. |
| 221 | if (ras.ColorMapType == RAS_COLOR_MAP_TYPE_RGB) { |
| 222 | // max 256 rgb elements palette is supported |
| 223 | if (ras.ColorMapLength > 768) { |
| 224 | return false; |
| 225 | } |
| 226 | QList<quint8> palette(ras.ColorMapLength); |
| 227 | for (quint32 i = 0; i < ras.ColorMapLength; ++i) { |
| 228 | s >> palette[i]; |
| 229 | if (s.status() != QDataStream::Ok) { |
| 230 | return false; |
| 231 | } |
| 232 | } |
| 233 | QList<QRgb> colorTable; |
| 234 | for (quint32 i = 0, n = ras.ColorMapLength / 3; i < n; ++i) { |
| 235 | colorTable << qRgb(r: palette.at(i), g: palette.at(i: i + n), b: palette.at(i: i + 2 * n)); |
| 236 | } |
| 237 | for (; colorTable.size() < 256;) { |
| 238 | colorTable << qRgb(r: 255, g: 255, b: 255); |
| 239 | } |
| 240 | img.setColorTable(colorTable); |
| 241 | } |
| 242 | |
| 243 | LineDecoder dec(s.device(), ras); |
| 244 | auto bytesPerLine = std::min(a: img.bytesPerLine(), b: qsizetype(rasLineSize)); |
| 245 | for (quint32 y = 0; y < ras.Height; ++y) { |
| 246 | auto rasLine = dec.readLine(size: rasLineSize); |
| 247 | if (rasLine.size() != rasLineSize) { |
| 248 | qWarning() << "LoadRAS() unable to read line" << y << ": the seems corrupted!" ; |
| 249 | return false; |
| 250 | } |
| 251 | |
| 252 | // Grayscale 1-bit / Grayscale 8-bit (never seen) |
| 253 | if (ras.ColorMapType == RAS_COLOR_MAP_TYPE_NONE && (ras.Depth == 1 || ras.Depth == 8)) { |
| 254 | for (auto &&b : rasLine) { |
| 255 | b = ~b; |
| 256 | } |
| 257 | std::memcpy(dest: img.scanLine(y), src: rasLine.constData(), n: bytesPerLine); |
| 258 | continue; |
| 259 | } |
| 260 | |
| 261 | // Image with palette |
| 262 | if (ras.ColorMapType == RAS_COLOR_MAP_TYPE_RGB && (ras.Depth == 1 || ras.Depth == 8)) { |
| 263 | std::memcpy(dest: img.scanLine(y), src: rasLine.constData(), n: bytesPerLine); |
| 264 | continue; |
| 265 | } |
| 266 | |
| 267 | // BGR 24-bit |
| 268 | if (ras.ColorMapType == RAS_COLOR_MAP_TYPE_NONE && ras.Depth == 24 && (ras.Type == RAS_TYPE_STANDARD || ras.Type == RAS_TYPE_BYTE_ENCODED)) { |
| 269 | quint8 red; |
| 270 | quint8 green; |
| 271 | quint8 blue; |
| 272 | auto scanLine = reinterpret_cast<QRgb *>(img.scanLine(y)); |
| 273 | for (quint32 x = 0; x < ras.Width; x++) { |
| 274 | red = rasLine.at(i: x * 3 + 2); |
| 275 | green = rasLine.at(i: x * 3 + 1); |
| 276 | blue = rasLine.at(i: x * 3); |
| 277 | *(scanLine + x) = qRgb(r: red, g: green, b: blue); |
| 278 | } |
| 279 | continue; |
| 280 | } |
| 281 | |
| 282 | // RGB 24-bit |
| 283 | if (ras.ColorMapType == RAS_COLOR_MAP_TYPE_NONE && ras.Depth == 24 && ras.Type == RAS_TYPE_RGB_FORMAT) { |
| 284 | quint8 red; |
| 285 | quint8 green; |
| 286 | quint8 blue; |
| 287 | auto scanLine = reinterpret_cast<QRgb *>(img.scanLine(y)); |
| 288 | for (quint32 x = 0; x < ras.Width; x++) { |
| 289 | red = rasLine.at(i: x * 3); |
| 290 | green = rasLine.at(i: x * 3 + 1); |
| 291 | blue = rasLine.at(i: x * 3 + 2); |
| 292 | *(scanLine + x) = qRgb(r: red, g: green, b: blue); |
| 293 | } |
| 294 | continue; |
| 295 | } |
| 296 | |
| 297 | // BGR 32-bit (not tested: test case missing) |
| 298 | if (ras.ColorMapType == RAS_COLOR_MAP_TYPE_NONE && ras.Depth == 32 && (ras.Type == RAS_TYPE_STANDARD || ras.Type == RAS_TYPE_BYTE_ENCODED)) { |
| 299 | quint8 red; |
| 300 | quint8 green; |
| 301 | quint8 blue; |
| 302 | auto scanLine = reinterpret_cast<QRgb *>(img.scanLine(y)); |
| 303 | for (quint32 x = 0; x < ras.Width; x++) { |
| 304 | red = rasLine.at(i: x * 4 + 3); |
| 305 | green = rasLine.at(i: x * 4 + 2); |
| 306 | blue = rasLine.at(i: x * 4 + 1); |
| 307 | *(scanLine + x) = qRgb(r: red, g: green, b: blue); |
| 308 | } |
| 309 | |
| 310 | continue; |
| 311 | } |
| 312 | |
| 313 | // RGB 32-bit (tested: test case missing due to image too large) |
| 314 | if (ras.ColorMapType == RAS_COLOR_MAP_TYPE_NONE && ras.Depth == 32 && ras.Type == RAS_TYPE_RGB_FORMAT) { |
| 315 | quint8 red; |
| 316 | quint8 green; |
| 317 | quint8 blue; |
| 318 | auto scanLine = reinterpret_cast<QRgb *>(img.scanLine(y)); |
| 319 | for (quint32 x = 0; x < ras.Width; x++) { |
| 320 | red = rasLine.at(i: x * 4 + 1); |
| 321 | green = rasLine.at(i: x * 4 + 2); |
| 322 | blue = rasLine.at(i: x * 4 + 3); |
| 323 | *(scanLine + x) = qRgb(r: red, g: green, b: blue); |
| 324 | } |
| 325 | continue; |
| 326 | } |
| 327 | |
| 328 | qWarning() << "LoadRAS() unsupported format!" |
| 329 | << "ColorMapType:" << ras.ColorMapType << "Type:" << ras.Type << "Depth:" << ras.Depth; |
| 330 | return false; |
| 331 | } |
| 332 | |
| 333 | return true; |
| 334 | } |
| 335 | } // namespace |
| 336 | |
| 337 | class RASHandlerPrivate |
| 338 | { |
| 339 | public: |
| 340 | RASHandlerPrivate() {} |
| 341 | ~RASHandlerPrivate() {} |
| 342 | |
| 343 | RasHeader m_header; |
| 344 | }; |
| 345 | |
| 346 | |
| 347 | RASHandler::RASHandler() |
| 348 | : QImageIOHandler() |
| 349 | , d(new RASHandlerPrivate) |
| 350 | { |
| 351 | } |
| 352 | |
| 353 | bool RASHandler::canRead() const |
| 354 | { |
| 355 | if (canRead(device: device())) { |
| 356 | setFormat("ras" ); |
| 357 | return true; |
| 358 | } |
| 359 | return false; |
| 360 | } |
| 361 | |
| 362 | bool RASHandler::canRead(QIODevice *device) |
| 363 | { |
| 364 | if (!device) { |
| 365 | qWarning(msg: "RASHandler::canRead() called with no device" ); |
| 366 | return false; |
| 367 | } |
| 368 | |
| 369 | auto head = device->peek(maxlen: RasHeader::SIZE); // header is exactly 32 bytes, always FIXME |
| 370 | if (head.size() < RasHeader::SIZE) { |
| 371 | return false; |
| 372 | } |
| 373 | |
| 374 | QDataStream stream(head); |
| 375 | stream.setByteOrder(QDataStream::BigEndian); |
| 376 | RasHeader ras; |
| 377 | stream >> ras; |
| 378 | return IsSupported(head: ras); |
| 379 | } |
| 380 | |
| 381 | bool RASHandler::read(QImage *outImage) |
| 382 | { |
| 383 | QDataStream s(device()); |
| 384 | s.setByteOrder(QDataStream::BigEndian); |
| 385 | |
| 386 | // Read image header. |
| 387 | auto&& ras = d->m_header; |
| 388 | s >> ras; |
| 389 | |
| 390 | if (ras.ColorMapLength > kMaxQVectorSize) { |
| 391 | qWarning() << "LoadRAS() unsupported image color map length in file header" << ras.ColorMapLength; |
| 392 | return false; |
| 393 | } |
| 394 | |
| 395 | // Check supported file types. |
| 396 | if (!IsSupported(head: ras)) { |
| 397 | // qDebug() << "This RAS file is not supported."; |
| 398 | return false; |
| 399 | } |
| 400 | |
| 401 | QImage img; |
| 402 | if (!LoadRAS(s, ras, img)) { |
| 403 | // qDebug() << "Error loading RAS file."; |
| 404 | return false; |
| 405 | } |
| 406 | |
| 407 | *outImage = img; |
| 408 | return true; |
| 409 | } |
| 410 | |
| 411 | bool RASHandler::supportsOption(ImageOption option) const |
| 412 | { |
| 413 | if (option == QImageIOHandler::Size) { |
| 414 | return true; |
| 415 | } |
| 416 | if (option == QImageIOHandler::ImageFormat) { |
| 417 | return true; |
| 418 | } |
| 419 | return false; |
| 420 | } |
| 421 | |
| 422 | QVariant RASHandler::option(ImageOption option) const |
| 423 | { |
| 424 | QVariant v; |
| 425 | |
| 426 | if (option == QImageIOHandler::Size) { |
| 427 | auto&& = d->m_header; |
| 428 | if (IsSupported(head: header)) { |
| 429 | v = QVariant::fromValue(value: QSize(header.Width, header.Height)); |
| 430 | } |
| 431 | else if (auto dev = device()) { |
| 432 | QDataStream s(dev->peek(maxlen: RasHeader::SIZE)); |
| 433 | s.setByteOrder(QDataStream::BigEndian); |
| 434 | s >> header; |
| 435 | if (s.status() == QDataStream::Ok && IsSupported(head: header)) { |
| 436 | v = QVariant::fromValue(value: QSize(header.Width, header.Height)); |
| 437 | } |
| 438 | } |
| 439 | } |
| 440 | |
| 441 | if (option == QImageIOHandler::ImageFormat) { |
| 442 | auto&& = d->m_header; |
| 443 | if (IsSupported(head: header)) { |
| 444 | v = QVariant::fromValue(value: imageFormat(header)); |
| 445 | } |
| 446 | else if (auto dev = device()) { |
| 447 | QDataStream s(dev->peek(maxlen: RasHeader::SIZE)); |
| 448 | s.setByteOrder(QDataStream::BigEndian); |
| 449 | s >> header; |
| 450 | if (s.status() == QDataStream::Ok && IsSupported(head: header)) { |
| 451 | v = QVariant::fromValue(value: imageFormat(header)); |
| 452 | } |
| 453 | } |
| 454 | } |
| 455 | |
| 456 | return v; |
| 457 | } |
| 458 | |
| 459 | QImageIOPlugin::Capabilities RASPlugin::capabilities(QIODevice *device, const QByteArray &format) const |
| 460 | { |
| 461 | if (format == "im1" || format == "im8" || format == "im24" || format == "im32" || format == "ras" || format == "sun" ) { |
| 462 | return Capabilities(CanRead); |
| 463 | } |
| 464 | if (!format.isEmpty()) { |
| 465 | return {}; |
| 466 | } |
| 467 | if (!device->isOpen()) { |
| 468 | return {}; |
| 469 | } |
| 470 | |
| 471 | Capabilities cap; |
| 472 | if (device->isReadable() && RASHandler::canRead(device)) { |
| 473 | cap |= CanRead; |
| 474 | } |
| 475 | return cap; |
| 476 | } |
| 477 | |
| 478 | QImageIOHandler *RASPlugin::create(QIODevice *device, const QByteArray &format) const |
| 479 | { |
| 480 | QImageIOHandler *handler = new RASHandler; |
| 481 | handler->setDevice(device); |
| 482 | handler->setFormat(format); |
| 483 | return handler; |
| 484 | } |
| 485 | |
| 486 | #include "moc_ras_p.cpp" |
| 487 | |