mirror of
https://invent.kde.org/frameworks/kimageformats.git
synced 2026-02-23 08:03:02 -05:00
Add support for CD-I IFF images
This commit is contained in:
@ -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.
|
||||
|
||||
|
||||
BIN
autotests/read/iff/cdi_cl7.iff
Normal file
BIN
autotests/read/iff/cdi_cl7.iff
Normal file
Binary file not shown.
BIN
autotests/read/iff/cdi_cl7.png
Normal file
BIN
autotests/read/iff/cdi_cl7.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
@ -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 ***
|
||||
* ****************** */
|
||||
|
||||
@ -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 model’s 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 ***
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -35,6 +35,8 @@ private:
|
||||
|
||||
bool readMayaImage(QImage *image);
|
||||
|
||||
bool readCDIImage(QImage *image);
|
||||
|
||||
private:
|
||||
const QScopedPointer<IFFHandlerPrivate> d;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user