mirror of
https://invent.kde.org/frameworks/kimageformats.git
synced 2026-06-10 01:59:11 -04:00
IFF: DEEP image support
This commit is contained in:
committed by
Mirco Miranda
parent
1206598337
commit
49a8fd38c8
@@ -403,6 +403,7 @@ The plugin supports the following image data:
|
||||
- FORM IMAG (Compact Disc-Interactive): It supports CLut4, CLut7, CLut8, Rle7
|
||||
and DYuv formats.
|
||||
- FORM RGFX: It supports uncompressed images only.
|
||||
- FORM DEEP: It supports uncompressed, RLE and TVDC images.
|
||||
- FOR4 CIMG (Maya Image File Format): It supports 24/48-bit RGB and 32/64-bit
|
||||
RGBA images.
|
||||
|
||||
|
||||
BIN
autotests/read/iff/sv5_unc_deep.iff
Normal file
BIN
autotests/read/iff/sv5_unc_deep.iff
Normal file
Binary file not shown.
BIN
autotests/read/iff/sv5_unc_deep.png
Normal file
BIN
autotests/read/iff/sv5_unc_deep.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.6 KiB |
@@ -295,6 +295,14 @@ IFFChunk::ChunkList IFFChunk::innerFromDevice(QIODevice *d, bool *ok, IFFChunk *
|
||||
chunk = QSharedPointer<IFFChunk>(new CTBLChunk());
|
||||
} else if (cid == DATE_CHUNK) {
|
||||
chunk = QSharedPointer<IFFChunk>(new DATEChunk());
|
||||
} else if (cid == DBOD_CHUNK) {
|
||||
chunk = QSharedPointer<IFFChunk>(new DBODChunk());
|
||||
} else if (cid == DGBL_CHUNK) {
|
||||
chunk = QSharedPointer<IFFChunk>(new DGBLChunk());
|
||||
} else if (cid == DLOC_CHUNK) {
|
||||
chunk = QSharedPointer<IFFChunk>(new DLOCChunk());
|
||||
} else if (cid == DPEL_CHUNK) {
|
||||
chunk = QSharedPointer<IFFChunk>(new DPELChunk());
|
||||
} else if (cid == DPI__CHUNK) {
|
||||
chunk = QSharedPointer<IFFChunk>(new DPIChunk());
|
||||
} else if (cid == EXIF_CHUNK) {
|
||||
@@ -341,6 +349,8 @@ IFFChunk::ChunkList IFFChunk::innerFromDevice(QIODevice *d, bool *ok, IFFChunk *
|
||||
chunk = QSharedPointer<IFFChunk>(new SHAMChunk());
|
||||
} else if (cid == TBHD_CHUNK) {
|
||||
chunk = QSharedPointer<IFFChunk>(new TBHDChunk());
|
||||
} else if (cid == TVDC_CHUNK) {
|
||||
chunk = QSharedPointer<IFFChunk>(new TVDCChunk());
|
||||
} else if (cid == VDAT_CHUNK) {
|
||||
chunk = QSharedPointer<IFFChunk>(new VDATChunk());
|
||||
} else if (cid == VERS_CHUNK) {
|
||||
@@ -1468,6 +1478,8 @@ bool FORMChunk::innerReadStructure(QIODevice *d)
|
||||
setChunks(IFFChunk::innerFromDevice(d, &ok, this));
|
||||
} else if (_type == RGFX_FORM_TYPE) {
|
||||
setChunks(IFFChunk::innerFromDevice(d, &ok, this));
|
||||
} else if (_type == DEEP_FORM_TYPE || _type == TVPP_FORM_TYPE) {
|
||||
setChunks(IFFChunk::innerFromDevice(d, &ok, this));
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
@@ -1585,6 +1597,72 @@ QImage::Format FORMChunk::rgfxFormat() const
|
||||
return QImage::Format_Invalid;
|
||||
}
|
||||
|
||||
QImage::Format FORMChunk::deepFormat() const
|
||||
{
|
||||
auto pels = IFFChunk::searchT<DPELChunk>(chunks());
|
||||
if (pels.isEmpty()) {
|
||||
return QImage::Format_Invalid;
|
||||
}
|
||||
auto list = pels.first()->elements();
|
||||
|
||||
// support for same depth on all elements
|
||||
auto depth = -1;
|
||||
for (auto &&el : list) {
|
||||
if (depth < 0)
|
||||
depth = el.depth;
|
||||
if (depth != el.depth)
|
||||
return QImage::Format_Invalid;
|
||||
}
|
||||
|
||||
// calculate the image format
|
||||
if (list.size() == 4) {
|
||||
if (list.at(0).type == DPELChunk::Red &&
|
||||
list.at(1).type == DPELChunk::Green &&
|
||||
list.at(2).type == DPELChunk::Blue &&
|
||||
list.at(3).type == DPELChunk::Alpha) {
|
||||
if (depth == 8)
|
||||
return FORMAT_RGBA_8BIT;
|
||||
else if (depth == 16)
|
||||
return QImage::Format_RGBA64;
|
||||
} else if (list.at(0).type == DPELChunk::Cyan &&
|
||||
list.at(1).type == DPELChunk::Magenta &&
|
||||
list.at(2).type == DPELChunk::Yellow &&
|
||||
list.at(3).type == DPELChunk::Black) {
|
||||
if (depth == 8)
|
||||
return QImage::Format_CMYK8888;
|
||||
} else if (list.at(0).type == DPELChunk::Red &&
|
||||
list.at(1).type == DPELChunk::Green &&
|
||||
list.at(2).type == DPELChunk::Blue) {
|
||||
// unknown type of channel 4 -> ignoring it
|
||||
if (depth == 8)
|
||||
return QImage::Format_RGBX8888;
|
||||
else if (depth == 16)
|
||||
return QImage::Format_RGBX64;
|
||||
}
|
||||
} else if (list.size() == 3) {
|
||||
if (list.at(0).type == DPELChunk::Red &&
|
||||
list.at(1).type == DPELChunk::Green &&
|
||||
list.at(2).type == DPELChunk::Blue) {
|
||||
if (depth == 8)
|
||||
return FORMAT_RGB_8BIT;
|
||||
} else if (list.at(0).type == DPELChunk::Blue &&
|
||||
list.at(1).type == DPELChunk::Green &&
|
||||
list.at(2).type == DPELChunk::Red) {
|
||||
if (depth == 8)
|
||||
return QImage::Format_BGR888;
|
||||
}
|
||||
} else if (list.size() == 1) {
|
||||
if (depth == 1)
|
||||
return QImage::Format_Mono;
|
||||
else if (depth == 8)
|
||||
return QImage::Format_Grayscale8;
|
||||
else if (depth == 16)
|
||||
return QImage::Format_Grayscale16;
|
||||
}
|
||||
|
||||
return QImage::Format_Invalid;
|
||||
}
|
||||
|
||||
QByteArray FORMChunk::formType() const
|
||||
{
|
||||
return _type;
|
||||
@@ -1596,6 +1674,8 @@ QImage::Format FORMChunk::format() const
|
||||
return cdiFormat();
|
||||
} else if (formType() == RGFX_FORM_TYPE) {
|
||||
return rgfxFormat();
|
||||
} else if (formType() == DEEP_FORM_TYPE || formType() == TVPP_FORM_TYPE) {
|
||||
return deepFormat();
|
||||
}
|
||||
return iffFormat();
|
||||
}
|
||||
@@ -1612,6 +1692,15 @@ QSize FORMChunk::size() const
|
||||
if (!rghds.isEmpty()) {
|
||||
return rghds.first()->size();
|
||||
}
|
||||
} else if (formType() == DEEP_FORM_TYPE || formType() == TVPP_FORM_TYPE) {
|
||||
auto dlocs = IFFChunk::searchT<DLOCChunk>(chunks());
|
||||
if (!dlocs.isEmpty()) {
|
||||
return dlocs.first()->size();
|
||||
}
|
||||
auto dgbls = IFFChunk::searchT<DGBLChunk>(chunks());
|
||||
if (!dgbls.isEmpty()) {
|
||||
return dgbls.first()->size();
|
||||
}
|
||||
} else {
|
||||
auto bmhds = IFFChunk::searchT<BMHDChunk>(chunks());
|
||||
if (!bmhds.isEmpty()) {
|
||||
@@ -1735,6 +1824,8 @@ bool CATChunk::innerReadStructure(QIODevice *d)
|
||||
setChunks(IFFChunk::innerFromDevice(d, &ok, this));
|
||||
} else if (_type == RGFX_FORM_TYPE) {
|
||||
setChunks(IFFChunk::innerFromDevice(d, &ok, this));
|
||||
} else if (_type == DEEP_FORM_TYPE || _type == TVPP_FORM_TYPE) {
|
||||
setChunks(IFFChunk::innerFromDevice(d, &ok, this));
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
@@ -3574,6 +3665,427 @@ quint32 RBODChunk::strideSize(const RGHDChunk *header) const
|
||||
}
|
||||
|
||||
|
||||
/* ******************
|
||||
* *** DGBL Chunk ***
|
||||
* ****************** */
|
||||
|
||||
DGBLChunk::~DGBLChunk()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
DGBLChunk::DGBLChunk()
|
||||
: IFFChunk()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
DGBLChunk::Compression DGBLChunk::compression() const
|
||||
{
|
||||
if (!isValid()) {
|
||||
return Compression::Uncompressed;
|
||||
}
|
||||
return Compression(ui16(data(), 4));
|
||||
|
||||
}
|
||||
|
||||
qint32 DGBLChunk::width() const
|
||||
{
|
||||
if (!isValid()) {
|
||||
return 0;
|
||||
}
|
||||
return qint32(ui16(data(), 0));
|
||||
}
|
||||
|
||||
qint32 DGBLChunk::height() const
|
||||
{
|
||||
if (!isValid()) {
|
||||
return 0;
|
||||
}
|
||||
return qint32(ui16(data(), 2));
|
||||
}
|
||||
|
||||
quint8 DGBLChunk::xAspectRatio() const
|
||||
{
|
||||
if (!isValid()) {
|
||||
return 0;
|
||||
}
|
||||
return quint8(data().at(6));
|
||||
}
|
||||
|
||||
quint8 DGBLChunk::yAspectRatio() const
|
||||
{
|
||||
if (!isValid()) {
|
||||
return 0;
|
||||
}
|
||||
return quint8(data().at(7));
|
||||
}
|
||||
|
||||
bool DGBLChunk::isValid() const
|
||||
{
|
||||
if (dataBytes() < 8) {
|
||||
return false;
|
||||
}
|
||||
return chunkId() == DGBLChunk::defaultChunkId();
|
||||
}
|
||||
|
||||
bool DGBLChunk::innerReadStructure(QIODevice *d)
|
||||
{
|
||||
return cacheData(d);
|
||||
}
|
||||
|
||||
|
||||
/* ******************
|
||||
* *** DPEL Chunk ***
|
||||
* ****************** */
|
||||
|
||||
DPELChunk::~DPELChunk()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
DPELChunk::DPELChunk()
|
||||
: IFFChunk()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
qint32 DPELChunk::count() const
|
||||
{
|
||||
if (dataBytes() < 4) {
|
||||
return 0;
|
||||
}
|
||||
auto cnt = i32(data(), 0);
|
||||
if (cnt < 0 || cnt > 128) {
|
||||
// an image should have 3, 4 or 5 channels:
|
||||
// 128 is enough to give an error.
|
||||
cnt = 0;
|
||||
}
|
||||
return cnt;
|
||||
}
|
||||
|
||||
qint32 DPELChunk::depth() const
|
||||
{
|
||||
auto depth = 0;
|
||||
auto list = elements();
|
||||
for (auto &&el : list) {
|
||||
depth += el.depth;
|
||||
}
|
||||
return depth;
|
||||
}
|
||||
|
||||
QList<DPELChunk::Element> DPELChunk::elements() const
|
||||
{
|
||||
QList<DPELChunk::Element> list;
|
||||
if (isValid()) {
|
||||
for (auto n = count(), i = 0; i < n; ++i) {
|
||||
auto idx = 4 + i * 4;
|
||||
list << DPELChunk::Element(DataType(ui16(data(), idx)),
|
||||
ui16(data(), idx + 2));
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
bool DPELChunk::isValid() const
|
||||
{
|
||||
if (dataBytes() < quint32(4 + count() * 4)) {
|
||||
return false;
|
||||
}
|
||||
return chunkId() == DPELChunk::defaultChunkId();
|
||||
}
|
||||
|
||||
bool DPELChunk::innerReadStructure(QIODevice *d)
|
||||
{
|
||||
return cacheData(d);
|
||||
}
|
||||
|
||||
|
||||
/* ******************
|
||||
* *** DLOC Chunk ***
|
||||
* ****************** */
|
||||
|
||||
DLOCChunk::~DLOCChunk()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
DLOCChunk::DLOCChunk()
|
||||
: IFFChunk()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
qint32 DLOCChunk::width() const
|
||||
{
|
||||
if (!isValid()) {
|
||||
return 0;
|
||||
}
|
||||
return qint32(ui16(data(), 0));
|
||||
}
|
||||
|
||||
qint32 DLOCChunk::height() const
|
||||
{
|
||||
if (!isValid()) {
|
||||
return 0;
|
||||
}
|
||||
return qint32(ui16(data(), 2));
|
||||
}
|
||||
|
||||
QSize DLOCChunk::size() const
|
||||
{
|
||||
return QSize(width(), height());
|
||||
}
|
||||
|
||||
qint32 DLOCChunk::xOffset() const
|
||||
{
|
||||
if (!isValid()) {
|
||||
return 0;
|
||||
}
|
||||
return qint32(i16(data(), 4));
|
||||
}
|
||||
|
||||
qint32 DLOCChunk::yOffset() const
|
||||
{
|
||||
if (!isValid()) {
|
||||
return 0;
|
||||
}
|
||||
return qint32(i16(data(), 6));
|
||||
}
|
||||
|
||||
bool DLOCChunk::isValid() const
|
||||
{
|
||||
if (dataBytes() < 8) {
|
||||
return false;
|
||||
}
|
||||
return chunkId() == DLOCChunk::defaultChunkId();
|
||||
}
|
||||
|
||||
bool DLOCChunk::innerReadStructure(QIODevice *d)
|
||||
{
|
||||
return cacheData(d);
|
||||
}
|
||||
|
||||
|
||||
/* ******************
|
||||
* *** TVDC Chunk ***
|
||||
* ****************** */
|
||||
|
||||
TVDCChunk::~TVDCChunk()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
TVDCChunk::TVDCChunk()
|
||||
: IFFChunk()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
qint32 TVDCChunk::count() const
|
||||
{
|
||||
if (!isValid()) {
|
||||
return 0;
|
||||
}
|
||||
return dataBytes() / 2;
|
||||
}
|
||||
|
||||
QList<quint16> TVDCChunk::table() const
|
||||
{
|
||||
QList<quint16> list;
|
||||
if (isValid()) {
|
||||
for (auto n = count(), i = 0; i < n; ++i) {
|
||||
list << ui16(data(), i * 2);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
bool TVDCChunk::isValid() const
|
||||
{
|
||||
if (dataBytes() < 32) {
|
||||
return false;
|
||||
}
|
||||
return chunkId() == TVDCChunk::defaultChunkId();
|
||||
}
|
||||
|
||||
bool TVDCChunk::innerReadStructure(QIODevice *d)
|
||||
{
|
||||
return cacheData(d);
|
||||
}
|
||||
|
||||
|
||||
/* ******************
|
||||
* *** DBOD Chunk ***
|
||||
* ****************** */
|
||||
|
||||
DBODChunk::~DBODChunk()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
DBODChunk::DBODChunk()
|
||||
: IFFChunk()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool DBODChunk::isValid() const
|
||||
{
|
||||
return chunkId() == DBODChunk::defaultChunkId();
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief rleDeepDecompress
|
||||
* Each run contains a pixel (all components)
|
||||
*/
|
||||
inline qint64 rleDeepDecompress(QIODevice *input, char *output, qint64 olen, qint32 pixelBytes)
|
||||
{
|
||||
qint64 j = 0;
|
||||
pixelBytes = std::max(1, pixelBytes);
|
||||
for (qint64 rr = 0, available = olen; j < olen; available = olen - j) {
|
||||
char n;
|
||||
|
||||
// check the output buffer space for the next run
|
||||
if (available < 129 * pixelBytes) {
|
||||
if (input->peek(&n, 1) != 1) { // end of data (or error)
|
||||
break;
|
||||
}
|
||||
if ((static_cast<signed char>(n) >= 0 ? qint64(n) + 1 : qint64(1 - n)) * pixelBytes > available)
|
||||
break;
|
||||
}
|
||||
|
||||
// decompress
|
||||
if (input->read(&n, 1) != 1) { // end of data (or error)
|
||||
break;
|
||||
}
|
||||
|
||||
if (static_cast<signed char>(n) >= 0) {
|
||||
rr = input->read(output + j, (qint64(n) + 1) * pixelBytes);
|
||||
if (rr == -1) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
auto buf = input->read(pixelBytes);
|
||||
if (buf.size() != pixelBytes) {
|
||||
break;
|
||||
}
|
||||
rr = qint64(1 - static_cast<signed char>(n));
|
||||
for (qint64 i = 0; i < rr; ++i) {
|
||||
std::memcpy(output + j + i * pixelBytes, buf.data(), buf.size());
|
||||
}
|
||||
rr *= pixelBytes;
|
||||
}
|
||||
|
||||
j += rr;
|
||||
}
|
||||
return j;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief tvdcDeepDecompress
|
||||
* the compression is made line by line for each elementof the chunk DPEL.
|
||||
* For RGBA for example we have a Red line, a Green line, and so on.
|
||||
*/
|
||||
inline qint64 tvdcDeepDecompress(QIODevice *input, char *output, qint64 olen, const QList<quint16>& table)
|
||||
{
|
||||
if (table.size() != 16) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
quint8 v = 0;
|
||||
quint8 last = 0;
|
||||
for (qint64 i = 0, pos = 0, lastRead = -1; i < olen; ++i) {
|
||||
if ((pos >> 1) != lastRead) {
|
||||
char n;
|
||||
if (input->read(&n, 1) != 1) {
|
||||
return -1;
|
||||
}
|
||||
lastRead = (pos >> 1);
|
||||
last = quint8(n);
|
||||
}
|
||||
quint8 d = last;
|
||||
if (pos++ & 1) {
|
||||
d &= 0xf;
|
||||
} else {
|
||||
d >>= 4;
|
||||
}
|
||||
v += table.at(d);
|
||||
output[i] = char(v);
|
||||
if (!table.at(d)) {
|
||||
if ((pos >> 1) != lastRead) {
|
||||
char n;
|
||||
if (input->read(&n, 1) != 1) {
|
||||
return -1;
|
||||
}
|
||||
lastRead = (pos >> 1);
|
||||
last = quint8(n);
|
||||
}
|
||||
d = last;
|
||||
if (pos++ & 1) {
|
||||
d &= 0xf;
|
||||
} else {
|
||||
d >>= 4;
|
||||
}
|
||||
while (d--) {
|
||||
if (i < olen - 1) {
|
||||
output[++i] = char(v);
|
||||
continue;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return olen;
|
||||
}
|
||||
|
||||
QByteArray DBODChunk::strideRead(QIODevice *d, qint32, const DGBLChunk *header, const DPELChunk *pel, const DLOCChunk *loc, const TVDCChunk *tvdc) const
|
||||
{
|
||||
auto size = strideSize(header, pel, loc);
|
||||
if (size == 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
qint64 rr = 0;
|
||||
QByteArray planes(size, char());
|
||||
if (header->compression() == DGBLChunk::Compression::Uncompressed) {
|
||||
rr = d->read(planes.data(), planes.size());
|
||||
} else if (header->compression() == DGBLChunk::Compression::Rle) {
|
||||
rr = rleDeepDecompress(d, planes.data(), planes.size(), pel->depth() / 8);
|
||||
} else if (header->compression() == DGBLChunk::Compression::TvDeepCompression) {
|
||||
if (tvdc) { // TVDC is planar, so I have to convert to chunky
|
||||
auto table = tvdc->table();
|
||||
for (auto i = 0, n = pel->count(); i < n; ++i) {
|
||||
QByteArray ba(size / n, char());
|
||||
rr += tvdcDeepDecompress(d, ba.data(), ba.size(), tvdc->table());
|
||||
for (auto j = 0, m = int(ba.size()); j < m; ++j) {
|
||||
planes[i + j * n] = ba.at(j);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
qCDebug(LOG_IFFPLUGIN) << "DBODChunk::strideRead(): unknown compression" << header->compression();
|
||||
}
|
||||
|
||||
// Uncompressed, Rle and TvDeepCompression: one line at a time.
|
||||
if (rr != size)
|
||||
return {};
|
||||
return planes;
|
||||
}
|
||||
|
||||
bool DBODChunk::resetStrideRead(QIODevice *d) const
|
||||
{
|
||||
return seek(d);
|
||||
}
|
||||
|
||||
quint32 DBODChunk::strideSize(const DGBLChunk *header, const DPELChunk *pel, const DLOCChunk *loc) const
|
||||
{
|
||||
auto width = loc ? loc->width() : header->width();
|
||||
return (width * pel->depth() + 7) / 8;
|
||||
}
|
||||
|
||||
|
||||
/* ******************
|
||||
* *** BEAM Chunk ***
|
||||
* ****************** */
|
||||
|
||||
@@ -55,6 +55,10 @@ Q_DECLARE_LOGGING_CATEGORY(LOG_IFFPLUGIN)
|
||||
#define CAMG_CHUNK QByteArray("CAMG")
|
||||
#define CMAP_CHUNK QByteArray("CMAP")
|
||||
#define CMYK_CHUNK QByteArray("CMYK") // https://wiki.amigaos.net/wiki/ILBM_IFF_Interleaved_Bitmap#ILBM.CMYK
|
||||
#define DBOD_CHUNK QByteArray("DBOD")
|
||||
#define DGBL_CHUNK QByteArray("DGBL")
|
||||
#define DLOC_CHUNK QByteArray("DLOC")
|
||||
#define DPEL_CHUNK QByteArray("DPEL")
|
||||
#define DPI__CHUNK QByteArray("DPI ")
|
||||
#define IDAT_CHUNK QByteArray("IDAT")
|
||||
#define IHDR_CHUNK QByteArray("IHDR")
|
||||
@@ -65,6 +69,7 @@ Q_DECLARE_LOGGING_CATEGORY(LOG_IFFPLUGIN)
|
||||
#define RFLG_CHUNK QByteArray("RFLG")
|
||||
#define RGHD_CHUNK QByteArray("RGHD")
|
||||
#define RSCM_CHUNK QByteArray("RSCM")
|
||||
#define TVDC_CHUNK QByteArray("TVDC")
|
||||
#define XBMI_CHUNK QByteArray("XBMI")
|
||||
#define YUVS_CHUNK QByteArray("YUVS")
|
||||
|
||||
@@ -96,12 +101,14 @@ Q_DECLARE_LOGGING_CATEGORY(LOG_IFFPLUGIN)
|
||||
|
||||
// FORM types
|
||||
#define ACBM_FORM_TYPE QByteArray("ACBM")
|
||||
#define DEEP_FORM_TYPE QByteArray("DEEP")
|
||||
#define ILBM_FORM_TYPE QByteArray("ILBM")
|
||||
#define IMAG_FORM_TYPE QByteArray("IMAG")
|
||||
#define PBM__FORM_TYPE QByteArray("PBM ")
|
||||
#define RGB8_FORM_TYPE QByteArray("RGB8")
|
||||
#define RGBN_FORM_TYPE QByteArray("RGBN")
|
||||
#define RGFX_FORM_TYPE QByteArray("RGFX")
|
||||
#define TVPP_FORM_TYPE QByteArray("TVPP") // same as DEEP
|
||||
|
||||
#define CIMG_FOR4_TYPE QByteArray("CIMG")
|
||||
#define TBMP_FOR4_TYPE QByteArray("TBMP")
|
||||
@@ -642,11 +649,23 @@ class CAMGChunk : public IFFChunk
|
||||
{
|
||||
public:
|
||||
enum ModeId {
|
||||
LoResLace = 0x0004,
|
||||
GenLockVideo = 0x0002,
|
||||
InterlacedDisplay = 0x0004,
|
||||
DoubleScan = 0x0008,
|
||||
SuperHighResolution = 0x0020,
|
||||
PlayfieldBitplaneAdjust = 0x0040,
|
||||
HalfBrite = 0x0080,
|
||||
LoResDpf = 0x0400,
|
||||
Ham = 0x0800,
|
||||
HiRes = 0x8000
|
||||
GenLockAudio = 0x0100,
|
||||
DualPlayfield = 0x0400,
|
||||
HoldAndModify = 0x0800,
|
||||
ExtendedMode = 0x1000,
|
||||
ViewPortHide = 0x2000,
|
||||
Sprites = 0x4000,
|
||||
HighResolution = 0x8000,
|
||||
|
||||
// aliases
|
||||
Lace = InterlacedDisplay,
|
||||
Ham = HoldAndModify
|
||||
};
|
||||
|
||||
Q_DECLARE_FLAGS(ModeIds, ModeId)
|
||||
@@ -946,6 +965,8 @@ private:
|
||||
QImage::Format cdiFormat() const;
|
||||
|
||||
QImage::Format rgfxFormat() const;
|
||||
|
||||
QImage::Format deepFormat() const;
|
||||
};
|
||||
|
||||
|
||||
@@ -1946,6 +1967,266 @@ private:
|
||||
};
|
||||
|
||||
|
||||
/*!
|
||||
* *** DEEP IFF CHUNKS ***
|
||||
*/
|
||||
|
||||
/*!
|
||||
* \brief The DGBLChunk class
|
||||
*/
|
||||
class DGBLChunk : public IFFChunk
|
||||
{
|
||||
public:
|
||||
enum Compression : quint16 {
|
||||
Uncompressed = 0,
|
||||
Rle = 1,
|
||||
Huffman = 2,
|
||||
DynamicHuffman = 3,
|
||||
Jpeg = 4,
|
||||
TvDeepCompression = 5
|
||||
};
|
||||
|
||||
virtual ~DGBLChunk() override;
|
||||
DGBLChunk();
|
||||
DGBLChunk(const DGBLChunk& other) = default;
|
||||
DGBLChunk& operator =(const DGBLChunk& other) = default;
|
||||
|
||||
/*!
|
||||
* \brief compression
|
||||
* \return The type of compression used.
|
||||
*/
|
||||
Compression compression() const;
|
||||
|
||||
/*!
|
||||
* \brief width
|
||||
* \return Width of the source display in pixels.
|
||||
*/
|
||||
qint32 width() const;
|
||||
|
||||
/*!
|
||||
* \brief height
|
||||
* \return Height of the source display in pixels.
|
||||
*/
|
||||
qint32 height() const;
|
||||
|
||||
/*!
|
||||
* \brief size
|
||||
* \return Size of the source display in pixels.
|
||||
*/
|
||||
QSize size() const
|
||||
{
|
||||
return QSize(width(), height());
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief xAspectRatio
|
||||
* \return X pixel aspect.
|
||||
*/
|
||||
quint8 xAspectRatio() const;
|
||||
|
||||
/*!
|
||||
* \brief yAspectRatio
|
||||
* \return Y pixel aspect.
|
||||
*/
|
||||
quint8 yAspectRatio() const;
|
||||
|
||||
virtual bool isValid() const override;
|
||||
|
||||
CHUNKID_DEFINE(DGBL_CHUNK)
|
||||
|
||||
protected:
|
||||
virtual bool innerReadStructure(QIODevice *d) override;
|
||||
};
|
||||
|
||||
|
||||
/*!
|
||||
* \brief The DPELChunk class
|
||||
*/
|
||||
class DPELChunk : public IFFChunk
|
||||
{
|
||||
public:
|
||||
enum DataType : quint16 {
|
||||
Invalid = 0,
|
||||
Red = 1,
|
||||
Green = 2,
|
||||
Blue = 3,
|
||||
Alpha = 4,
|
||||
Yellow = 5,
|
||||
Cyan = 6,
|
||||
Magenta = 7,
|
||||
Black = 8,
|
||||
Mask = 9,
|
||||
ZBuffer = 10,
|
||||
Opacity = 11,
|
||||
LinearKey = 12,
|
||||
BinaryKey = 13
|
||||
};
|
||||
|
||||
struct Element {
|
||||
Element(const DataType& t = DataType::Invalid, quint16 d = 0) : type(t), depth(d) {}
|
||||
DataType type;
|
||||
quint16 depth;
|
||||
};
|
||||
|
||||
virtual ~DPELChunk() override;
|
||||
DPELChunk();
|
||||
DPELChunk(const DPELChunk& other) = default;
|
||||
DPELChunk& operator =(const DPELChunk& other) = default;
|
||||
|
||||
/*!
|
||||
* \brief count
|
||||
* \return The number of elements
|
||||
*/
|
||||
qint32 count() const;
|
||||
|
||||
/*!
|
||||
* \brief depth
|
||||
* \return The pixel depth.
|
||||
*/
|
||||
qint32 depth() const;
|
||||
|
||||
/*!
|
||||
* \brief elements
|
||||
* Elements needed to identify the content of every pixel.
|
||||
* Pixels will always be padded to byte boundaries.
|
||||
* \return The list of elements. An empty list on error.
|
||||
*/
|
||||
QList<Element> elements() const;
|
||||
|
||||
virtual bool isValid() const override;
|
||||
|
||||
CHUNKID_DEFINE(DPEL_CHUNK)
|
||||
|
||||
protected:
|
||||
virtual bool innerReadStructure(QIODevice *d) override;
|
||||
};
|
||||
|
||||
|
||||
/*!
|
||||
* \brief The DLOCChunk class
|
||||
*/
|
||||
class DLOCChunk : public IFFChunk
|
||||
{
|
||||
public:
|
||||
virtual ~DLOCChunk() override;
|
||||
DLOCChunk();
|
||||
DLOCChunk(const DLOCChunk& other) = default;
|
||||
DLOCChunk& operator =(const DLOCChunk& other) = default;
|
||||
|
||||
/*!
|
||||
* \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 xOffset
|
||||
* X offset of origin in source image.
|
||||
*/
|
||||
qint32 xOffset() const;
|
||||
|
||||
/*!
|
||||
* \brief yOffset
|
||||
* \returnX offset of origin in source image.
|
||||
*/
|
||||
qint32 yOffset() const;
|
||||
|
||||
virtual bool isValid() const override;
|
||||
|
||||
CHUNKID_DEFINE(DLOC_CHUNK)
|
||||
|
||||
protected:
|
||||
virtual bool innerReadStructure(QIODevice *d) override;
|
||||
};
|
||||
|
||||
|
||||
/*!
|
||||
* \brief The TVDCChunk class
|
||||
*/
|
||||
class TVDCChunk : public IFFChunk
|
||||
{
|
||||
public:
|
||||
virtual ~TVDCChunk() override;
|
||||
TVDCChunk();
|
||||
TVDCChunk(const TVDCChunk& other) = default;
|
||||
TVDCChunk& operator =(const TVDCChunk& other) = default;
|
||||
|
||||
qint32 count() const;
|
||||
|
||||
QList<quint16> table() const;
|
||||
|
||||
virtual bool isValid() const override;
|
||||
|
||||
CHUNKID_DEFINE(TVDC_CHUNK)
|
||||
|
||||
protected:
|
||||
virtual bool innerReadStructure(QIODevice *d) override;
|
||||
};
|
||||
|
||||
|
||||
/*!
|
||||
* \brief The DBODChunk class
|
||||
*/
|
||||
class DBODChunk : public IFFChunk
|
||||
{
|
||||
public:
|
||||
virtual ~DBODChunk() override;
|
||||
DBODChunk();
|
||||
DBODChunk(const DBODChunk& other) = default;
|
||||
DBODChunk& operator =(const DBODChunk& other) = default;
|
||||
|
||||
virtual bool isValid() const override;
|
||||
|
||||
CHUNKID_DEFINE(DBOD_CHUNK)
|
||||
|
||||
/*!
|
||||
* \brief readStride
|
||||
* \param d The device.
|
||||
* \param y The current scanline.
|
||||
* \param header The bitmap header.
|
||||
* \param pel The pixel elements info.
|
||||
* \param loc The display location (optional).
|
||||
* \return The scanline as requested for QImage.
|
||||
* \warning Call resetStrideRead() once before this one.
|
||||
*/
|
||||
QByteArray strideRead(QIODevice *d,
|
||||
qint32 y,
|
||||
const DGBLChunk *header,
|
||||
const DPELChunk *pel,
|
||||
const DLOCChunk *loc = nullptr,
|
||||
const TVDCChunk *tvdc = 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:
|
||||
/*!
|
||||
* \brief strideSize
|
||||
* \return The size of data to have to decode an image row.
|
||||
*/
|
||||
quint32 strideSize(const DGBLChunk *header, const DPELChunk *pel, const DLOCChunk *loc = nullptr) const;
|
||||
};
|
||||
|
||||
|
||||
/*!
|
||||
* *** UNDOCUMENTED CHUNKS ***
|
||||
*/
|
||||
|
||||
@@ -607,6 +607,69 @@ bool IFFHandler::readRGFXImage(QImage *image)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IFFHandler::readDEEPImage(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<DGBLChunk>(form);
|
||||
if (headers.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
// create the image
|
||||
auto &&header = headers.first();
|
||||
auto size = header->size();
|
||||
auto locs = IFFChunk::searchT<DLOCChunk>(form);
|
||||
if (!locs.isEmpty()) {
|
||||
size = locs.first()->size();
|
||||
}
|
||||
auto img = imageAlloc(size, form->format());
|
||||
if (img.isNull()) {
|
||||
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readDEEPImage(): error while allocating the image";
|
||||
return false;
|
||||
}
|
||||
|
||||
// decoding the image
|
||||
auto bodies = IFFChunk::searchT<DBODChunk>(form);
|
||||
if (bodies.isEmpty()) {
|
||||
img.fill(0);
|
||||
} else {
|
||||
auto &&body = bodies.first();
|
||||
if (!body->resetStrideRead(device())) {
|
||||
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readDEEPImage(): error while reading image data";
|
||||
return false;
|
||||
}
|
||||
auto pels = IFFChunk::searchT<DPELChunk>(form);
|
||||
if (pels.isEmpty()) {
|
||||
// DPEL is used to calculate the image format, so here should not be empty.
|
||||
return false;
|
||||
}
|
||||
auto tvdcs = IFFChunk::searchT<TVDCChunk>(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, pels.first(),
|
||||
locs.isEmpty() ? nullptr : locs.first(),
|
||||
tvdcs.isEmpty() ? nullptr : tvdcs.first());
|
||||
if (ba.isEmpty()) {
|
||||
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readDEEPImage(): 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())) {
|
||||
@@ -630,6 +693,10 @@ bool IFFHandler::read(QImage *image)
|
||||
return true;
|
||||
}
|
||||
|
||||
if (readDEEPImage(image)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::read(): no supported image found";
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -39,6 +39,8 @@ private:
|
||||
|
||||
bool readRGFXImage(QImage *image);
|
||||
|
||||
bool readDEEPImage(QImage *image);
|
||||
|
||||
private:
|
||||
const QScopedPointer<IFFHandlerPrivate> d;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user