IMAGE: Add support for loading CUR and ANI files

This commit is contained in:
elasota
2024-01-18 00:43:00 -05:00
parent a66b5ea4be
commit 26c054e0c5
7 changed files with 723 additions and 12 deletions
+26 -12
View File
@@ -31,7 +31,7 @@ namespace Graphics {
/** A Windows cursor. */
class WinCursor : public Cursor {
public:
WinCursor();
WinCursor(uint16 hotspotX, uint16 hotspotY);
~WinCursor();
/** Return the cursor's width. */
@@ -56,6 +56,8 @@ public:
bool readFromStream(Common::SeekableReadStream &stream);
private:
WinCursor() = delete;
byte *_surface;
byte *_mask;
byte _palette[256 * 3];
@@ -70,11 +72,11 @@ private:
void clear();
};
WinCursor::WinCursor() {
WinCursor::WinCursor(uint16 hotspotX, uint16 hotspotY) {
_width = 0;
_height = 0;
_hotspotX = 0;
_hotspotY = 0;
_hotspotX = hotspotX;
_hotspotY = hotspotY;
_surface = nullptr;
_mask = nullptr;
_keyColor = 0;
@@ -111,9 +113,6 @@ bool WinCursor::readFromStream(Common::SeekableReadStream &stream) {
const bool supportOpacity = g_system->hasFeature(OSystem::kFeatureCursorMask);
const bool supportInvert = g_system->hasFeature(OSystem::kFeatureCursorMaskInvert);
_hotspotX = stream.readUint16LE();
_hotspotY = stream.readUint16LE();
// Check header size
if (stream.readUint32LE() != 40)
return false;
@@ -151,8 +150,10 @@ bool WinCursor::readFromStream(Common::SeekableReadStream &stream) {
if (numColors == 0)
numColors = 1 << bitsPerPixel;
// Skip number of important colors
stream.skip(4);
// Reading the palette
stream.seek(40 + 4);
for (uint32 i = 0 ; i < numColors; i++) {
_palette[i * 3 + 2] = stream.readByte();
_palette[i * 3 + 1] = stream.readByte();
@@ -311,11 +312,14 @@ WinCursorGroup *WinCursorGroup::createCursorGroup(Common::WinResources *exe, con
return 0;
}
WinCursor *cursor = new WinCursor();
if (!cursor->readFromStream(*cursorStream)) {
delete cursor;
uint16 hotspotX = cursorStream->readUint16LE();
uint16 hotspotY = cursorStream->readUint16LE();
Cursor *cursor = loadWindowsCursorFromDIB(*cursorStream, hotspotX, hotspotY);
if (!cursor) {
delete group;
return 0;
return nullptr;
}
CursorItem item;
@@ -448,4 +452,14 @@ Cursor *makeBusyWinCursor() {
return new BusyWinCursor();
}
Cursor *loadWindowsCursorFromDIB(Common::SeekableReadStream &stream, uint16 hotspotX, uint16 hotspotY) {
WinCursor *cursor = new WinCursor(hotspotX, hotspotY);
if (!cursor->readFromStream(stream)) {
delete cursor;
return nullptr;
}
return cursor;
}
} // End of namespace Graphics
+7
View File
@@ -80,6 +80,13 @@ Cursor *makeDefaultWinCursor();
*/
Cursor *makeBusyWinCursor();
/**
* Create a Cursor from DIB-format data, i.e. starting with a BITMAPINFOHEADER
*
* @note The calling code is responsible for deleting the returned pointer.
*/
Cursor *loadWindowsCursorFromDIB(Common::SeekableReadStream &stream, uint16 hotspotX, uint16 hotspotY);
/** @} */
} // End of namespace Graphics
+304
View File
@@ -0,0 +1,304 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/memstream.h"
#include "common/stream.h"
#include "common/substream.h"
#include "image/ani.h"
namespace Image {
AniDecoder::Metadata::Metadata()
: numFrames(0), numSteps(0), width(0), height(0), bitCount(0),
numPlanes(0), perFrameDelay(0), haveSeqData(false), isCURFormat(false) {
}
AniDecoder::FrameDef::FrameDef() : delay(0), imageIndex(0) {
}
AniDecoder::AniDecoder() : _stream(nullptr), _disposeAfterUse(DisposeAfterUse::NO) {
}
AniDecoder::~AniDecoder() {
close();
}
void AniDecoder::close() {
if (_disposeAfterUse == DisposeAfterUse::YES && _stream != nullptr)
delete _stream;
_stream = nullptr;
}
const AniDecoder::Metadata &AniDecoder::getMetadata() const {
return _metadata;
}
AniDecoder::FrameDef AniDecoder::getSequenceFrame(uint sequenceIndex) const {
FrameDef frameDef;
if (sequenceIndex >= _rateData.size())
frameDef.delay = _metadata.perFrameDelay;
else
frameDef.delay = _rateData[sequenceIndex];
if (sequenceIndex >= _seqData.size())
frameDef.imageIndex = sequenceIndex;
else
frameDef.imageIndex = _seqData[sequenceIndex];
return frameDef;
}
Common::SeekableReadStream *AniDecoder::openImageStream(uint imageIndex) const {
if (imageIndex >= _frameDataLocations.size())
error("Invalid ANI image index");
const FrameDataRange &frameDataRange = _frameDataLocations[imageIndex];
return new Common::SafeSeekableSubReadStream(_stream, frameDataRange.pos, frameDataRange.pos + frameDataRange.size);
}
bool AniDecoder::open(Common::SeekableReadStream &stream, DisposeAfterUse::Flag disposeAfterUse) {
close();
_stream = &stream;
_disposeAfterUse = disposeAfterUse;
bool loadedOK = load();
if (!loadedOK)
close();
return loadedOK;
}
bool AniDecoder::load() {
if (!parseRIFFChunks(*_stream, Common::Functor2Mem<const RIFFChunkDef &, Common::SeekableReadStream &, bool, AniDecoder>(this, &AniDecoder::parseTopLevelChunk))) {
warning("AniDecoder::load: Failed to load ANI container");
return false;
}
return true;
}
bool AniDecoder::parseRIFFChunks(Common::SeekableReadStream &stream, const RIFFChunkParseFunc_t &callback) {
int64 nextChunkStartPos = 0;
int64 endPos = stream.size();
while (nextChunkStartPos < endPos) {
if (!stream.seek(nextChunkStartPos)) {
warning("AniDecoder::parseRIFFChunks: Failed to reset to start of RIFF chunk");
return false;
}
byte riffChunkHeader[8];
if (stream.read(riffChunkHeader, 8) != 8) {
warning("AniDecoder::parseRIFFChunks: Failed to read RIFF chunk header");
return false;
}
uint32 chunkSize = READ_LE_UINT32(riffChunkHeader + 4);
int64 actualChunkSize = chunkSize;
if (chunkSize & 1)
actualChunkSize++;
int64 chunkAvailable = stream.size() - stream.pos();
if (chunkAvailable < actualChunkSize) {
warning("AniDecoder::parseRIFFChunk: RIFF chunk is too large");
return false;
}
RIFFChunkDef chunkDef;
chunkDef.id = READ_BE_UINT32(riffChunkHeader);
chunkDef.size = chunkSize;
Common::SeekableSubReadStream substream(&stream, static_cast<uint32>(stream.pos()), static_cast<uint32>(stream.pos()) + chunkSize);
if (!callback(chunkDef, substream))
return false;
nextChunkStartPos += actualChunkSize + 8;
}
return true;
}
bool AniDecoder::parseRIFFContainer(Common::SeekableReadStream &chunkStream, const RIFFChunkDef &chunkDef, const RIFFContainerParseFunc_t &callback) {
if (chunkDef.size < 4) {
warning("AniDecoder::parseRIFFContainer: RIFF container is too small");
return false;
}
byte containerTypeID[4];
if (chunkStream.read(containerTypeID, 4) != 4) {
warning("AniDecoder::parseRIFFContainer: Failed to read RIFF container type");
return false;
}
RIFFContainerDef containerDef;
containerDef.id = READ_BE_UINT32(containerTypeID);
containerDef.size = chunkDef.size - 4;
Common::SeekableSubReadStream substream(&chunkStream, 4, chunkDef.size);
return callback(containerDef, substream);
}
bool AniDecoder::parseTopLevelChunk(const RIFFChunkDef &chunk, Common::SeekableReadStream &stream) {
if (chunk.id != MKTAG('R', 'I', 'F', 'F')) {
warning("AniDecoder::parseTopLevelChunk: Top-level chunk isn't RIFF");
return false;
}
return parseRIFFContainer(stream, chunk, Common::Functor2Mem<const RIFFContainerDef &, Common::SeekableReadStream &, bool, AniDecoder>(this, &AniDecoder::parseTopLevelContainer));
}
bool AniDecoder::parseTopLevelContainer(const RIFFContainerDef &container, Common::SeekableReadStream &stream) {
if (container.id == MKTAG('A', 'C', 'O', 'N'))
return parseRIFFChunks(stream, Common::Functor2Mem<const RIFFChunkDef &, Common::SeekableReadStream &, bool, AniDecoder>(this, &AniDecoder::parseSecondLevelChunk));
warning("AniDecoder::parseTopLevelContainer: Top-level container isn't ACON");
return false;
}
bool AniDecoder::parseSecondLevelChunk(const RIFFChunkDef &chunk, Common::SeekableReadStream &stream) {
if (chunk.id == MKTAG('L', 'I', 'S', 'T'))
return parseRIFFContainer(stream, chunk, Common::Functor2Mem<const RIFFContainerDef &, Common::SeekableReadStream &, bool, AniDecoder>(this, &AniDecoder::parseListContainer));
if (chunk.id == MKTAG('a', 'n', 'i', 'h'))
return parseAnimHeaderChunk(chunk, stream);
if (chunk.id == MKTAG('s', 'e', 'q', ' '))
return parseSeqChunk(chunk, stream);
if (chunk.id == MKTAG('r', 'a', 't', 'e'))
return parseRateChunk(chunk, stream);
return true;
}
bool AniDecoder::parseListContainer(const RIFFContainerDef &container, Common::SeekableReadStream &stream) {
if (container.id == MKTAG('f', 'r', 'a', 'm'))
return parseRIFFChunks(stream, Common::Functor2Mem<const RIFFChunkDef &, Common::SeekableReadStream &, bool, AniDecoder>(this, &AniDecoder::parseIconChunk));
return true;
}
bool AniDecoder::parseAnimHeaderChunk(const RIFFChunkDef &chunk, Common::SeekableReadStream &stream) {
byte animHeader[36];
for (byte &b : animHeader)
b = 0;
uint32 amountToRead = 36;
if (chunk.size < amountToRead)
amountToRead = chunk.size;
if (amountToRead > 0 && stream.read(animHeader, amountToRead) != amountToRead) {
warning("AniDecoder::parseAnimHeaderChunk: Read failed");
return false;
}
uint32 structSize = READ_LE_UINT32(animHeader);
if (structSize < 36) {
for (uint i = structSize; i < 36; i++)
animHeader[i] = 0;
}
_metadata.numFrames = READ_LE_UINT32(animHeader + 4);
_metadata.numSteps = READ_LE_UINT32(animHeader + 8);
_metadata.width = READ_LE_UINT32(animHeader + 12);
_metadata.height = READ_LE_UINT32(animHeader + 16);
_metadata.bitCount = READ_LE_UINT32(animHeader + 20);
_metadata.numPlanes = READ_LE_UINT32(animHeader + 24);
_metadata.perFrameDelay = READ_LE_UINT32(animHeader + 28);
uint32 flags = READ_LE_UINT32(animHeader + 32);
_metadata.isCURFormat = ((flags & 1) != 0);
_metadata.haveSeqData = ((flags & 2) != 0);
return true;
}
bool AniDecoder::parseSeqChunk(const RIFFChunkDef &chunk, Common::SeekableReadStream &stream) {
uint32 numFrames = chunk.size / 4u;
if (numFrames > 1000u) {
warning("AniDecoder::parseRateChunk: Too many frames");
return false;
}
if (numFrames > _seqData.size())
_seqData.resize(numFrames);
for (uint i = 0; i < numFrames; i++) {
byte seqData[4];
if (stream.read(seqData, 4) != 4) {
warning("AniDecoder::parseRateChunk: Failed to read sequence information");
return false;
}
_seqData[i] = READ_LE_UINT32(seqData);
}
return true;
}
bool AniDecoder::parseRateChunk(const RIFFChunkDef &chunk, Common::SeekableReadStream &stream) {
uint32 numFrames = chunk.size / 4u;
if (numFrames > 1000u) {
warning("AniDecoder::parseRateChunk: Too many frames");
return false;
}
if (numFrames > _rateData.size())
_rateData.resize(numFrames);
for (uint i = 0; i < numFrames; i++) {
byte rateData[4];
if (stream.read(rateData, 4) != 4) {
warning("AniDecoder::parseRateChunk: Failed to read rate information");
return false;
}
_rateData[i] = READ_LE_UINT32(rateData);
}
return true;
}
bool AniDecoder::parseIconChunk(const RIFFChunkDef &chunk, Common::SeekableReadStream &stream) {
FrameDataRange frameDataRange;
// Get the global stream position
frameDataRange.pos = static_cast<uint32>(_stream->pos());
frameDataRange.size = chunk.size;
_frameDataLocations.push_back(frameDataRange);
return true;
}
} // End of namespace Image
+136
View File
@@ -0,0 +1,136 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GFX_ANI_H
#define GFX_ANI_H
#include "common/array.h"
#include "common/types.h"
#include "common/func.h"
namespace Common {
class SeekableReadStream;
struct IFFChunk;
} // End of namespace Common
namespace Graphics {
class Cursor;
struct Surface;
} // End of namespace Graphics
namespace Image {
class AniDecoder {
public:
struct Metadata {
Metadata();
uint32 numFrames; // Number of images
uint32 numSteps; // Number of frames (use the FrameDef to determine which frame)
uint32 width;
uint32 height;
uint32 bitCount;
uint32 numPlanes;
uint32 perFrameDelay;
bool haveSeqData;
bool isCURFormat;
};
struct FrameDef {
FrameDef();
uint32 imageIndex;
uint32 delay; // In 1/60 sec
};
AniDecoder();
~AniDecoder();
bool open(Common::SeekableReadStream &stream, DisposeAfterUse::Flag = DisposeAfterUse::NO);
void close();
const Metadata &getMetadata() const;
FrameDef getSequenceFrame(uint sequenceIndex) const;
/**
* Opens a substream for an image. If the metadata field
* "isCURFormat" is set, you can pass the stream to IcoCurDecoder to
* read it. Otherwise, you must determine the format. The stream
* is valid for as long as the stream used to construct the AniDecoder
* is valid.
*
* @param imageIndex The index of the image in the ANI file.
* @return A substream for the image.
*/
Common::SeekableReadStream *openImageStream(uint imageIndex) const;
private:
struct RIFFContainerDef {
uint32 id;
uint32 size;
};
struct RIFFChunkDef {
uint32 id;
uint32 size;
};
struct FrameDataRange {
uint32 pos;
uint32 size;
};
typedef Common::Functor2<const RIFFContainerDef &, Common::SeekableReadStream &, bool> RIFFContainerParseFunc_t;
typedef Common::Functor2<const RIFFChunkDef &, Common::SeekableReadStream &, bool> RIFFChunkParseFunc_t;
bool load();
static bool parseRIFFChunks(Common::SeekableReadStream &stream, const RIFFChunkParseFunc_t &callback);
static bool parseRIFFContainer(Common::SeekableReadStream &stream, const RIFFChunkDef &chunkDef, const RIFFContainerParseFunc_t &callback);
bool parseTopLevelChunk(const RIFFChunkDef &chunk, Common::SeekableReadStream &stream);
bool parseTopLevelContainer(const RIFFContainerDef &container, Common::SeekableReadStream &stream);
bool parseSecondLevelChunk(const RIFFChunkDef &chunk, Common::SeekableReadStream &stream);
bool parseListContainer(const RIFFContainerDef &container, Common::SeekableReadStream &stream);
bool parseAnimHeaderChunk(const RIFFChunkDef &chunk, Common::SeekableReadStream &stream);
bool parseSeqChunk(const RIFFChunkDef &chunk, Common::SeekableReadStream &stream);
bool parseRateChunk(const RIFFChunkDef &chunk, Common::SeekableReadStream &stream);
bool parseIconChunk(const RIFFChunkDef &chunk, Common::SeekableReadStream &stream);
Metadata _metadata;
Common::Array<uint32> _rateData;
Common::Array<uint32> _seqData;
Common::Array<FrameDataRange> _frameDataLocations;
Common::SeekableReadStream *_stream;
DisposeAfterUse::Flag _disposeAfterUse;
};
} // End of namespace Image
#endif
+142
View File
@@ -0,0 +1,142 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/stream.h"
#include "common/substream.h"
#include "common/memstream.h"
#include "image/icocur.h"
#include "graphics/wincursor.h"
namespace Image {
IcoCurDecoder::IcoCurDecoder() : _type(kTypeInvalid), _stream(nullptr), _disposeAfterUse(DisposeAfterUse::NO) {
}
IcoCurDecoder::~IcoCurDecoder() {
close();
}
void IcoCurDecoder::close() {
if (_disposeAfterUse == DisposeAfterUse::YES && _stream != nullptr)
delete _stream;
_stream = nullptr;
_type = kTypeInvalid;
_items.clear();
}
bool IcoCurDecoder::open(Common::SeekableReadStream &stream, DisposeAfterUse::Flag disposeAfterUse) {
close();
_stream = &stream;
_disposeAfterUse = disposeAfterUse;
bool loadedOK = load();
if (!loadedOK)
close();
return loadedOK;
}
bool IcoCurDecoder::load() {
uint8 iconDirData[6];
if (_stream->read(iconDirData, 6) != 6)
return false;
if (iconDirData[0] != 0 || iconDirData[1] != 0 || (iconDirData[2] != 1 && iconDirData[2] != 2) || iconDirData[3] != 0) {
warning("Malformed ICO/CUR header");
return false;
}
uint16 numImages = READ_LE_UINT16(iconDirData + 4);
_type = static_cast<Type>(iconDirData[2]);
if (numImages == 0)
return true;
uint32 dirSize = static_cast<uint32>(numImages) * 16;
Common::Array<uint8> iconDir;
iconDir.resize(dirSize);
if (_stream->read(&iconDir[0], dirSize) != dirSize)
return false;
_items.resize(numImages);
for (uint i = 0; i < numImages; i++) {
const uint8 *entryData = &iconDir[i * 16u];
Item &item = _items[i];
item.width = entryData[0];
if (item.width == 0)
item.width = 256;
item.height = entryData[1];
if (item.height == 0)
item.height = 256;
item.numColors = entryData[2];
item.data.ico.numPlanes = READ_LE_UINT16(entryData + 4);
item.data.ico.bitsPerPixel = READ_LE_UINT16(entryData + 6);
item.dataSize = READ_LE_UINT32(entryData + 8);
item.dataOffset = READ_LE_UINT32(entryData + 12);
}
return true;
}
IcoCurDecoder::Type IcoCurDecoder::getType() const {
return _type;
}
uint IcoCurDecoder::numItems() const {
return _items.size();
}
const IcoCurDecoder::Item &IcoCurDecoder::getItem(uint itemIndex) const {
return _items[itemIndex];
}
Graphics::Cursor *IcoCurDecoder::loadItemAsCursor(uint itemIndex) const {
const IcoCurDecoder::Item &dirItem = _items[itemIndex];
if (_type != kTypeCUR)
warning("ICO/CUR file type wasn't a cursor, but is being requested as a cursor anyway");
if (static_cast<int64>(dirItem.dataOffset) > _stream->size()) {
warning("ICO/CUR data offset was outside of the file");
return nullptr;
}
if (_stream->size() - static_cast<int64>(dirItem.dataOffset) < static_cast<int64>(dirItem.dataSize)) {
warning("ICO/CUR data bounds were outside of the file");
return nullptr;
}
Common::SeekableSubReadStream substream(_stream, dirItem.dataOffset, dirItem.dataOffset + dirItem.dataSize);
return Graphics::loadWindowsCursorFromDIB(substream, dirItem.data.cur.hotspotX, dirItem.data.cur.hotspotY);
}
} // End of namespace Image
+106
View File
@@ -0,0 +1,106 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GFX_ICOCUR_H
#define GFX_ICOCUR_H
#include "common/array.h"
#include "common/types.h"
namespace Common {
class SeekableReadStream;
} // End of namespace Common
namespace Graphics {
class Cursor;
struct Surface;
} // End of namespace Graphics
namespace Image {
class IcoCurDecoder {
public:
enum Type {
kTypeInvalid,
kTypeICO,
kTypeCUR,
};
struct Item {
struct IconData {
uint16 numPlanes;
uint16 bitsPerPixel;
};
struct CursorData {
uint16 hotspotX;
uint16 hotspotY;
};
union DataUnion {
IconData ico;
CursorData cur;
};
uint16 width;
uint16 height;
uint8 numColors; // May be 0
DataUnion data;
uint32 dataSize;
uint32 dataOffset;
};
IcoCurDecoder();
~IcoCurDecoder();
bool open(Common::SeekableReadStream &stream, DisposeAfterUse::Flag = DisposeAfterUse::NO);
void close();
Type getType() const;
uint numItems() const;
const Item &getItem(uint itemIndex) const;
/**
* Loads an item from the directory as a cursor.
*
* @param itemIndex The index of the item in the directory.
* @return Loaded cursor.
*/
Graphics::Cursor *loadItemAsCursor(uint itemIndex) const;
private:
bool load();
Type _type;
Common::Array<Item> _items;
Common::SeekableReadStream *_stream;
DisposeAfterUse::Flag _disposeAfterUse;
};
} // End of namespace Image
#endif
+2
View File
@@ -1,9 +1,11 @@
MODULE := image
MODULE_OBJS := \
ani.o \
bmp.o \
cel_3do.o \
gif.o \
icocur.o \
iff.o \
jpeg.o \
neo.o \