33 quint32_le bfAttributes;
39 quint16_le wNumImages;
42struct CursorDirEntry {
49 quint32_le dwBytesInImage;
50 quint32_le dwImageOffset;
55ANIHandler::ANIHandler() =
default;
57bool ANIHandler::canRead()
const
59 if (canRead(device())) {
65 const QByteArray nextFrame = device()->peek(
sizeof(ChunkHeader));
66 if (nextFrame.
size() ==
sizeof(ChunkHeader)) {
67 const auto *header =
reinterpret_cast<const ChunkHeader *
>(nextFrame.
data());
68 if (qstrncmp(header->magic,
"icon",
sizeof(header->magic)) == 0 && header->size > 0) {
77bool ANIHandler::read(
QImage *outImage)
79 if (!ensureScanned()) {
83 if (device()->pos() < m_firstFrameOffset) {
84 device()->seek(m_firstFrameOffset);
87 const QByteArray frameType = device()->read(4);
88 if (frameType !=
"icon") {
92 const QByteArray frameSizeData = device()->read(
sizeof(quint32_le));
93 if (frameSizeData.
size() !=
sizeof(quint32_le)) {
97 const auto frameSize = *(
reinterpret_cast<const quint32_le *
>(frameSizeData.
data()));
102 const QByteArray frameData = device()->read(frameSize);
106 ++m_currentImageNumber;
109 if (!m_imageSequence.isEmpty()) {
110 if (m_currentImageNumber < m_imageSequence.count()) {
111 const int nextFrame = m_imageSequence.
at(m_currentImageNumber);
112 if (nextFrame < 0 || nextFrame >= m_frameOffsets.count()) {
115 const auto nextOffset = m_frameOffsets.at(nextFrame);
116 device()->seek(nextOffset);
117 }
else if (m_currentImageNumber == m_imageSequence.count()) {
118 const auto endOffset = m_frameOffsets.last();
119 if (device()->pos() != endOffset) {
120 device()->seek(endOffset);
128int ANIHandler::currentImageNumber()
const
130 if (!ensureScanned()) {
133 return m_currentImageNumber;
136int ANIHandler::imageCount()
const
138 if (!ensureScanned()) {
144bool ANIHandler::jumpToImage(
int imageNumber)
146 if (!ensureScanned()) {
150 if (imageNumber < 0) {
154 if (imageNumber == m_currentImageNumber) {
159 if (!m_imageSequence.isEmpty()) {
160 if (imageNumber >= m_imageSequence.count()) {
164 const int targetFrame = m_imageSequence.at(imageNumber);
166 const auto targetOffset = m_frameOffsets.value(targetFrame, -1);
168 if (device()->seek(targetOffset)) {
169 m_currentImageNumber = imageNumber;
176 if (imageNumber >= m_frameCount) {
181 const auto oldPos = device()->pos();
183 if (imageNumber < m_currentImageNumber) {
185 if (!device()->seek(m_firstFrameOffset)) {
190 while (m_currentImageNumber < imageNumber) {
191 if (!jumpToNextImage()) {
192 device()->seek(oldPos);
197 m_currentImageNumber = imageNumber;
201bool ANIHandler::jumpToNextImage()
203 if (!ensureScanned()) {
209 if (!m_imageSequence.isEmpty()) {
210 return jumpToImage(m_currentImageNumber + 1);
213 if (device()->pos() < m_firstFrameOffset) {
214 if (!device()->seek(m_firstFrameOffset)) {
219 const QByteArray nextFrame = device()->peek(
sizeof(ChunkHeader));
220 if (nextFrame.
size() !=
sizeof(ChunkHeader)) {
224 const auto *header =
reinterpret_cast<const ChunkHeader *
>(nextFrame.
data());
225 if (qstrncmp(header->magic,
"icon",
sizeof(header->magic)) != 0) {
229 const qint64 seekBy =
sizeof(ChunkHeader) + header->size;
231 if (!device()->seek(device()->pos() + seekBy)) {
235 ++m_currentImageNumber;
239int ANIHandler::loopCount()
const
241 if (!ensureScanned()) {
247int ANIHandler::nextImageDelay()
const
249 if (!ensureScanned()) {
253 int rate = m_displayRate;
255 if (!m_displayRates.isEmpty()) {
256 int previousImage = m_currentImageNumber - 1;
257 if (previousImage < 0) {
258 previousImage = m_displayRates.count() - 1;
260 rate = m_displayRates.at(previousImage);
263 return rate * 1000 / 60;
266bool ANIHandler::supportsOption(ImageOption option)
const
271QVariant ANIHandler::option(ImageOption option)
const
273 if (!supportsOption(option) || !ensureScanned()) {
288 if (!m_name.isEmpty()) {
289 description += QStringLiteral(
"Title: %1\n\n").
arg(m_name);
291 if (!m_artist.isEmpty()) {
292 description += QStringLiteral(
"Author: %1\n\n").
arg(m_artist);
306bool ANIHandler::ensureScanned()
const
312 if (device()->isSequential()) {
316 auto *mutableThis =
const_cast<ANIHandler *
>(
this);
318 const auto oldPos = device()->pos();
319 auto cleanup = qScopeGuard([
this, oldPos] {
320 device()->seek(oldPos);
325 const QByteArray riffIntro = device()->read(4);
326 if (riffIntro !=
"RIFF") {
330 const auto riffSizeData = device()->read(
sizeof(quint32_le));
331 if (riffSizeData.size() !=
sizeof(quint32_le)) {
334 const auto riffSize = *(
reinterpret_cast<const quint32_le *
>(riffSizeData.data()));
340 mutableThis->m_displayRates.
clear();
341 mutableThis->m_imageSequence.clear();
343 while (device()->pos() < riffSize) {
345 if (chunkId.
length() != 4) {
349 if (chunkId ==
"ACON") {
353 const QByteArray chunkSizeData = device()->read(
sizeof(quint32_le));
354 if (chunkSizeData.
length() !=
sizeof(quint32_le)) {
357 auto chunkSize = *(
reinterpret_cast<const quint32_le *
>(chunkSizeData.
data()));
359 if (chunkId ==
"anih") {
360 if (chunkSize !=
sizeof(AniHeader)) {
361 qWarning() <<
"anih chunk size does not match ANIHEADER size";
365 const QByteArray anihData = device()->read(
sizeof(AniHeader));
366 if (anihData.
size() !=
sizeof(AniHeader)) {
370 auto *aniHeader =
reinterpret_cast<const AniHeader *
>(anihData.
data());
374 mutableThis->m_size =
QSize(aniHeader->iWidth, aniHeader->iHeight);
375 mutableThis->m_frameCount = aniHeader->nFrames;
376 mutableThis->m_imageCount = aniHeader->nSteps;
377 mutableThis->m_displayRate = aniHeader->iDispRate;
378 }
else if (chunkId ==
"rate" || chunkId ==
"seq ") {
379 const QByteArray data = device()->read(chunkSize);
380 if (
static_cast<quint32_le
>(data.
size()) != chunkSize || data.
size() %
sizeof(quint32_le) != 0) {
385 auto *dataPtr = data.
data();
387 for (
int i = 0; i < data.
size(); i +=
sizeof(quint32_le)) {
388 const auto entry = *(
reinterpret_cast<const quint32_le *
>(dataPtr + i));
392 if (chunkId ==
"rate") {
394 mutableThis->m_displayRates =
list;
395 }
else if (chunkId ==
"seq ") {
397 bool isAscending =
true;
406 mutableThis->m_imageSequence =
list;
411 }
else if (chunkId ==
"INAM" || chunkId ==
"IART") {
412 const QByteArray value = device()->read(chunkSize);
414 if (
static_cast<quint32_le
>(value.
size()) != chunkSize) {
419 if (chunkSize % 2 != 0) {
425 if (chunkId ==
"INAM") {
426 mutableThis->m_name = stringValue;
427 }
else if (chunkId ==
"IART") {
428 mutableThis->m_artist = stringValue;
430 }
else if (chunkId ==
"LIST") {
431 const QByteArray listType = device()->read(4);
433 if (listType ==
"INFO") {
435 }
else if (listType ==
"fram") {
437 while (read < chunkSize) {
438 const QByteArray chunkType = device()->read(4);
440 if (chunkType !=
"icon") {
444 if (!m_firstFrameOffset) {
445 mutableThis->m_firstFrameOffset = device()->pos() - 4;
446 mutableThis->m_currentImageNumber = 0;
449 if (!m_size.isValid() || m_size.isEmpty()) {
450 const auto oldPos = device()->pos();
452 device()->read(
sizeof(quint32_le));
454 const QByteArray curHeaderData = device()->read(
sizeof(CurHeader));
455 const QByteArray cursorDirEntryData = device()->read(
sizeof(CursorDirEntry));
457 if (curHeaderData.
length() ==
sizeof(CurHeader) && cursorDirEntryData.
length() ==
sizeof(CursorDirEntry)) {
458 auto *cursorDirEntry =
reinterpret_cast<const CursorDirEntry *
>(cursorDirEntryData.
data());
459 mutableThis->m_size =
QSize(cursorDirEntry->bWidth, cursorDirEntry->bHeight);
462 device()->seek(oldPos);
466 if (m_imageSequence.isEmpty()) {
471 mutableThis->m_frameOffsets.append(device()->pos() - 4);
473 const QByteArray frameSizeData = device()->read(
sizeof(quint32_le));
474 if (frameSizeData.
size() !=
sizeof(quint32_le)) {
478 const auto frameSize = *(
reinterpret_cast<const quint32_le *
>(frameSizeData.
data()));
479 device()->seek(device()->pos() + frameSize);
483 if (m_frameOffsets.count() == m_frameCount) {
485 mutableThis->m_frameOffsets.append(device()->pos() - 4);
494 if (m_imageCount != m_frameCount && m_imageSequence.isEmpty()) {
495 qWarning(
"ANIHandler: 'nSteps' is not equal to 'nFrames' but no 'seq' entries were provided");
499 if (!m_imageSequence.isEmpty() && m_imageSequence.count() != m_imageCount) {
500 qWarning(
"ANIHandler: count of entries in 'seq' does not match 'nSteps' in anih");
504 if (!m_displayRates.isEmpty() && m_displayRates.count() != m_imageCount) {
505 qWarning(
"ANIHandler: count of entries in 'rate' does not match 'nSteps' in anih");
509 if (!m_frameOffsets.isEmpty() && m_frameOffsets.count() - 1 != m_frameCount) {
510 qWarning(
"ANIHandler: number of actual frames does not match 'nFrames' in anih");
514 mutableThis->m_scanned =
true;
518bool ANIHandler::canRead(
QIODevice *device)
521 qWarning(
"ANIHandler::canRead() called with no device");
530 if (riffIntro.
length() != 12) {
540 if (riffIntro.
mid(4 + 4, 4) !=
"ACON") {
549 if (format ==
"ani") {
560 if (device->
isReadable() && ANIHandler::canRead(device)) {
574#include "moc_ani_p.cpp"
QFlags< Capability > Capabilities
QVariant read(const QByteArray &data, int versionOverride=0)
KIOCORE_EXPORT QStringList list(const QString &fileClass)
char at(qsizetype i) const const
const char * constData() const const
bool isEmpty() const const
qsizetype length() const const
QByteArray mid(qsizetype pos, qsizetype len) const const
qsizetype size() const const
bool startsWith(QByteArrayView bv) const const
bool loadFromData(QByteArrayView data, const char *format)
void setDevice(QIODevice *device)
bool isOpen() const const
bool isReadable() const const
virtual bool isSequential() const const
QByteArray peek(qint64 maxSize)
void append(QList< T > &&value)
const_reference at(qsizetype i) const const
qsizetype count() const const
QString arg(Args &&... args) const const
QString fromLocal8Bit(QByteArrayView str)