From 296444189e75b79b0b5869fc2925834b91f52d6e Mon Sep 17 00:00:00 2001 From: Ana Bobillo Date: Tue, 27 Oct 2026 10:39:33 +0100 Subject: [PATCH] IFF: set every pixel of each line Images are allocated without being initialized, so a reader must write every pixel of every line. The IFF reader did not always: - Maya: nothing checked that the tiles cover the image: areas no tile covers are now opaque black. - readStandardImage(), readCDIImage(), readRGFXImage() and readDEEPImage() copied min(bytesPerLine, decoded line) bytes, and some decoded lines are shorter than the image line: - a PBM with 8 bitplanes and a 64 color CMAP was taken for HAM and allocated as RGB888, while PBM data are one index per pixel: HAM is no longer applied to PBM images; - other mismatches (e.g. HAM without CMAP, RGB8 with 32 bitplanes, CD-i CLUT3 or RGB888, a truncated CD-i line) are now an error: a decoded line that does not cover the image line is refused. Added the read test pbm_64colors.iff, which fails without this change. --- autotests/read/iff/pbm_64colors.iff | Bin 0 -> 338 bytes autotests/read/iff/pbm_64colors.png | Bin 0 -> 341 bytes src/imageformats/chunks.cpp | 3 ++- src/imageformats/iff.cpp | 37 ++++++++++++++++++---------- 4 files changed, 26 insertions(+), 14 deletions(-) create mode 100644 autotests/read/iff/pbm_64colors.iff create mode 100644 autotests/read/iff/pbm_64colors.png diff --git a/autotests/read/iff/pbm_64colors.iff b/autotests/read/iff/pbm_64colors.iff new file mode 100644 index 0000000000000000000000000000000000000000..7b847da1f36f4bcf37859209af8b2ccfef6bb196 GIT binary patch literal 338 zcmZ?s5AtPTVDt)b@>Ov1^>ATeU=U&8Wncr+3>*xMK$4LW#CP^}3;=Qth-lbYHW$X- zTh?rOd&Lxiv+rF$c~yxoU2ntiBr#gDZJXa}!R8A&JH8%&E2X4lUmKosqo>pO;JUtV zmtSiCa7mDzKh^2isr1nQJsV6ma%WykoA>+RWd#`y*K*seOGW*<%eQpgx%pV|pPifX z%xaG>8*=@hPFf|koz?$NLJQ;WooZt5ybF~J&O}UA@7>jO=>FaBEEYPNlN0^kO)K_1 zJ!1*O-v6eLqO#bx&esvR5}3_dcFb(#RZHK*(vF#f7Z@IjYT5>-R`x8Me8Lj) X47@sqX4WwMS`57AHjHdA{iX~6haPW+ literal 0 HcmV?d00001 diff --git a/autotests/read/iff/pbm_64colors.png b/autotests/read/iff/pbm_64colors.png new file mode 100644 index 0000000000000000000000000000000000000000..274bc778c8e68fca90a065b6028cd88af949533d GIT binary patch literal 341 zcmV-b0jmCqP)c++c(OySXM7?n`kaaLibhCyly-!r$HZ1UM`ylVMKomUBtVn4|!` z|25)TY6rESED+RDYYBG7KARzPzD6K|w_#+1=`{rakFN=l9I@B000000NkvXXu0mjfOQx0B literal 0 HcmV?d00001 diff --git a/src/imageformats/chunks.cpp b/src/imageformats/chunks.cpp index 8827a9a..ab71e63 100644 --- a/src/imageformats/chunks.cpp +++ b/src/imageformats/chunks.cpp @@ -1517,7 +1517,8 @@ QImage::Format FORMChunk::iffFormat() const return QImage::Format_RGBA64; } if (h->bitplanes() >= 1 && h->bitplanes() <= 8) { - if (h->bitplanes() >= BITPLANES_HAM_MIN && h->bitplanes() <= BITPLANES_HAM_MAX) { + // PBM data are chunky indexes (no HAM decoding) + if (h->bitplanes() >= BITPLANES_HAM_MIN && h->bitplanes() <= BITPLANES_HAM_MAX && formType() != PBM__FORM_TYPE) { if (modeId & CAMGChunk::ModeId::Ham) return FORMAT_RGB_8BIT; } diff --git a/src/imageformats/iff.cpp b/src/imageformats/iff.cpp index d46f094..95dd1f0 100644 --- a/src/imageformats/iff.cpp +++ b/src/imageformats/iff.cpp @@ -327,6 +327,24 @@ static QImage convertIPAL(const QImage& img, const IPALChunk *ipal) return tmp; } +/*! + * \brief copyStride + * Copies the decoded stride to an image line. + * \param img The image. + * \param y The line of the image. + * \param ba The decoded stride. + * \return False if the stride does not hold all the pixels of the line. + */ +static bool copyStride(QImage &img, qint32 y, const QByteArray &ba) +{ + // the stride must cover every pixel (bytesPerLine() may include padding) + if (ba.size() < (qsizetype(img.width()) * img.depth() + 7) / 8) { + return false; + } + memcpy(img.scanLine(y), ba.constData(), std::min(img.bytesPerLine(), ba.size())); + return true; +} + bool IFFHandler::readStandardImage(QImage *image) { auto forms = d->searchForms(); @@ -403,13 +421,11 @@ bool IFFHandler::readStandardImage(QImage *image) return false; } for (auto y = 0, h = img.height(); y < h; ++y) { - auto line = reinterpret_cast(img.scanLine(y)); auto ba = body->strideRead(device(), y, header, camg, cmap, ipal.get(), form->formType()); - if (ba.isEmpty()) { + if (!copyStride(img, y, ba)) { qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readStandardImage(): error while reading image scanline"; return false; } - memcpy(line, ba.constData(), std::min(img.bytesPerLine(), ba.size())); } } @@ -442,7 +458,8 @@ bool IFFHandler::readMayaImage(QImage *image) // create the image auto &&header = headers.first(); - auto img = imageAlloc(header->size(), form->format()); + // tiles are not required to cover the whole image + auto img = imageAlloc(header->size(), form->format(), ImageInitToZero::All); if (img.isNull()) { qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readMayaImage(): error while allocating the image"; return false; @@ -525,15 +542,13 @@ bool IFFHandler::readCDIImage(QImage *image) auto pars = IFFChunk::searchT(form); auto yuvs = IFFChunk::searchT(form); for (auto y = 0, h = img.height(); y < h; ++y) { - auto line = reinterpret_cast(img.scanLine(y)); auto ba = body->strideRead(device(), y, header, pars.isEmpty() ? nullptr : pars.first(), yuvs.isEmpty() ? nullptr : yuvs.first()); - if (ba.isEmpty()) { + if (!copyStride(img, y, ba)) { qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readCDIImage(): error while reading image scanline"; return false; } - memcpy(line, ba.constData(), std::min(img.bytesPerLine(), ba.size())); } } @@ -588,15 +603,13 @@ bool IFFHandler::readRGFXImage(QImage *image) auto rcsms = IFFChunk::searchT(form); auto rcols = IFFChunk::searchT(form); for (auto y = 0, h = img.height(); y < h; ++y) { - auto line = reinterpret_cast(img.scanLine(y)); auto ba = body->strideRead(device(), y, header, rcsms.isEmpty() ? nullptr : rcsms.first(), rcols.isEmpty() ? nullptr : rcols.first()); - if (ba.isEmpty()) { + if (!copyStride(img, y, ba)) { qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readRGFXImage(): error while reading image scanline"; return false; } - memcpy(line, ba.constData(), std::min(img.bytesPerLine(), ba.size())); } } @@ -651,15 +664,13 @@ bool IFFHandler::readDEEPImage(QImage *image) } auto tvdcs = IFFChunk::searchT(form); for (auto y = 0, h = img.height(); y < h; ++y) { - auto line = reinterpret_cast(img.scanLine(y)); auto ba = body->strideRead(device(), y, header, pels.first(), locs.isEmpty() ? nullptr : locs.first(), tvdcs.isEmpty() ? nullptr : tvdcs.first()); - if (ba.isEmpty()) { + if (!copyStride(img, y, ba)) { qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readDEEPImage(): error while reading image scanline"; return false; } - memcpy(line, ba.constData(), std::min(img.bytesPerLine(), ba.size())); } }