Add support for CD-I IFF images

This commit is contained in:
Mirco Miranda
2026-01-08 17:49:01 +01:00
parent 8d7fb2c3fd
commit 8224c0099d
7 changed files with 822 additions and 21 deletions

View File

@ -357,6 +357,8 @@ The plugin supports the following image data:
type 4.
- FORM PBM: PBM is a chunky version of IFF pictures. It supports 8-bit images
with color map only.
- FORM IMAG (Compact Disc-Interactive): It supports CLut4, CLut8 and DYuv
formats.
- FOR4 CIMG (Maya Image File Format): It supports 24/48-bit RGB and 32/64-bit
RGBA images.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -311,10 +311,18 @@ IFFChunk::ChunkList IFFChunk::innerFromDevice(QIODevice *d, bool *ok, IFFChunk *
chunk = QSharedPointer<IFFChunk>(new ICCNChunk());
} else if (cid == ICCP_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new ICCPChunk());
} else if (cid == IDAT_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new IDATChunk());
} else if (cid == IHDR_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new IHDRChunk());
} else if (cid == IPAR_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new IPARChunk());
} else if (cid == NAME_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new NAMEChunk());
} else if (cid == PCHG_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new PCHGChunk());
} else if (cid == PLTE_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new PLTEChunk());
} else if (cid == RAST_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new RASTChunk());
} else if (cid == RGBA_CHUNK) {
@ -1406,16 +1414,13 @@ bool FORMChunk::innerReadStructure(QIODevice *d)
setChunks(IFFChunk::innerFromDevice(d, &ok, this));
} else if (_type == RGBN_FORM_TYPE) {
setChunks(IFFChunk::innerFromDevice(d, &ok, this));
} else if (_type == IMAG_FORM_TYPE) {
setChunks(IFFChunk::innerFromDevice(d, &ok, this));
}
return ok;
}
QByteArray FORMChunk::formType() const
{
return _type;
}
QImage::Format FORMChunk::format() const
QImage::Format FORMChunk::iffFormat() const
{
auto headers = IFFChunk::searchT<BMHDChunk>(chunks());
if (headers.isEmpty()) {
@ -1464,13 +1469,67 @@ QImage::Format FORMChunk::format() const
return QImage::Format_Invalid;
}
QImage::Format FORMChunk::cdiFormat() const
{
auto headers = IFFChunk::searchT<IHDRChunk>(chunks());
if (headers.isEmpty()) {
return QImage::Format_Invalid;
}
if (auto &&h = headers.first()) {
if (h->model() == IHDRChunk::Rgb555 && h->depth() == 16) { // no test case
return QImage::Format_RGB555;
}
if (h->depth() == 4) {
if (h->model() == IHDRChunk::CLut4 || h->model() == IHDRChunk::CLut3) { // CLut3: no test case
return QImage::Format_Indexed8;
}
}
if (h->depth() == 8) {
if (h->model() == IHDRChunk::CLut8 || h->model() == IHDRChunk::CLut7) { // CLut7: no test case
return QImage::Format_Indexed8;
}
if (h->model() == IHDRChunk::Rgb888) { // no test case
return FORMAT_RGB_8BIT;
}
if (h->model() == IHDRChunk::DYuv && h->yuvKind() == IHDRChunk::One) {
return FORMAT_RGB_8BIT;
}
}
}
return QImage::Format_Invalid;
}
QByteArray FORMChunk::formType() const
{
return _type;
}
QImage::Format FORMChunk::format() const
{
if (formType() == IMAG_FORM_TYPE) {
return cdiFormat();
}
return iffFormat();
}
QSize FORMChunk::size() const
{
auto headers = IFFChunk::searchT<BMHDChunk>(chunks());
if (headers.isEmpty()) {
return {};
if (formType() == IMAG_FORM_TYPE) {
auto ihdrs = IFFChunk::searchT<IHDRChunk>(chunks());
if (!ihdrs.isEmpty()) {
return ihdrs.first()->size();
}
} else {
auto bmhds = IFFChunk::searchT<BMHDChunk>(chunks());
if (!bmhds.isEmpty()) {
return bmhds.first()->size();
}
}
return headers.first()->size();
return {};
}
/* ******************
@ -1583,6 +1642,8 @@ bool CATChunk::innerReadStructure(QIODevice *d)
setChunks(IFFChunk::innerFromDevice(d, &ok, this));
} else if (_type == RGBN_FORM_TYPE) {
setChunks(IFFChunk::innerFromDevice(d, &ok, this));
} else if (_type == IMAG_FORM_TYPE) {
setChunks(IFFChunk::innerFromDevice(d, &ok, this));
}
return ok;
}
@ -2371,6 +2432,398 @@ bool XMP0Chunk::innerReadStructure(QIODevice *d)
}
/* ******************
* *** IHDR Chunk ***
* ****************** */
IHDRChunk::~IHDRChunk()
{
}
IHDRChunk::IHDRChunk()
{
}
bool IHDRChunk::isValid() const
{
if (dataBytes() < 14) {
return false;
}
return chunkId() == IHDRChunk::defaultChunkId();
}
qint32 IHDRChunk::width() const
{
if (!isValid()) {
return 0;
}
return qint32(ui16(data().at(1), data().at(0)));
}
qint32 IHDRChunk::height() const
{
if (!isValid()) {
return 0;
}
return qint32(ui16(data().at(5), data().at(4)));
}
QSize IHDRChunk::size() const
{
return QSize(width(), height());
}
qint32 IHDRChunk::lineSize() const
{
if (!isValid()) {
return 0;
}
return qint32(ui16(data().at(3), data().at(2)));
}
quint16 IHDRChunk::depth() const
{
if (!isValid()) {
return 0;
}
return qint32(ui16(data().at(9), data().at(8)));
}
IHDRChunk::Model IHDRChunk::model() const
{
if (!isValid()) {
return IHDRChunk::Model::Invalid;
}
return IHDRChunk::Model(ui16(data().at(7), data().at(6)));
}
IHDRChunk::DYuvKind IHDRChunk::yuvKind() const
{
if (!isValid()) {
return IHDRChunk::DYuvKind::One;
}
return IHDRChunk::DYuvKind(data().at(10));
}
IHDRChunk::Yuv IHDRChunk::yuvStart() const
{
if (!isValid()) {
return{};
}
return(Yuv(data().at(11), data().at(12), data().at(13)));
}
bool IHDRChunk::innerReadStructure(QIODevice *d)
{
return cacheData(d);
}
/* ******************
* *** IPAR Chunk ***
* ****************** */
IPARChunk::~IPARChunk()
{
}
IPARChunk::IPARChunk()
{
}
bool IPARChunk::isValid() const
{
if (dataBytes() < 22) {
return false;
}
return chunkId() == IPARChunk::defaultChunkId();
}
qint32 IPARChunk::xOffset() const
{
if (!isValid()) {
return 0;
}
return qint32(ui16(data().at(1), data().at(0)));
}
qint32 IPARChunk::yOffset() const
{
if (!isValid()) {
return 0;
}
return qint32(ui16(data().at(3), data().at(2)));
}
double IPARChunk::aspectRatio() const
{
if (!isValid()) {
return 1;
}
if (auto xr = ui16(data().at(5), data().at(4))) {
auto yr = double(ui16(data().at(7), data().at(6)));
return yr / xr;
}
return 1;
}
qint32 IPARChunk::xPage() const
{
if (!isValid()) {
return 0;
}
return qint32(ui16(data().at(9), data().at(8)));
}
qint32 IPARChunk::yPage() const
{
if (!isValid()) {
return 0;
}
return qint32(ui16(data().at(11), data().at(10)));
}
qint32 IPARChunk::xGrub() const
{
if (!isValid()) {
return 0;
}
return qint32(ui16(data().at(13), data().at(12)));
}
qint32 IPARChunk::yGrub() const
{
if (!isValid()) {
return 0;
}
return qint32(ui16(data().at(15), data().at(14)));
}
IPARChunk::Rgb IPARChunk::mask() const
{
if (!isValid()) {
return {};
}
return Rgb(data().at(16), data().at(17), data().at(18));
}
IPARChunk::Rgb IPARChunk::transparency() const
{
if (!isValid()) {
return {};
}
return Rgb(data().at(19), data().at(20), data().at(21));
}
bool IPARChunk::innerReadStructure(QIODevice *d)
{
return cacheData(d);
}
/* ******************
* *** PLTE Chunk ***
* ****************** */
PLTEChunk::~PLTEChunk()
{
}
PLTEChunk::PLTEChunk()
{
}
bool PLTEChunk::isValid() const
{
if (dataBytes() < 4) {
return false;
}
if (dataBytes() - 4 < total() * 3) {
return false;
}
return chunkId() == PLTEChunk::defaultChunkId();
}
qint32 PLTEChunk::count() const
{
if (!isValid()) {
return 0;
}
return total() - count();
}
qint32 PLTEChunk::offset() const
{
return qint32(ui16(data().at(1), data().at(0)));
}
qint32 PLTEChunk::total() const
{
return qint32(ui16(data().at(3), data().at(2)));
}
QList<QRgb> PLTEChunk::innerPalette() const
{
if (!isValid()) {
return{};
}
QList<QRgb> l;
auto &&d = data();
for (qint32 i = offset(), n = total(); i < n; ++i) {
auto i3 = 4 + i * 3;
l << qRgb(d.at(i3), d.at(i3 + 1), d.at(i3 + 2));
}
return l;
}
/* ******************
* *** IDAT Chunk ***
* ****************** */
IDATChunk::~IDATChunk()
{
}
IDATChunk::IDATChunk()
{
}
bool IDATChunk::isValid() const
{
return chunkId() == IDATChunk::defaultChunkId();
}
/*!
* Converts a YUV pixel to RGB.
*/
inline IPARChunk::Rgb yuvToRgb(IHDRChunk::Yuv yuv) {
IPARChunk::Rgb rgb;
// Green Book Cap. V Par. 4.4.2.2
const auto b = yuv.y + (yuv.u - 128.) * 1.733;
const auto r = yuv.y + (yuv.v - 128.) * 1.371;
const auto g = (yuv.y - 0.299 * r - 0.114 * b) / 0.587;
rgb.r = quint8(std::clamp(r + 0.5, 0., 255.));
rgb.g = quint8(std::clamp(g + 0.5, 0., 255.));
rgb.b = quint8(std::clamp(b + 0.5, 0., 255.));
return rgb;
}
QByteArray IDATChunk::strideRead(QIODevice *d, qint32 y, const IHDRChunk *header, const IPARChunk *params) const
{
Q_UNUSED(y)
Q_UNUSED(params)
if (!isValid() || header == nullptr || d == nullptr) {
return {};
}
auto read = strideSize(header);
for (auto nextPos = nextChunkPos(); !d->atEnd() && d->pos() < nextPos;) {
auto rr = d->read(read);
if (header->model() == IHDRChunk::CLut4) {
if (rr.size() < header->width() / 2) {
return {};
}
QByteArray tmp(header->width(), char());
for (auto x = 0, w = header->width(); x < w; ++x) {
auto i8 = quint8(rr.at(x / 2));
tmp[x] = x & 1 ? i8 & 0xF : (i8 >> 4) & 0xF;
}
rr = tmp;
}
if (header->model() == IHDRChunk::Rgb555) {
for(qint32 x = 0, w = rr.size() - 1; x < w; x += 2) {
std::swap(rr[x], rr[x + 1]);
}
}
if (header->model() == IHDRChunk::DYuv) {
if (rr.size() < header->width()) {
return {};
}
// delta table: Green Book Cap. V Par. 3.4.1.3
// NOTE 1: using the wrong delta table creates visible artifacts on the image.
// NOTE 2: using { 0, 1, 4, 9, 16, 27, 44, 79, -128, -79, -44, -27, -16, -9, -4, -1 }
// table gives the same result (when assigned to an uint8).
static const qint32 deltaTable[16] = {
0, 1, 4, 9, 16, 27, 44, 79, 128, 177, 212, 229, 240, 247, 252, 255
};
auto yuv = header->yuvStart();
QByteArray tmp(header->width() * 3, char());
for (auto x = 0, w = header->width() - 1; x < w; x += 2) {
// nibble order from Green Book Cap. V Par. 6.5.1.1
// NOTE: using the wrong nibble order creates visible artifacts on the image.
const auto du = deltaTable[(rr.at(x) >> 4) & 0x0F];
const auto d1 = deltaTable[rr.at(x) & 0x0F];
const auto dv = deltaTable[(rr.at(x + 1) >> 4) & 0x0F];
const auto d2 = deltaTable[rr.at(x + 1) & 0x0F];
// pixel 1
yuv.y = d1 + yuv.y;
yuv.u = du + yuv.u;
yuv.v = dv + yuv.v;
auto rgb = yuvToRgb(yuv);
tmp[x * 3] = rgb.r;
tmp[x * 3 + 1] = rgb.g;
tmp[x * 3 + 2] = rgb.b;
// pixel 2
yuv.y = d2 + yuv.y;
rgb = yuvToRgb(yuv);
tmp[(x + 1) * 3] = rgb.r;
tmp[(x + 1) * 3 + 1] = rgb.g;
tmp[(x + 1) * 3 + 2] = rgb.b;
}
rr = tmp;
}
return rr;
}
return {};
}
bool IDATChunk::resetStrideRead(QIODevice *d) const
{
return seek(d);
}
quint32 IDATChunk::strideSize(const IHDRChunk *header) const
{
if (header == nullptr) {
return 0;
}
auto rs = (header->width() * header->depth() + 7) / 8;
// No padding bytes are inserted in the data.
if (header->model() == IHDRChunk::Rgb888) {
return rs;
}
// The first pixel of each scan line must begin in a longword boundary.
if (auto mod = rs % 4)
rs += (4 - mod);
return rs;
}
/* ******************
* *** BEAM Chunk ***
* ****************** */

View File

@ -55,6 +55,10 @@ Q_DECLARE_LOGGING_CATEGORY(LOG_IFFPLUGIN)
#define CMAP_CHUNK QByteArray("CMAP")
#define CMYK_CHUNK QByteArray("CMYK") // https://wiki.amigaos.net/wiki/ILBM_IFF_Interleaved_Bitmap#ILBM.CMYK
#define DPI__CHUNK QByteArray("DPI ")
#define IDAT_CHUNK QByteArray("IDAT")
#define IHDR_CHUNK QByteArray("IHDR")
#define IPAR_CHUNK QByteArray("IPAR")
#define PLTE_CHUNK QByteArray("PLTE")
#define XBMI_CHUNK QByteArray("XBMI")
// Different palette for scanline
@ -82,11 +86,13 @@ Q_DECLARE_LOGGING_CATEGORY(LOG_IFFPLUGIN)
#define VERS_CHUNK QByteArray("VERS")
#define XMP0_CHUNK QByteArray("XMP0") // https://aminet.net/package/docs/misc/IFF-metadata
// FORM types
#define ACBM_FORM_TYPE QByteArray("ACBM")
#define ILBM_FORM_TYPE QByteArray("ILBM")
#define PBM__FORM_TYPE QByteArray("PBM ")
#define RGB8_FORM_TYPE QByteArray("RGB8")
#define RGBN_FORM_TYPE QByteArray("RGBN")
#define IMAG_FORM_TYPE QByteArray("IMAG")
#define CIMG_FOR4_TYPE QByteArray("CIMG")
#define TBMP_FOR4_TYPE QByteArray("TBMP")
@ -756,10 +762,11 @@ public:
/*!
* \brief readStride
* \param d The device.
* \param header The bitmap header.
* \param y The current scanline.
* \param header The bitmap header.
* \param camg The CAMG chunk (optional)
* \param cmap The CMAP chunk (optional)
* \param ipal The per-line palette chunk (optional)
* \param formType The type of the current form chunk.
* \return The scanline as requested for QImage.
* \warning Call resetStrideRead() once before this one.
@ -776,8 +783,6 @@ public:
* \brief resetStrideRead
* Reset the stride read set the position at the beginning of the data and reset all buffers.
* \param d The device.
* \param header The BMHDChunk chunk (mandatory)
* \param camg The CAMG chunk (optional)
* \return True on success, otherwise false.
* \sa strideRead
* \note Must be called once before strideRead().
@ -788,6 +793,7 @@ public:
* \brief safeModeId
* \param header The header.
* \param camg The CAMG chunk.
* \param cmap The CMAP chunk.
* \return The most likely ModeId if not explicitly specified.
*/
static CAMGChunk::ModeIds safeModeId(const BMHDChunk *header, const CAMGChunk *camg, const CMAPChunk *cmap = nullptr);
@ -921,6 +927,11 @@ public:
protected:
virtual bool innerReadStructure(QIODevice *d) override;
private:
QImage::Format iffFormat() const;
QImage::Format cdiFormat() const;
};
@ -1400,6 +1411,267 @@ protected:
virtual bool innerReadStructure(QIODevice *d) override;
};
/*!
* *** I-CD IFF CHUNKS ***
*/
/*!
* \brief The IHDRChunk class
* Image Header
*/
class IHDRChunk: public IFFChunk
{
public:
enum Model {
Invalid = 0, /**< Invalid model. */
Rgb888 = 1, /**< red, green, blue - 8 bits per color. */
Rgb555 = 2, /**< Green Book absolute RGB. */
DYuv = 3, /**< Green Book Delta YUV. */
CLut8 = 4, /**< Green Book 8 bit CLUT. */
CLut7 = 5, /**< Green Book 7 bit CLUT. */
CLut4 = 6, /**< Green Book 4 bit CLUT. */
CLut3 = 7, /**< Green Book 3 bit CLUT. */
Rle7 = 8, /**< Green Book runlength coded 7 bit CLUT. */
Rle3 = 9, /**< Green Book runlength coded 3 bit CLUT. */
PaletteOnly = 10 /**< color palette only. */
};
enum DYuvKind {
One = 0,
Each = 1
};
struct Yuv {
Yuv(quint8 y0 = 0, quint8 u0 = 0, quint8 v0 = 0) : y(y0), u(u0), v(v0) {}
quint8 y;
quint8 u;
quint8 v;
};
virtual ~IHDRChunk() override;
IHDRChunk();
IHDRChunk(const IHDRChunk& other) = default;
IHDRChunk& operator =(const IHDRChunk& other) = default;
virtual bool isValid() const override;
/*!
* \brief width
* \return Width of the bitmap in pixels.
*/
qint32 width() const;
/*!
* \brief height
* \return Height of the bitmap in pixels.
*/
qint32 height() const;
/*!
* \brief size
* \return Size in pixels.
*/
QSize size() const;
/*!
* \brief lineSize
* Physical width of image (number of bytes in each scan line, including any data required at
* the end of each scan line for padding [see description of each models IDAT chunk for padding
* rules]) This field is not used when model() = Rle7 or Rle3.
* When model() = Rgb555, this field defines the size of one scan line of the upper
* or lower portion of the pixel data, but not the size of them both together.
*/
qint32 lineSize() const;
/*!
* \brief model
* Image model (coding method)
*/
Model model() const;
/*!
* \brief depth
* Physical size of pixel (number of bits per pixel used in storing image data) When
* model() = Rle7 or Rle3, this value only represents the size of a
* single pixel; the size of a run of pixels is indeterminate.
*/
quint16 depth() const;
/*!
* \brief yuvKind
* if model() = DYuv, indicates whether there is one DYUV start value for all
* scan lines (in yuvStart()), or whether each scan line has its own start value in the
* YUVS chunk which follows.
*/
DYuvKind yuvKind() const;
/*!
* \brief yuvStart
* Start values for DYUV image if model() = DYuv and dYuvKind() = One
*/
Yuv yuvStart() const;
CHUNKID_DEFINE(IHDR_CHUNK)
protected:
virtual bool innerReadStructure(QIODevice *d) override;
};
/*!
* \brief The IHDRChunk class
*/
class IPARChunk: public IFFChunk
{
public:
struct Rgb {
Rgb(quint8 r0 = 0, quint8 g0 = 0, quint8 b0 = 0) : r(r0), g(g0), b(b0) {}
quint8 r;
quint8 g;
quint8 b;
};
virtual ~IPARChunk() override;
IPARChunk();
IPARChunk(const IPARChunk& other) = default;
IPARChunk& operator =(const IPARChunk& other) = default;
virtual bool isValid() const override;
/*!
* \brief xOffset
* X offset of origin in source image [0 < xOffset() < xPage()]
*/
qint32 xOffset() const;
/*!
* \brief yOffset
* \returnX offset of origin in source image [0 < yOffset() < yPage()]
*/
qint32 yOffset() const;
/*!
* \brief aspectRatio
* Aspect ratio of pixels in source image.
*/
double aspectRatio() const;
/*!
* \brief xPage
* X size of source image.
*/
qint32 xPage() const;
/*!
* \brief yPage
* Y size of source image.
*/
qint32 yPage() const;
/*!
* \brief xGrub
* X location of hot spot within image.
*/
qint32 xGrub() const;
/*!
* \brief yGrub
* Y location of hot spot within image.
*/
qint32 yGrub() const;
/*!
* \brief transparency
* Transparent color.
*/
Rgb transparency() const;
/*!
* \brief mask
* Mask color.
*/
Rgb mask() const;
CHUNKID_DEFINE(IPAR_CHUNK)
protected:
virtual bool innerReadStructure(QIODevice *d) override;
};
/*!
* \brief The PLTEChunk class
*/
class PLTEChunk : public CMAPChunk
{
public:
virtual ~PLTEChunk() override;
PLTEChunk();
PLTEChunk(const PLTEChunk& other) = default;
PLTEChunk& operator =(const PLTEChunk& other) = default;
virtual bool isValid() const override;
/*!
* \brief count
* \return The number of color in the palette.
*/
virtual qint32 count() const override;
CHUNKID_DEFINE(PLTE_CHUNK)
protected:
qint32 offset() const;
qint32 total() const;
virtual QList<QRgb> innerPalette() const override;
};
/*!
* \brief The IDATChunk class
*/
class IDATChunk : public IFFChunk
{
public:
virtual ~IDATChunk() override;
IDATChunk();
IDATChunk(const IDATChunk& other) = default;
IDATChunk& operator =(const IDATChunk& other) = default;
virtual bool isValid() const override;
CHUNKID_DEFINE(IDAT_CHUNK)
/*!
* \brief readStride
* \param d The device.
* \param y The current scanline.
* \param header The bitmap header.
* \param params The additional parameters (optional)
* \return The scanline as requested for QImage.
* \warning Call resetStrideRead() once before this one.
*/
QByteArray strideRead(QIODevice *d,
qint32 y,
const IHDRChunk *header,
const IPARChunk *params = nullptr) const;
/*!
* \brief resetStrideRead
* Reset the stride read set the position at the beginning of the data and reset all buffers.
* \param d The device.
* \return True on success, otherwise false.
* \sa strideRead
* \note Must be called once before strideRead().
*/
bool resetStrideRead(QIODevice *d) const;
protected:
quint32 strideSize(const IHDRChunk *header) const;
};
/*!
* *** UNDOCUMENTED CHUNKS ***

View File

@ -262,14 +262,21 @@ static void addMetadata(QImage &img, const IFOR_Chunk *form)
// if no explicit resolution was found, apply the aspect ratio to the default one
if (!resChanged) {
auto headers = IFFChunk::searchT<BMHDChunk>(form);
if (!headers.isEmpty()) {
auto xr = headers.first()->xAspectRatio();
auto yr = headers.first()->yAspectRatio();
if (xr > 0 && yr > 0 && xr > yr) {
img.setDotsPerMeterX(img.dotsPerMeterX() * yr / xr);
} else if (xr > 0 && yr > 0 && xr < yr) {
img.setDotsPerMeterY(img.dotsPerMeterY() * xr / yr);
if (form->formType() == IMAG_FORM_TYPE) {
auto params = IFFChunk::searchT<IPARChunk>(form);
if (!params.isEmpty()) {
img.setDotsPerMeterY(img.dotsPerMeterY() * params.first()->aspectRatio());
}
} else {
auto headers = IFFChunk::searchT<BMHDChunk>(form);
if (!headers.isEmpty()) {
auto xr = headers.first()->xAspectRatio();
auto yr = headers.first()->yAspectRatio();
if (xr > 0 && yr > 0 && xr > yr) {
img.setDotsPerMeterX(img.dotsPerMeterX() * yr / xr);
} else if (xr > 0 && yr > 0 && xr < yr) {
img.setDotsPerMeterY(img.dotsPerMeterY() * xr / yr);
}
}
}
}
@ -475,6 +482,67 @@ bool IFFHandler::readMayaImage(QImage *image)
return true;
}
bool IFFHandler::readCDIImage(QImage *image)
{
auto forms = d->searchForms<FORMChunk>();
if (forms.isEmpty()) {
return false;
}
auto cin = qBound(0, currentImageNumber(), int(forms.size() - 1));
auto &&form = forms.at(cin);
// show the first one (I don't have a sample with many images)
auto headers = IFFChunk::searchT<IHDRChunk>(form);
if (headers.isEmpty()) {
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readCDIImage(): no supported image found";
return false;
}
// create the image
auto &&header = headers.first();
auto img = imageAlloc(header->size(), form->format());
if (img.isNull()) {
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readCDIImage(): error while allocating the image";
return false;
}
// set the palette
if (img.format() == QImage::Format_Indexed8) {
auto pltes = IFFChunk::searchT<PLTEChunk>(form);
if (!pltes.isEmpty()) {
img.setColorTable(pltes.first()->palette());
}
}
// decoding the image
auto bodies = IFFChunk::searchT<IDATChunk>(form);
if (bodies.isEmpty()) {
img.fill(0);
} else {
auto &&body = bodies.first();
if (!body->resetStrideRead(device())) {
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readCDIImage(): error while reading image data";
return false;
}
auto pars = IFFChunk::searchT<IPARChunk>(form);
for (auto y = 0, h = img.height(); y < h; ++y) {
auto line = reinterpret_cast<char*>(img.scanLine(y));
auto ba = body->strideRead(device(), y, header, pars.isEmpty() ? nullptr : pars.first());
if (ba.isEmpty()) {
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readCDIImage(): error while reading image scanline";
return false;
}
memcpy(line, ba.constData(), std::min(img.bytesPerLine(), ba.size()));
}
}
// set metadata (including image resolution)
addMetadata(img, form);
*image = img;
return true;
}
bool IFFHandler::read(QImage *image)
{
if (!d->readStructure(device())) {
@ -490,6 +558,10 @@ bool IFFHandler::read(QImage *image)
return true;
}
if (readCDIImage(image)) {
return true;
}
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::read(): no supported image found";
return false;
}

View File

@ -35,6 +35,8 @@ private:
bool readMayaImage(QImage *image);
bool readCDIImage(QImage *image);
private:
const QScopedPointer<IFFHandlerPrivate> d;
};