mirror of
https://invent.kde.org/frameworks/kimageformats.git
synced 2026-04-12 04:42:44 -04:00
IFF: add support for RGBN/RGB8 image data and CAT chunk
Add the following features: - RGB8 image data support (test case added) - RGBN image data support ([Clouds.iff](/uploads/9db869350f74421bf1813fa7d4332f4f/Clouds.iff)) - CAT chunk support: you can have more than one image for file (test case added) - Image transformation support via EXIF data [RGBN/RGB8](https://wiki.amigaos.net/wiki/RGBN_and_RGB8_IFF_Image_Data) files are used in Impulse's Turbo Silver and Imagine. Closes #34
This commit is contained in:
committed by
Albert Astals Cid
parent
37e3c65a05
commit
480ea8dba0
27
README.md
27
README.md
@ -317,6 +317,33 @@ plugin:
|
|||||||
- `HDR_HALF_QUALITY`: on read, a 16-bit float image is returned instead of a
|
- `HDR_HALF_QUALITY`: on read, a 16-bit float image is returned instead of a
|
||||||
32-bit float one.
|
32-bit float one.
|
||||||
|
|
||||||
|
|
||||||
|
### The IFF plugin
|
||||||
|
|
||||||
|
Interchange File Format is a chunk-based format. Since the original 1985
|
||||||
|
version, various extensions have been created over time.
|
||||||
|
|
||||||
|
The plugin supports the following image data:
|
||||||
|
- FORM ILBM (Interleaved Bitmap): Electronic Arts’ IFF standard for
|
||||||
|
Interchange File Format (EA IFF 1985). ILBM is a format to handle raster
|
||||||
|
images, specifically an InterLeaved bitplane BitMap image with color map.
|
||||||
|
It supports from 1 to 8-bit indexed images with HAM, Halfbride, and normal
|
||||||
|
encoding. It also supports interleaved 24-bit RGB and 32-bit RGBA
|
||||||
|
extension without color map.
|
||||||
|
- FORM ILBM 64: ILBM extension to support 48-bit RGB and 64-bit RGBA encoding.
|
||||||
|
- FORM ACBM (Amiga Contiguous BitMap): It supports uncompressed ACBMs by
|
||||||
|
converting them to ILBMs at runtime.
|
||||||
|
- FORM RGBN / RGB8: It supports 13-bit and 25-bit RGB images with compression
|
||||||
|
type 4.
|
||||||
|
- FORM PBM: PBM is a chunky version of IFF pictures. It supports 8-bit images
|
||||||
|
with color map only.
|
||||||
|
- FOR4 CIMG (Maya Image File Format): It supports 24/48-bit RGB and 32/64-bit
|
||||||
|
RGBA images.
|
||||||
|
|
||||||
|
The plugin does not load images with non-standard SHAM/CTBL chunks due to the
|
||||||
|
lack of clear specifications.
|
||||||
|
|
||||||
|
|
||||||
### The JP2 plugin
|
### The JP2 plugin
|
||||||
|
|
||||||
**This plugin can be disabled by setting `KIMAGEFORMATS_JP2` to `OFF`
|
**This plugin can be disabled by setting `KIMAGEFORMATS_JP2` to `OFF`
|
||||||
|
|||||||
BIN
autotests/read/iff/cat_ilbm.iff
Normal file
BIN
autotests/read/iff/cat_ilbm.iff
Normal file
Binary file not shown.
5
autotests/read/iff/cat_ilbm.iff.json
Normal file
5
autotests/read/iff/cat_ilbm.iff.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"fileName" : "ps_testcard_rgb_maya.png"
|
||||||
|
}
|
||||||
|
]
|
||||||
BIN
autotests/read/iff/sv5_testcard_rgb_rgb8.iff
Normal file
BIN
autotests/read/iff/sv5_testcard_rgb_rgb8.iff
Normal file
Binary file not shown.
5
autotests/read/iff/sv5_testcard_rgb_rgb8.iff.json
Normal file
5
autotests/read/iff/sv5_testcard_rgb_rgb8.iff.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"fileName" : "ps_testcard_rgb_maya.png"
|
||||||
|
}
|
||||||
|
]
|
||||||
@ -25,7 +25,9 @@ static QString dataToString(const IFFChunk *chunk)
|
|||||||
if (chunk == nullptr || !chunk->isValid()) {
|
if (chunk == nullptr || !chunk->isValid()) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
return QString::fromUtf8(chunk->data()).replace(QStringLiteral("\0"), QStringLiteral(" ")).trimmed();
|
auto dt = chunk->data();
|
||||||
|
for (; dt.endsWith(char()); dt = dt.removeLast());
|
||||||
|
return QString::fromUtf8(dt).trimmed();
|
||||||
}
|
}
|
||||||
|
|
||||||
IFFChunk::~IFFChunk()
|
IFFChunk::~IFFChunk()
|
||||||
@ -268,6 +270,8 @@ IFFChunk::ChunkList IFFChunk::innerFromDevice(QIODevice *d, bool *ok, IFFChunk *
|
|||||||
chunk = QSharedPointer<IFFChunk>(new BODYChunk());
|
chunk = QSharedPointer<IFFChunk>(new BODYChunk());
|
||||||
} else if (cid == CAMG_CHUNK) {
|
} else if (cid == CAMG_CHUNK) {
|
||||||
chunk = QSharedPointer<IFFChunk>(new CAMGChunk());
|
chunk = QSharedPointer<IFFChunk>(new CAMGChunk());
|
||||||
|
} else if (cid == CAT__CHUNK) {
|
||||||
|
chunk = QSharedPointer<IFFChunk>(new CATChunk());
|
||||||
} else if (cid == CMAP_CHUNK) {
|
} else if (cid == CMAP_CHUNK) {
|
||||||
chunk = QSharedPointer<IFFChunk>(new CMAPChunk());
|
chunk = QSharedPointer<IFFChunk>(new CMAPChunk());
|
||||||
} else if (cid == CMYK_CHUNK) {
|
} else if (cid == CMYK_CHUNK) {
|
||||||
@ -509,7 +513,7 @@ QList<QRgb> CMAPChunk::palette(bool halfbride) const
|
|||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
auto tmp = p;
|
auto tmp = p;
|
||||||
for(auto &&v : tmp) {
|
for (auto &&v : tmp) {
|
||||||
p << qRgb(qRed(v) / 2, qGreen(v) / 2, qBlue(v) / 2);
|
p << qRgb(qRed(v) / 2, qGreen(v) / 2, qBlue(v) / 2);
|
||||||
}
|
}
|
||||||
return p;
|
return p;
|
||||||
@ -679,31 +683,150 @@ bool BODYChunk::isValid() const
|
|||||||
return chunkId() == BODYChunk::defaultChunkId();
|
return chunkId() == BODYChunk::defaultChunkId();
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray BODYChunk::strideRead(QIODevice *d, const BMHDChunk *header, const CAMGChunk *camg, const CMAPChunk *cmap, bool isPbm) const
|
// For each RGB value, a LONG-word (32 bits) is written:
|
||||||
|
// with the 24 RGB bits in the MSB positions; the "genlock"
|
||||||
|
// bit next, and then a 7 bit repeat count.
|
||||||
|
//
|
||||||
|
// See also: https://wiki.amigaos.net/wiki/RGBN_and_RGB8_IFF_Image_Data
|
||||||
|
inline qint64 rgb8Decompress(QIODevice *input, char *output, qint64 olen)
|
||||||
|
{
|
||||||
|
qint64 j = 0;
|
||||||
|
for (qint64 available = olen; j < olen; available = olen - j) {
|
||||||
|
auto pos = input->pos();
|
||||||
|
auto ba4 = input->read(4);
|
||||||
|
if (ba4.size() != 4) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
auto cnt = qint32(ba4.at(3) & 0x7F);
|
||||||
|
if (cnt * 3 > available) {
|
||||||
|
if (!input->seek(pos))
|
||||||
|
return -1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
for (qint32 i = 0; i < cnt; ++i) {
|
||||||
|
output[j++] = ba4.at(0);
|
||||||
|
output[j++] = ba4.at(1);
|
||||||
|
output[j++] = ba4.at(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return j;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For each RGB value, a WORD (16-bits) is written: with the
|
||||||
|
// 12 RGB bits in the MSB (most significant bit) positions;
|
||||||
|
// the "genlock" bit next; and then a 3 bit repeat count.
|
||||||
|
// If the repeat count is greater than 7, the 3-bit count is
|
||||||
|
// zero, and a BYTE repeat count follows. If the repeat count
|
||||||
|
// is greater than 255, the BYTE count is zero, and a WORD
|
||||||
|
// repeat count follows. Repeat counts greater than 65536 are
|
||||||
|
// not supported.
|
||||||
|
//
|
||||||
|
// See also: https://wiki.amigaos.net/wiki/RGBN_and_RGB8_IFF_Image_Data
|
||||||
|
inline qint32 rgbnCount(QIODevice *input, quint8 &R, quint8& G, quint8 &B)
|
||||||
|
{
|
||||||
|
auto ba2 = input->read(2);
|
||||||
|
if (ba2.size() != 2)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
R = ba2.at(0) & 0xF0;
|
||||||
|
R = R | (R >> 4);
|
||||||
|
|
||||||
|
G = ba2.at(0) & 0x0F;
|
||||||
|
G = G | (G << 4);
|
||||||
|
|
||||||
|
B = ba2.at(1) & 0xF0;
|
||||||
|
B = B | (B >> 4);
|
||||||
|
|
||||||
|
auto cnt = ba2.at(1) & 7;
|
||||||
|
if (cnt == 0) {
|
||||||
|
auto ba1 = input->read(1);
|
||||||
|
if (ba1.size() != 1)
|
||||||
|
return 0;
|
||||||
|
cnt = quint8(ba1.at(0));
|
||||||
|
}
|
||||||
|
if (cnt == 0) {
|
||||||
|
auto baw = input->read(2);
|
||||||
|
if (baw.size() != 2)
|
||||||
|
return 0;
|
||||||
|
cnt = qint32(quint8(baw.at(0))) << 8 | quint8(baw.at(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
return cnt;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline qint64 rgbNDecompress(QIODevice *input, char *output, qint64 olen)
|
||||||
|
{
|
||||||
|
qint64 j = 0;
|
||||||
|
for (qint64 available = olen; j < olen; available = olen - j) {
|
||||||
|
quint8 R = 0, G = 0, B = 0;
|
||||||
|
auto pos = input->pos();
|
||||||
|
auto cnt = rgbnCount(input, R, G, B);
|
||||||
|
if (cnt * 3 > available || cnt == 0) {
|
||||||
|
if (!input->seek(pos))
|
||||||
|
return -1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
for (qint32 i = 0; i < cnt; ++i) {
|
||||||
|
output[j++] = R;
|
||||||
|
output[j++] = G;
|
||||||
|
output[j++] = B;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return j;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray BODYChunk::strideRead(QIODevice *d, const BMHDChunk *header, const CAMGChunk *camg, const CMAPChunk *cmap, const QByteArray& formType) const
|
||||||
{
|
{
|
||||||
if (!isValid() || header == nullptr || d == nullptr) {
|
if (!isValid() || header == nullptr || d == nullptr) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
auto readSize = strideSize(header, isPbm);
|
auto isRgbN = formType == RGBN_FORM_TYPE;
|
||||||
for(;!d->atEnd() && _readBuffer.size() < readSize;) {
|
auto isRgb8 = formType == RGB8_FORM_TYPE;
|
||||||
QByteArray buf(readSize, char());
|
auto isPbm = formType == PBM__FORM_TYPE;
|
||||||
|
auto lineCompressed = isRgbN || isRgb8 ? false : true;
|
||||||
|
auto readSize = strideSize(header, formType);
|
||||||
|
auto bufSize = readSize;
|
||||||
|
if (isRgbN) {
|
||||||
|
bufSize = std::max(quint32(65536 * 3), readSize);
|
||||||
|
}
|
||||||
|
if (isRgb8) {
|
||||||
|
bufSize = std::max(quint32(127 * 3), readSize);
|
||||||
|
}
|
||||||
|
for (auto nextPos = nextChunkPos(); !d->atEnd() && d->pos() < nextPos && _readBuffer.size() < readSize;) {
|
||||||
|
QByteArray buf(bufSize, char());
|
||||||
qint64 rr = -1;
|
qint64 rr = -1;
|
||||||
if (header->compression() == BMHDChunk::Compression::Rle) {
|
if (header->compression() == BMHDChunk::Compression::Rle) {
|
||||||
// WARNING: The online spec says it's the same as TIFF but that's
|
// WARNING: The online spec says it's the same as TIFF but that's
|
||||||
// not accurate: the RLE -128 code is not a noop.
|
// not accurate: the RLE -128 code is not a noop.
|
||||||
rr = packbitsDecompress(d, buf.data(), buf.size(), true);
|
rr = packbitsDecompress(d, buf.data(), buf.size(), true);
|
||||||
|
} else if (header->compression() == BMHDChunk::Compression::RgbN8) {
|
||||||
|
if (isRgb8)
|
||||||
|
rr = rgb8Decompress(d, buf.data(), buf.size());
|
||||||
|
else if (isRgbN)
|
||||||
|
rr = rgbNDecompress(d, buf.data(), buf.size());
|
||||||
} else if (header->compression() == BMHDChunk::Compression::Uncompressed) {
|
} else if (header->compression() == BMHDChunk::Compression::Uncompressed) {
|
||||||
rr = d->read(buf.data(), buf.size()); // never seen
|
rr = d->read(buf.data(), buf.size()); // never seen
|
||||||
|
} else {
|
||||||
|
qCDebug(LOG_IFFPLUGIN) << "BODYChunk::strideRead: unknown compression" << header->compression();
|
||||||
}
|
}
|
||||||
if (rr != readSize)
|
if ((rr != readSize && lineCompressed) || (rr < 1))
|
||||||
return {};
|
return {};
|
||||||
_readBuffer.append(buf.data(), rr);
|
_readBuffer.append(buf.data(), rr);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto planes = _readBuffer.left(readSize);
|
auto planes = _readBuffer.left(readSize);
|
||||||
_readBuffer.remove(0, readSize);
|
_readBuffer.remove(0, readSize);
|
||||||
return deinterleave(planes, header, camg, cmap, isPbm);
|
if (isPbm) {
|
||||||
|
return pbm(planes, header, camg, cmap);
|
||||||
|
}
|
||||||
|
if (isRgb8) {
|
||||||
|
return rgb8(planes, header, camg, cmap);
|
||||||
|
}
|
||||||
|
if (isRgbN) {
|
||||||
|
return rgbN(planes, header, camg, cmap);
|
||||||
|
}
|
||||||
|
return deinterleave(planes, header, camg, cmap);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BODYChunk::resetStrideRead(QIODevice *d) const
|
bool BODYChunk::resetStrideRead(QIODevice *d) const
|
||||||
@ -731,20 +854,56 @@ CAMGChunk::ModeIds BODYChunk::safeModeId(const BMHDChunk *header, const CAMGChun
|
|||||||
return CAMGChunk::ModeIds();
|
return CAMGChunk::ModeIds();
|
||||||
}
|
}
|
||||||
|
|
||||||
quint32 BODYChunk::strideSize(const BMHDChunk *header, bool isPbm) const
|
quint32 BODYChunk::strideSize(const BMHDChunk *header, const QByteArray& formType) const
|
||||||
{
|
{
|
||||||
if (!isPbm) {
|
// RGB8 / RGBN
|
||||||
return header->rowLen() * header->bitplanes();
|
if (formType == RGB8_FORM_TYPE || formType == RGBN_FORM_TYPE) {
|
||||||
|
return header->width() * 3;
|
||||||
}
|
}
|
||||||
auto rs = header->width() * header->bitplanes() / 8;
|
|
||||||
if (rs & 1)
|
// PBM
|
||||||
++rs;
|
if (formType == PBM__FORM_TYPE) {
|
||||||
return rs;
|
auto rs = header->width() * header->bitplanes() / 8;
|
||||||
|
if (rs & 1)
|
||||||
|
++rs;
|
||||||
|
return rs;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ILBM
|
||||||
|
return header->rowLen() * header->bitplanes();
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray BODYChunk::deinterleave(const QByteArray &planes, const BMHDChunk *header, const CAMGChunk *camg, const CMAPChunk *cmap, bool isPbm) const
|
QByteArray BODYChunk::pbm(const QByteArray &planes, const BMHDChunk *header, const CAMGChunk *, const CMAPChunk *) const
|
||||||
{
|
{
|
||||||
if (planes.size() != strideSize(header, isPbm)) {
|
if (planes.size() != strideSize(header, PBM__FORM_TYPE)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
if (header->bitplanes() == 8) {
|
||||||
|
// The data are contiguous.
|
||||||
|
return planes;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray BODYChunk::rgb8(const QByteArray &planes, const BMHDChunk *header, const CAMGChunk *, const CMAPChunk *) const
|
||||||
|
{
|
||||||
|
if (planes.size() != strideSize(header, RGB8_FORM_TYPE)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return planes;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray BODYChunk::rgbN(const QByteArray &planes, const BMHDChunk *header, const CAMGChunk *, const CMAPChunk *) const
|
||||||
|
{
|
||||||
|
if (planes.size() != strideSize(header, RGBN_FORM_TYPE)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return planes;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray BODYChunk::deinterleave(const QByteArray &planes, const BMHDChunk *header, const CAMGChunk *camg, const CMAPChunk *cmap) const
|
||||||
|
{
|
||||||
|
if (planes.size() != strideSize(header, ILBM_FORM_TYPE)) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -762,10 +921,7 @@ QByteArray BODYChunk::deinterleave(const QByteArray &planes, const BMHDChunk *he
|
|||||||
case 6:
|
case 6:
|
||||||
case 7:
|
case 7:
|
||||||
case 8:
|
case 8:
|
||||||
if (isPbm && bitplanes == 8) {
|
if ((modeId & CAMGChunk::ModeId::Ham) && (cmap) && (bitplanes >= 5 && bitplanes <= 8)) {
|
||||||
// The data are contiguous.
|
|
||||||
ba = planes;
|
|
||||||
} else if ((modeId & CAMGChunk::ModeId::Ham) && (cmap) && (bitplanes >= 5 && bitplanes <= 8)) {
|
|
||||||
// From A Quick Introduction to IFF.txt:
|
// From A Quick Introduction to IFF.txt:
|
||||||
//
|
//
|
||||||
// Amiga HAM (Hold and Modify) mode lets the Amiga display all 4096 RGB values.
|
// Amiga HAM (Hold and Modify) mode lets the Amiga display all 4096 RGB values.
|
||||||
@ -895,11 +1051,6 @@ QByteArray BODYChunk::deinterleave(const QByteArray &planes, const BMHDChunk *he
|
|||||||
|
|
||||||
case 24: // rgb
|
case 24: // rgb
|
||||||
case 32: // rgba (SView5 extension)
|
case 32: // rgba (SView5 extension)
|
||||||
if (isPbm) {
|
|
||||||
// PBM cannot be a 24/32-bits image
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// From A Quick Introduction to IFF.txt:
|
// From A Quick Introduction to IFF.txt:
|
||||||
//
|
//
|
||||||
// If a deep ILBM (like 12 or 24 planes), there should be no CMAP
|
// If a deep ILBM (like 12 or 24 planes), there should be no CMAP
|
||||||
@ -935,11 +1086,6 @@ QByteArray BODYChunk::deinterleave(const QByteArray &planes, const BMHDChunk *he
|
|||||||
|
|
||||||
case 48: // rgb (SView5 extension)
|
case 48: // rgb (SView5 extension)
|
||||||
case 64: // rgba (SView5 extension)
|
case 64: // rgba (SView5 extension)
|
||||||
if (isPbm) {
|
|
||||||
// PBM cannot be a 48/64-bits image
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// From https://aminet.net/package/docs/misc/ILBM64:
|
// From https://aminet.net/package/docs/misc/ILBM64:
|
||||||
//
|
//
|
||||||
// Previously, the IFF-ILBM fileformat has been
|
// Previously, the IFF-ILBM fileformat has been
|
||||||
@ -1020,17 +1166,17 @@ bool ABITChunk::isValid() const
|
|||||||
return chunkId() == ABITChunk::defaultChunkId();
|
return chunkId() == ABITChunk::defaultChunkId();
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray ABITChunk::strideRead(QIODevice *d, const BMHDChunk *header, const CAMGChunk *camg, const CMAPChunk *cmap, bool isPbm) const
|
QByteArray ABITChunk::strideRead(QIODevice *d, const BMHDChunk *header, const CAMGChunk *camg, const CMAPChunk *cmap, const QByteArray& formType) const
|
||||||
{
|
{
|
||||||
if (!isValid() || header == nullptr || d == nullptr) {
|
if (!isValid() || header == nullptr || d == nullptr) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
if (header->compression() != BMHDChunk::Compression::Uncompressed || isPbm) {
|
if (header->compression() != BMHDChunk::Compression::Uncompressed || formType != ACBM_FORM_TYPE) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// convert ABIT data to an ILBM line on the fly
|
// convert ABIT data to an ILBM line on the fly
|
||||||
auto ilbmLine = QByteArray(strideSize(header, isPbm), char());
|
auto ilbmLine = QByteArray(strideSize(header, formType), char());
|
||||||
auto rowSize = header->rowLen();
|
auto rowSize = header->rowLen();
|
||||||
auto height = header->height();
|
auto height = header->height();
|
||||||
if (_y >= height) {
|
if (_y >= height) {
|
||||||
@ -1054,7 +1200,7 @@ QByteArray ABITChunk::strideRead(QIODevice *d, const BMHDChunk *header, const CA
|
|||||||
if (!buf.open(QBuffer::ReadOnly)) {
|
if (!buf.open(QBuffer::ReadOnly)) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
return BODYChunk::strideRead(&buf, header, camg, cmap, isPbm);
|
return BODYChunk::strideRead(&buf, header, camg, cmap, ILBM_FORM_TYPE);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ABITChunk::resetStrideRead(QIODevice *d) const
|
bool ABITChunk::resetStrideRead(QIODevice *d) const
|
||||||
@ -1063,6 +1209,33 @@ bool ABITChunk::resetStrideRead(QIODevice *d) const
|
|||||||
return BODYChunk::resetStrideRead(d);
|
return BODYChunk::resetStrideRead(d);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* **********************
|
||||||
|
* *** FORM Interface ***
|
||||||
|
* ********************** */
|
||||||
|
|
||||||
|
IFOR_Chunk::~IFOR_Chunk()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
IFOR_Chunk::IFOR_Chunk() : IFFChunk()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
QImageIOHandler::Transformation IFOR_Chunk::transformation() const
|
||||||
|
{
|
||||||
|
auto exifs = IFFChunk::searchT<EXIFChunk>(chunks());
|
||||||
|
if (!exifs.isEmpty()) {
|
||||||
|
auto exif = exifs.first()->value();
|
||||||
|
if (!exif.isEmpty())
|
||||||
|
return exif.transformation();
|
||||||
|
}
|
||||||
|
return QImageIOHandler::Transformation::TransformationNone;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ******************
|
/* ******************
|
||||||
* *** FORM Chunk ***
|
* *** FORM Chunk ***
|
||||||
* ****************** */
|
* ****************** */
|
||||||
@ -1072,7 +1245,7 @@ FORMChunk::~FORMChunk()
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FORMChunk::FORMChunk() : IFFChunk()
|
FORMChunk::FORMChunk() : IFOR_Chunk()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1093,12 +1266,18 @@ bool FORMChunk::innerReadStructure(QIODevice *d)
|
|||||||
}
|
}
|
||||||
_type = d->read(4);
|
_type = d->read(4);
|
||||||
auto ok = true;
|
auto ok = true;
|
||||||
|
|
||||||
|
// NOTE: add new supported type to CATChunk as well.
|
||||||
if (_type == ILBM_FORM_TYPE) {
|
if (_type == ILBM_FORM_TYPE) {
|
||||||
setChunks(IFFChunk::innerFromDevice(d, &ok, this));
|
setChunks(IFFChunk::innerFromDevice(d, &ok, this));
|
||||||
} else if (_type == PBM__FORM_TYPE) {
|
} else if (_type == PBM__FORM_TYPE) {
|
||||||
setChunks(IFFChunk::innerFromDevice(d, &ok, this));
|
setChunks(IFFChunk::innerFromDevice(d, &ok, this));
|
||||||
} else if (_type == ACBM_FORM_TYPE) {
|
} else if (_type == ACBM_FORM_TYPE) {
|
||||||
setChunks(IFFChunk::innerFromDevice(d, &ok, this));
|
setChunks(IFFChunk::innerFromDevice(d, &ok, this));
|
||||||
|
} else if (_type == RGB8_FORM_TYPE) {
|
||||||
|
setChunks(IFFChunk::innerFromDevice(d, &ok, this));
|
||||||
|
} else if (_type == RGBN_FORM_TYPE) {
|
||||||
|
setChunks(IFFChunk::innerFromDevice(d, &ok, this));
|
||||||
}
|
}
|
||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
@ -1124,7 +1303,10 @@ QImage::Format FORMChunk::format() const
|
|||||||
}
|
}
|
||||||
auto camgs = IFFChunk::searchT<CAMGChunk>(chunks());
|
auto camgs = IFFChunk::searchT<CAMGChunk>(chunks());
|
||||||
auto modeId = BODYChunk::safeModeId(h, camgs.isEmpty() ? nullptr : camgs.first(), cmaps.isEmpty() ? nullptr : cmaps.first());
|
auto modeId = BODYChunk::safeModeId(h, camgs.isEmpty() ? nullptr : camgs.first(), cmaps.isEmpty() ? nullptr : cmaps.first());
|
||||||
if (h->bitplanes() == 24) {
|
if (h->bitplanes() == 13) {
|
||||||
|
return QImage::Format_RGB888; // NOTE: with a little work you could use Format_RGB444
|
||||||
|
}
|
||||||
|
if (h->bitplanes() == 24 || h->bitplanes() == 25) {
|
||||||
return QImage::Format_RGB888;
|
return QImage::Format_RGB888;
|
||||||
}
|
}
|
||||||
if (h->bitplanes() == 48) {
|
if (h->bitplanes() == 48) {
|
||||||
@ -1154,6 +1336,7 @@ QImage::Format FORMChunk::format() const
|
|||||||
|
|
||||||
return QImage::Format_Grayscale8;
|
return QImage::Format_Grayscale8;
|
||||||
}
|
}
|
||||||
|
qCDebug(LOG_IFFPLUGIN) << "FORMChunk::format: Unsupported" << h->bitplanes() << "bitplanes";
|
||||||
}
|
}
|
||||||
|
|
||||||
return QImage::Format_Invalid;
|
return QImage::Format_Invalid;
|
||||||
@ -1177,7 +1360,7 @@ FOR4Chunk::~FOR4Chunk()
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FOR4Chunk::FOR4Chunk() : IFFChunk()
|
FOR4Chunk::FOR4Chunk() : IFOR_Chunk()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -1235,6 +1418,52 @@ QSize FOR4Chunk::size() const
|
|||||||
return headers.first()->size();
|
return headers.first()->size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ******************
|
||||||
|
* *** CAT Chunk ***
|
||||||
|
* ****************** */
|
||||||
|
|
||||||
|
CATChunk::~CATChunk()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
CATChunk::CATChunk() : IFFChunk()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CATChunk::isValid() const
|
||||||
|
{
|
||||||
|
return chunkId() == CATChunk::defaultChunkId();
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray CATChunk::catType() const
|
||||||
|
{
|
||||||
|
return _type;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CATChunk::innerReadStructure(QIODevice *d)
|
||||||
|
{
|
||||||
|
if (bytes() < 4) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_type = d->read(4);
|
||||||
|
auto ok = true;
|
||||||
|
|
||||||
|
// supports the image formats of FORMChunk.
|
||||||
|
if (_type == ILBM_FORM_TYPE) {
|
||||||
|
setChunks(IFFChunk::innerFromDevice(d, &ok, this));
|
||||||
|
} else if (_type == PBM__FORM_TYPE) {
|
||||||
|
setChunks(IFFChunk::innerFromDevice(d, &ok, this));
|
||||||
|
} else if (_type == ACBM_FORM_TYPE) {
|
||||||
|
setChunks(IFFChunk::innerFromDevice(d, &ok, this));
|
||||||
|
} else if (_type == RGB8_FORM_TYPE) {
|
||||||
|
setChunks(IFFChunk::innerFromDevice(d, &ok, this));
|
||||||
|
} else if (_type == RGBN_FORM_TYPE) {
|
||||||
|
setChunks(IFFChunk::innerFromDevice(d, &ok, this));
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
/* ******************
|
/* ******************
|
||||||
* *** TBHD Chunk ***
|
* *** TBHD Chunk ***
|
||||||
@ -1477,7 +1706,7 @@ QByteArray RGBAChunk::readStride(QIODevice *d, const TBHDChunk *header) const
|
|||||||
}
|
}
|
||||||
|
|
||||||
// compressed
|
// compressed
|
||||||
for(;!d->atEnd() && _readBuffer.size() < readSize;) {
|
for (auto nextPos = nextChunkPos(); !d->atEnd() && d->pos() < nextPos && _readBuffer.size() < readSize;) {
|
||||||
QByteArray buf(readSize * size().height(), char());
|
QByteArray buf(readSize * size().height(), char());
|
||||||
qint64 rr = -1;
|
qint64 rr = -1;
|
||||||
if (header->compression() == TBHDChunk::Compression::Rle) {
|
if (header->compression() == TBHDChunk::Compression::Rle) {
|
||||||
|
|||||||
@ -79,6 +79,9 @@ Q_DECLARE_LOGGING_CATEGORY(LOG_IFFPLUGIN)
|
|||||||
#define ACBM_FORM_TYPE QByteArray("ACBM")
|
#define ACBM_FORM_TYPE QByteArray("ACBM")
|
||||||
#define ILBM_FORM_TYPE QByteArray("ILBM")
|
#define ILBM_FORM_TYPE QByteArray("ILBM")
|
||||||
#define PBM__FORM_TYPE QByteArray("PBM ")
|
#define PBM__FORM_TYPE QByteArray("PBM ")
|
||||||
|
#define RGB8_FORM_TYPE QByteArray("RGB8")
|
||||||
|
#define RGBN_FORM_TYPE QByteArray("RGBN")
|
||||||
|
|
||||||
#define CIMG_FOR4_TYPE QByteArray("CIMG")
|
#define CIMG_FOR4_TYPE QByteArray("CIMG")
|
||||||
#define TBMP_FOR4_TYPE QByteArray("TBMP")
|
#define TBMP_FOR4_TYPE QByteArray("TBMP")
|
||||||
|
|
||||||
@ -345,7 +348,8 @@ class BMHDChunk: public IFFChunk
|
|||||||
public:
|
public:
|
||||||
enum Compression {
|
enum Compression {
|
||||||
Uncompressed = 0, /**< Image data are uncompressed. */
|
Uncompressed = 0, /**< Image data are uncompressed. */
|
||||||
Rle = 1 /**< Image data are RLE compressed. */
|
Rle = 1, /**< Image data are RLE compressed. */
|
||||||
|
RgbN8 = 4 /**< RGB8/RGBN compresson. */
|
||||||
};
|
};
|
||||||
enum Masking {
|
enum Masking {
|
||||||
None = 0, /**< Designates an opaque rectangular image. */
|
None = 0, /**< Designates an opaque rectangular image. */
|
||||||
@ -626,11 +630,11 @@ public:
|
|||||||
* \param header The bitmap header.
|
* \param header The bitmap header.
|
||||||
* \param camg The CAMG chunk (optional)
|
* \param camg The CAMG chunk (optional)
|
||||||
* \param cmap The CMAP chunk (optional)
|
* \param cmap The CMAP chunk (optional)
|
||||||
* \param isPbm Set to true if the formType() == "PBM "
|
* \param formType The type of the current form chunk.
|
||||||
* \return The scanline as requested for QImage.
|
* \return The scanline as requested for QImage.
|
||||||
* \warning Call resetStrideRead() once before this one.
|
* \warning Call resetStrideRead() once before this one.
|
||||||
*/
|
*/
|
||||||
virtual QByteArray strideRead(QIODevice *d, const BMHDChunk *header, const CAMGChunk *camg = nullptr, const CMAPChunk *cmap = nullptr, bool isPbm = false) const;
|
virtual QByteArray strideRead(QIODevice *d, const BMHDChunk *header, const CAMGChunk *camg = nullptr, const CMAPChunk *cmap = nullptr, const QByteArray& formType = ILBM_FORM_TYPE) const;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief resetStrideRead
|
* \brief resetStrideRead
|
||||||
@ -655,12 +659,18 @@ public:
|
|||||||
protected:
|
protected:
|
||||||
/*!
|
/*!
|
||||||
* \brief strideSize
|
* \brief strideSize
|
||||||
* \param isPbm Set true if the image is PBM.
|
* \param formType The type of the current form chunk.
|
||||||
* \return The size of data to have to decode an image row.
|
* \return The size of data to have to decode an image row.
|
||||||
*/
|
*/
|
||||||
quint32 strideSize(const BMHDChunk *header, bool isPbm) const;
|
quint32 strideSize(const BMHDChunk *header, const QByteArray& formType) const;
|
||||||
|
|
||||||
QByteArray deinterleave(const QByteArray &planes, const BMHDChunk *header, const CAMGChunk *camg = nullptr, const CMAPChunk *cmap = nullptr, bool isPbm = false) const;
|
QByteArray deinterleave(const QByteArray &planes, const BMHDChunk *header, const CAMGChunk *camg = nullptr, const CMAPChunk *cmap = nullptr) const;
|
||||||
|
|
||||||
|
QByteArray pbm(const QByteArray &planes, const BMHDChunk *header, const CAMGChunk *camg = nullptr, const CMAPChunk *cmap = nullptr) const;
|
||||||
|
|
||||||
|
QByteArray rgb8(const QByteArray &planes, const BMHDChunk *header, const CAMGChunk *camg = nullptr, const CMAPChunk *cmap = nullptr) const;
|
||||||
|
|
||||||
|
QByteArray rgbN(const QByteArray &planes, const BMHDChunk *header, const CAMGChunk *camg = nullptr, const CMAPChunk *cmap = nullptr) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
mutable QByteArray _readBuffer;
|
mutable QByteArray _readBuffer;
|
||||||
@ -682,7 +692,7 @@ public:
|
|||||||
|
|
||||||
CHUNKID_DEFINE(ABIT_CHUNK)
|
CHUNKID_DEFINE(ABIT_CHUNK)
|
||||||
|
|
||||||
virtual QByteArray strideRead(QIODevice *d, const BMHDChunk *header, const CAMGChunk *camg = nullptr, const CMAPChunk *cmap = nullptr, bool isPbm = false) const override;
|
virtual QByteArray strideRead(QIODevice *d, const BMHDChunk *header, const CAMGChunk *camg = nullptr, const CMAPChunk *cmap = nullptr, const QByteArray& formType = ACBM_FORM_TYPE) const override;
|
||||||
|
|
||||||
virtual bool resetStrideRead(QIODevice *d) const override;
|
virtual bool resetStrideRead(QIODevice *d) const override;
|
||||||
|
|
||||||
@ -690,11 +700,52 @@ private:
|
|||||||
mutable qint32 _y;
|
mutable qint32 _y;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief The IFOR_Chunk class
|
||||||
|
* Interface for FORM chunks.
|
||||||
|
*/
|
||||||
|
class IFOR_Chunk : public IFFChunk
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~IFOR_Chunk() override;
|
||||||
|
IFOR_Chunk();
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief isSupported
|
||||||
|
* \return True if the form is supported by the plugin.
|
||||||
|
*/
|
||||||
|
virtual bool isSupported() const = 0;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief formType
|
||||||
|
* \return The type of image data contained in the form.
|
||||||
|
*/
|
||||||
|
virtual QByteArray formType() const = 0;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief format
|
||||||
|
* \return The Qt image format the form is converted to.
|
||||||
|
*/
|
||||||
|
virtual QImage::Format format() const = 0;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief transformation
|
||||||
|
* \return The image transformation.
|
||||||
|
* \note The Default implentation returns the trasformation of EXIF chunk (if any).
|
||||||
|
*/
|
||||||
|
virtual QImageIOHandler::Transformation transformation() const;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief size
|
||||||
|
* \return The image size in pixels.
|
||||||
|
*/
|
||||||
|
virtual QSize size() const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief The FORMChunk class
|
* \brief The FORMChunk class
|
||||||
*/
|
*/
|
||||||
class FORMChunk : public IFFChunk
|
class FORMChunk : public IFOR_Chunk
|
||||||
{
|
{
|
||||||
QByteArray _type;
|
QByteArray _type;
|
||||||
|
|
||||||
@ -706,13 +757,13 @@ public:
|
|||||||
|
|
||||||
virtual bool isValid() const override;
|
virtual bool isValid() const override;
|
||||||
|
|
||||||
bool isSupported() const;
|
virtual bool isSupported() const override;
|
||||||
|
|
||||||
QByteArray formType() const;
|
virtual QByteArray formType() const override;
|
||||||
|
|
||||||
QImage::Format format() const;
|
virtual QImage::Format format() const override;
|
||||||
|
|
||||||
QSize size() const;
|
virtual QSize size() const override;
|
||||||
|
|
||||||
CHUNKID_DEFINE(FORM_CHUNK)
|
CHUNKID_DEFINE(FORM_CHUNK)
|
||||||
|
|
||||||
@ -724,7 +775,7 @@ protected:
|
|||||||
/*!
|
/*!
|
||||||
* \brief The FOR4Chunk class
|
* \brief The FOR4Chunk class
|
||||||
*/
|
*/
|
||||||
class FOR4Chunk : public IFFChunk
|
class FOR4Chunk : public IFOR_Chunk
|
||||||
{
|
{
|
||||||
QByteArray _type;
|
QByteArray _type;
|
||||||
|
|
||||||
@ -738,13 +789,13 @@ public:
|
|||||||
|
|
||||||
virtual qint32 alignBytes() const override;
|
virtual qint32 alignBytes() const override;
|
||||||
|
|
||||||
bool isSupported() const;
|
virtual bool isSupported() const override;
|
||||||
|
|
||||||
QByteArray formType() const;
|
virtual QByteArray formType() const override;
|
||||||
|
|
||||||
QImage::Format format() const;
|
virtual QImage::Format format() const override;
|
||||||
|
|
||||||
QSize size() const;
|
virtual QSize size() const override;
|
||||||
|
|
||||||
CHUNKID_DEFINE(FOR4_CHUNK)
|
CHUNKID_DEFINE(FOR4_CHUNK)
|
||||||
|
|
||||||
@ -752,6 +803,29 @@ protected:
|
|||||||
virtual bool innerReadStructure(QIODevice *d) override;
|
virtual bool innerReadStructure(QIODevice *d) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief The CATChunk class
|
||||||
|
*/
|
||||||
|
class CATChunk : public IFFChunk
|
||||||
|
{
|
||||||
|
QByteArray _type;
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual ~CATChunk() override;
|
||||||
|
CATChunk();
|
||||||
|
CATChunk(const CATChunk& other) = default;
|
||||||
|
CATChunk& operator =(const CATChunk& other) = default;
|
||||||
|
|
||||||
|
virtual bool isValid() const override;
|
||||||
|
|
||||||
|
QByteArray catType() const;
|
||||||
|
|
||||||
|
CHUNKID_DEFINE(CAT__CHUNK)
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual bool innerReadStructure(QIODevice *d) override;
|
||||||
|
};
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief The TBHDChunk class
|
* \brief The TBHDChunk class
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -16,28 +16,38 @@
|
|||||||
class IFFHandlerPrivate
|
class IFFHandlerPrivate
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
IFFHandlerPrivate() {}
|
IFFHandlerPrivate()
|
||||||
~IFFHandlerPrivate() {}
|
: m_imageNumber(0)
|
||||||
|
, m_imageCount(0)
|
||||||
|
{
|
||||||
|
|
||||||
bool readStructure(QIODevice *d) {
|
}
|
||||||
|
~IFFHandlerPrivate()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool readStructure(QIODevice *d)
|
||||||
|
{
|
||||||
if (d == nullptr) {
|
if (d == nullptr) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_chunks.isEmpty()) {
|
if (!m_chunks.isEmpty()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto ok = false;
|
auto ok = false;
|
||||||
auto chunks = IFFChunk::fromDevice(d, &ok);
|
auto chunks = IFFChunk::fromDevice(d, &ok);
|
||||||
if (ok) {
|
if (ok) {
|
||||||
_chunks = chunks;
|
m_chunks = chunks;
|
||||||
}
|
}
|
||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
static QList<const T*> searchForms(const IFFChunk::ChunkList &chunks, bool supportedOnly = true) {
|
static QList<const T*> searchForms(const IFFChunk::ChunkList &chunks, bool supportedOnly = true)
|
||||||
|
{
|
||||||
QList<const T*> list;
|
QList<const T*> list;
|
||||||
auto cid = T::defaultChunkId();
|
auto cid = T::defaultChunkId();
|
||||||
auto forms = IFFChunk::search(cid, chunks);
|
auto forms = IFFChunk::search(cid, chunks);
|
||||||
@ -50,11 +60,25 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
QList<const T*> searchForms(bool supportedOnly = true) {
|
QList<const T*> searchForms(bool supportedOnly = true)
|
||||||
return searchForms<T>(_chunks, supportedOnly);
|
{
|
||||||
|
return searchForms<T>(m_chunks, supportedOnly);
|
||||||
}
|
}
|
||||||
|
|
||||||
IFFChunk::ChunkList _chunks;
|
IFFChunk::ChunkList m_chunks;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief m_imageNumber
|
||||||
|
* Value set by QImageReader::jumpToImage() or QImageReader::jumpToNextImage().
|
||||||
|
* The number of view selected in a multiview image.
|
||||||
|
*/
|
||||||
|
qint32 m_imageNumber;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief m_imageCount
|
||||||
|
* The total number of views (cache value)
|
||||||
|
*/
|
||||||
|
mutable qint32 m_imageCount;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -62,6 +86,7 @@ IFFHandler::IFFHandler()
|
|||||||
: QImageIOHandler()
|
: QImageIOHandler()
|
||||||
, d(new IFFHandlerPrivate)
|
, d(new IFFHandlerPrivate)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IFFHandler::canRead() const
|
bool IFFHandler::canRead() const
|
||||||
@ -204,7 +229,8 @@ bool IFFHandler::readStandardImage(QImage *image)
|
|||||||
if (forms.isEmpty()) {
|
if (forms.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
auto &&form = forms.first();
|
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)
|
// show the first one (I don't have a sample with many images)
|
||||||
auto headers = IFFChunk::searchT<BMHDChunk>(form);
|
auto headers = IFFChunk::searchT<BMHDChunk>(form);
|
||||||
@ -260,10 +286,9 @@ bool IFFHandler::readStandardImage(QImage *image)
|
|||||||
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readStandardImage() error while reading image data";
|
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readStandardImage() error while reading image data";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
auto isPbm = form->formType() == PBM__FORM_TYPE;
|
|
||||||
for (auto y = 0, h = img.height(); y < h; ++y) {
|
for (auto y = 0, h = img.height(); y < h; ++y) {
|
||||||
auto line = reinterpret_cast<char*>(img.scanLine(y));
|
auto line = reinterpret_cast<char*>(img.scanLine(y));
|
||||||
auto ba = body->strideRead(device(), header, camg, cmap, isPbm);
|
auto ba = body->strideRead(device(), header, camg, cmap, form->formType());
|
||||||
if (ba.isEmpty()) {
|
if (ba.isEmpty()) {
|
||||||
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readStandardImage() error while reading image scanline";
|
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readStandardImage() error while reading image scanline";
|
||||||
return false;
|
return false;
|
||||||
@ -285,7 +310,8 @@ bool IFFHandler::readMayaImage(QImage *image)
|
|||||||
if (forms.isEmpty()) {
|
if (forms.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
auto &&form = forms.first();
|
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)
|
// show the first one (I don't have a sample with many images)
|
||||||
auto headers = IFFChunk::searchT<TBHDChunk>(form);
|
auto headers = IFFChunk::searchT<TBHDChunk>(form);
|
||||||
@ -366,42 +392,88 @@ bool IFFHandler::supportsOption(ImageOption option) const
|
|||||||
if (option == QImageIOHandler::ImageFormat) {
|
if (option == QImageIOHandler::ImageFormat) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (option == QImageIOHandler::ImageTransformation) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant IFFHandler::option(ImageOption option) const
|
QVariant IFFHandler::option(ImageOption option) const
|
||||||
{
|
{
|
||||||
QVariant v;
|
if (!supportsOption(option)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const IFOR_Chunk *form = nullptr;
|
||||||
|
if (d->readStructure(device())) {
|
||||||
|
auto forms = d->searchForms<FORMChunk>();
|
||||||
|
auto for4s = d->searchForms<FOR4Chunk>();
|
||||||
|
auto cin = currentImageNumber();
|
||||||
|
if (!forms.isEmpty())
|
||||||
|
form = cin < forms.size() ? forms.at(cin) : forms.first();
|
||||||
|
else if (!for4s.isEmpty())
|
||||||
|
form = cin < for4s.size() ? for4s.at(cin) : for4s.first();
|
||||||
|
}
|
||||||
|
if (form == nullptr) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
if (option == QImageIOHandler::Size) {
|
if (option == QImageIOHandler::Size) {
|
||||||
if (d->readStructure(device())) {
|
return QVariant::fromValue(form->size());
|
||||||
auto forms = d->searchForms<FORMChunk>();
|
|
||||||
if (!forms.isEmpty())
|
|
||||||
if (auto &&form = forms.first())
|
|
||||||
v = QVariant::fromValue(form->size());
|
|
||||||
|
|
||||||
auto for4s = d->searchForms<FOR4Chunk>();
|
|
||||||
if (!for4s.isEmpty())
|
|
||||||
if (auto &&form = for4s.first())
|
|
||||||
v = QVariant::fromValue(form->size());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (option == QImageIOHandler::ImageFormat) {
|
if (option == QImageIOHandler::ImageFormat) {
|
||||||
if (d->readStructure(device())) {
|
return QVariant::fromValue(form->format());
|
||||||
auto forms = d->searchForms<FORMChunk>();
|
|
||||||
if (!forms.isEmpty())
|
|
||||||
if (auto &&form = forms.first())
|
|
||||||
v = QVariant::fromValue(form->format());
|
|
||||||
|
|
||||||
auto for4s = d->searchForms<FOR4Chunk>();
|
|
||||||
if (!for4s.isEmpty())
|
|
||||||
if (auto &&form = for4s.first())
|
|
||||||
v = QVariant::fromValue(form->format());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return v;
|
if (option == QImageIOHandler::ImageTransformation) {
|
||||||
|
return QVariant::fromValue(form->transformation());
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IFFHandler::jumpToNextImage()
|
||||||
|
{
|
||||||
|
return jumpToImage(d->m_imageNumber + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IFFHandler::jumpToImage(int imageNumber)
|
||||||
|
{
|
||||||
|
if (imageNumber < 0 || imageNumber >= imageCount()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
d->m_imageNumber = imageNumber;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int IFFHandler::imageCount() const
|
||||||
|
{
|
||||||
|
// NOTE: image count is cached for performance reason
|
||||||
|
auto &&count = d->m_imageCount;
|
||||||
|
if (count > 0) {
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
count = QImageIOHandler::imageCount();
|
||||||
|
if (!d->readStructure(device())) {
|
||||||
|
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::imageCount() invalid IFF structure";
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto forms = d->searchForms<FORMChunk>();
|
||||||
|
auto for4s = d->searchForms<FOR4Chunk>();
|
||||||
|
if (!forms.isEmpty())
|
||||||
|
count = forms.size();
|
||||||
|
else if (!for4s.isEmpty())
|
||||||
|
count = for4s.size();
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
int IFFHandler::currentImageNumber() const
|
||||||
|
{
|
||||||
|
return d->m_imageNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
QImageIOPlugin::Capabilities IFFPlugin::capabilities(QIODevice *device, const QByteArray &format) const
|
QImageIOPlugin::Capabilities IFFPlugin::capabilities(QIODevice *device, const QByteArray &format) const
|
||||||
|
|||||||
@ -23,6 +23,11 @@ public:
|
|||||||
bool supportsOption(QImageIOHandler::ImageOption option) const override;
|
bool supportsOption(QImageIOHandler::ImageOption option) const override;
|
||||||
QVariant option(QImageIOHandler::ImageOption option) const override;
|
QVariant option(QImageIOHandler::ImageOption option) const override;
|
||||||
|
|
||||||
|
bool jumpToNextImage() override;
|
||||||
|
bool jumpToImage(int imageNumber) override;
|
||||||
|
int imageCount() const override;
|
||||||
|
int currentImageNumber() const override;
|
||||||
|
|
||||||
static bool canRead(QIODevice *device);
|
static bool canRead(QIODevice *device);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
Reference in New Issue
Block a user