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