mirror of
https://github.com/scummvm/scummvm.git
synced 2026-05-21 05:40:43 +00:00
IMAGE: Add support for loading CUR and ANI files
This commit is contained in:
+26
-12
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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 \
|
||||
|
||||
Reference in New Issue
Block a user