2020-11-21 08:58:42 -08:00
|
|
|
/* 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 2
|
|
|
|
* 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, write to the Free Software
|
|
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
2020-11-21 08:30:51 -08:00
|
|
|
//=============================================================================
|
|
|
|
//
|
|
|
|
// sprite caching system
|
|
|
|
//
|
|
|
|
//=============================================================================
|
|
|
|
|
|
|
|
#ifdef _MANAGED
|
|
|
|
// ensure this doesn't get compiled to .NET IL
|
|
|
|
#pragma unmanaged
|
|
|
|
#pragma warning (disable: 4996 4312) // disable deprecation warnings
|
|
|
|
#endif
|
|
|
|
|
2021-05-25 07:52:55 -07:00
|
|
|
#include "common/system.h"
|
2021-07-07 19:19:04 -07:00
|
|
|
#include "ags/lib/std/algorithm.h"
|
2020-11-21 18:19:22 -08:00
|
|
|
#include "ags/shared/ac/common.h" // quit
|
2021-05-25 07:52:55 -07:00
|
|
|
#include "ags/shared/ac/game_struct_defines.h"
|
|
|
|
#include "ags/shared/ac/sprite_cache.h"
|
|
|
|
#include "ags/shared/core/asset_manager.h"
|
2020-11-21 18:19:22 -08:00
|
|
|
#include "ags/shared/debugging/out.h"
|
|
|
|
#include "ags/shared/gfx/bitmap.h"
|
|
|
|
#include "ags/shared/util/compress.h"
|
|
|
|
#include "ags/shared/util/file.h"
|
|
|
|
#include "ags/shared/util/stream.h"
|
2021-05-25 07:52:55 -07:00
|
|
|
#include "ags/globals.h"
|
2020-11-21 08:30:51 -08:00
|
|
|
|
2020-11-21 16:24:18 -08:00
|
|
|
namespace AGS3 {
|
|
|
|
|
2020-11-21 16:10:55 -08:00
|
|
|
using namespace AGS::Shared;
|
2020-11-21 08:30:51 -08:00
|
|
|
|
|
|
|
// [IKM] We have to forward-declare these because their implementations are in the Engine
|
|
|
|
extern void initialize_sprite(int);
|
2021-07-07 19:19:04 -07:00
|
|
|
extern void pre_save_sprite(Bitmap *image);
|
2020-11-21 08:30:51 -08:00
|
|
|
extern void get_new_size_for_sprite(int, int, int, int &, int &);
|
|
|
|
|
|
|
|
#define START_OF_LIST -1
|
|
|
|
#define END_OF_LIST -1
|
|
|
|
|
|
|
|
const char *spindexid = "SPRINDEX";
|
|
|
|
|
|
|
|
// TODO: should not be part of SpriteCache, but rather some asset management class?
|
2021-07-07 19:19:04 -07:00
|
|
|
const char *const SpriteFile::DefaultSpriteFileName = "acsprset.spr";
|
|
|
|
const char *const SpriteFile::DefaultSpriteIndexName = "sprindex.dat";
|
2020-11-21 08:30:51 -08:00
|
|
|
|
|
|
|
SpriteInfo::SpriteInfo()
|
2020-11-21 19:31:48 +00:00
|
|
|
: Flags(0)
|
|
|
|
, Width(0)
|
|
|
|
, Height(0) {
|
2020-11-21 08:30:51 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
SpriteCache::SpriteData::SpriteData()
|
2021-07-07 19:19:04 -07:00
|
|
|
: Size(0)
|
2020-11-21 19:31:48 +00:00
|
|
|
, Flags(0)
|
|
|
|
, Image(nullptr) {
|
2020-11-21 08:30:51 -08:00
|
|
|
}
|
|
|
|
|
2020-11-21 19:31:48 +00:00
|
|
|
SpriteCache::SpriteData::~SpriteData() {
|
|
|
|
// TODO: investigate, if it's actually safe/preferred to delete bitmap here
|
|
|
|
// (some of these bitmaps may be assigned from outside of the cache)
|
2020-11-21 08:30:51 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-07-07 19:19:04 -07:00
|
|
|
SpriteFile::SpriteFile() {
|
|
|
|
_compressed = false;
|
|
|
|
_curPos = -2;
|
|
|
|
}
|
|
|
|
|
2020-11-21 08:30:51 -08:00
|
|
|
SpriteCache::SpriteCache(std::vector<SpriteInfo> &sprInfos)
|
2020-11-21 19:31:48 +00:00
|
|
|
: _sprInfos(sprInfos) {
|
|
|
|
Init();
|
2020-11-21 08:30:51 -08:00
|
|
|
}
|
|
|
|
|
2020-11-21 19:31:48 +00:00
|
|
|
SpriteCache::~SpriteCache() {
|
|
|
|
Reset();
|
2020-11-21 08:30:51 -08:00
|
|
|
}
|
|
|
|
|
2020-11-21 19:31:48 +00:00
|
|
|
size_t SpriteCache::GetCacheSize() const {
|
|
|
|
return _cacheSize;
|
2020-11-21 08:30:51 -08:00
|
|
|
}
|
|
|
|
|
2020-11-21 19:31:48 +00:00
|
|
|
size_t SpriteCache::GetLockedSize() const {
|
|
|
|
return _lockedSize;
|
2020-11-21 08:30:51 -08:00
|
|
|
}
|
|
|
|
|
2020-11-21 19:31:48 +00:00
|
|
|
size_t SpriteCache::GetMaxCacheSize() const {
|
|
|
|
return _maxCacheSize;
|
2020-11-21 08:30:51 -08:00
|
|
|
}
|
|
|
|
|
2021-05-25 07:52:55 -07:00
|
|
|
size_t SpriteCache::GetSpriteSlotCount() const {
|
2020-11-21 19:31:48 +00:00
|
|
|
return _spriteData.size();
|
2020-11-21 08:30:51 -08:00
|
|
|
}
|
|
|
|
|
2021-07-07 19:19:04 -07:00
|
|
|
sprkey_t SpriteFile::FindTopmostSprite(const std::vector<Bitmap *> &sprites) {
|
2020-11-21 19:31:48 +00:00
|
|
|
sprkey_t topmost = -1;
|
2021-07-07 19:19:04 -07:00
|
|
|
for (sprkey_t i = 0; i < static_cast<sprkey_t>(sprites.size()); ++i)
|
|
|
|
if (sprites[i])
|
2020-11-21 19:31:48 +00:00
|
|
|
topmost = i;
|
|
|
|
return topmost;
|
2020-11-21 08:30:51 -08:00
|
|
|
}
|
|
|
|
|
2020-11-21 19:31:48 +00:00
|
|
|
void SpriteCache::SetMaxCacheSize(size_t size) {
|
|
|
|
_maxCacheSize = size;
|
2020-11-21 08:30:51 -08:00
|
|
|
}
|
|
|
|
|
2020-11-21 19:31:48 +00:00
|
|
|
void SpriteCache::Init() {
|
|
|
|
_cacheSize = 0;
|
|
|
|
_lockedSize = 0;
|
|
|
|
_maxCacheSize = (size_t)DEFAULTCACHESIZE_KB * 1024;
|
|
|
|
_liststart = -1;
|
|
|
|
_listend = -1;
|
2020-11-21 08:30:51 -08:00
|
|
|
}
|
|
|
|
|
2020-11-21 19:31:48 +00:00
|
|
|
void SpriteCache::Reset() {
|
2021-07-07 19:19:04 -07:00
|
|
|
_file.Reset();
|
2020-11-21 19:31:48 +00:00
|
|
|
// TODO: find out if it's safe to simply always delete _spriteData.Image with array element
|
|
|
|
for (size_t i = 0; i < _spriteData.size(); ++i) {
|
|
|
|
if (_spriteData[i].Image) {
|
|
|
|
delete _spriteData[i].Image;
|
|
|
|
_spriteData[i].Image = nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_spriteData.clear();
|
2020-11-21 08:30:51 -08:00
|
|
|
|
2020-11-21 19:31:48 +00:00
|
|
|
_mrulist.clear();
|
|
|
|
_mrubacklink.clear();
|
2020-11-21 08:30:51 -08:00
|
|
|
|
2020-11-21 19:31:48 +00:00
|
|
|
Init();
|
2020-11-21 08:30:51 -08:00
|
|
|
}
|
|
|
|
|
2020-11-21 19:31:48 +00:00
|
|
|
void SpriteCache::SetSprite(sprkey_t index, Bitmap *sprite) {
|
|
|
|
if (index < 0 || EnlargeTo(index) != index) {
|
|
|
|
Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Error, "SetSprite: unable to use index %d", index);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!sprite) {
|
|
|
|
Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Error, "SetSprite: attempt to assign nullptr to index %d", index);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
_spriteData[index].Image = sprite;
|
|
|
|
_spriteData[index].Flags = SPRCACHEFLAG_LOCKED; // NOT from asset file
|
|
|
|
_spriteData[index].Size = 0;
|
2020-11-21 08:30:51 -08:00
|
|
|
#ifdef DEBUG_SPRITECACHE
|
2020-11-21 19:31:48 +00:00
|
|
|
Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Debug, "SetSprite: (external) %d", index);
|
2020-11-21 08:30:51 -08:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2020-11-21 19:31:48 +00:00
|
|
|
void SpriteCache::SetEmptySprite(sprkey_t index, bool as_asset) {
|
|
|
|
if (index < 0 || EnlargeTo(index) != index) {
|
|
|
|
Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Error, "SetEmptySprite: unable to use index %d", index);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (as_asset)
|
|
|
|
_spriteData[index].Flags = SPRCACHEFLAG_ISASSET;
|
|
|
|
RemapSpriteToSprite0(index);
|
|
|
|
}
|
|
|
|
|
2021-07-07 19:19:04 -07:00
|
|
|
void SpriteCache::SubstituteBitmap(sprkey_t index, Bitmap *sprite) {
|
2020-11-21 19:31:48 +00:00
|
|
|
if (!DoesSpriteExist(index)) {
|
|
|
|
Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Error, "SubstituteBitmap: attempt to set for non-existing sprite %d", index);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
_spriteData[index].Image = sprite;
|
2020-11-21 08:30:51 -08:00
|
|
|
#ifdef DEBUG_SPRITECACHE
|
2020-11-21 19:31:48 +00:00
|
|
|
Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Debug, "SubstituteBitmap: %d", index);
|
2020-11-21 08:30:51 -08:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2020-11-21 19:31:48 +00:00
|
|
|
void SpriteCache::RemoveSprite(sprkey_t index, bool freeMemory) {
|
|
|
|
if (freeMemory)
|
|
|
|
delete _spriteData[index].Image;
|
|
|
|
InitNullSpriteParams(index);
|
2020-11-21 08:30:51 -08:00
|
|
|
#ifdef DEBUG_SPRITECACHE
|
2020-11-21 19:31:48 +00:00
|
|
|
Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Debug, "RemoveSprite: %d", index);
|
2020-11-21 08:30:51 -08:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2020-11-21 19:31:48 +00:00
|
|
|
sprkey_t SpriteCache::EnlargeTo(sprkey_t topmost) {
|
|
|
|
if (topmost < 0 || topmost > MAX_SPRITE_INDEX)
|
|
|
|
return -1;
|
|
|
|
if ((size_t)topmost < _spriteData.size())
|
|
|
|
return topmost;
|
|
|
|
|
|
|
|
size_t newsize = topmost + 1;
|
|
|
|
_sprInfos.resize(newsize);
|
|
|
|
_spriteData.resize(newsize);
|
|
|
|
_mrulist.resize(newsize);
|
|
|
|
_mrubacklink.resize(newsize);
|
|
|
|
return topmost;
|
|
|
|
}
|
|
|
|
|
|
|
|
sprkey_t SpriteCache::GetFreeIndex() {
|
|
|
|
for (size_t i = MIN_SPRITE_INDEX; i < _spriteData.size(); ++i) {
|
|
|
|
// slot empty
|
|
|
|
if (!DoesSpriteExist(i)) {
|
|
|
|
_sprInfos[i] = SpriteInfo();
|
|
|
|
_spriteData[i] = SpriteData();
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// enlarge the sprite bank to find a free slot and return the first new free slot
|
|
|
|
return EnlargeTo(_spriteData.size());
|
|
|
|
}
|
|
|
|
|
|
|
|
bool SpriteCache::SpriteData::DoesSpriteExist() const {
|
|
|
|
return (Image != nullptr) || // HAS loaded bitmap
|
2021-07-07 19:19:04 -07:00
|
|
|
((Flags & SPRCACHEFLAG_ISASSET) != 0); // OR found in the game resources
|
2020-11-21 19:31:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool SpriteCache::SpriteData::IsAssetSprite() const {
|
|
|
|
return (Flags & SPRCACHEFLAG_ISASSET) != 0; // found in game resources
|
|
|
|
}
|
|
|
|
|
|
|
|
bool SpriteCache::SpriteData::IsExternalSprite() const {
|
|
|
|
return (Image != nullptr) && // HAS loaded bitmap
|
2021-07-07 19:19:04 -07:00
|
|
|
((Flags & SPRCACHEFLAG_ISASSET) == 0) && // AND NOT found in game resources
|
|
|
|
((Flags & SPRCACHEFLAG_REMAPPED) == 0); // AND was NOT remapped to another sprite
|
2020-11-21 19:31:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool SpriteCache::SpriteData::IsLocked() const {
|
|
|
|
return (Flags & SPRCACHEFLAG_LOCKED) != 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool SpriteCache::DoesSpriteExist(sprkey_t index) const {
|
|
|
|
return index >= 0 && (size_t)index < _spriteData.size() && _spriteData[index].DoesSpriteExist();
|
|
|
|
}
|
|
|
|
|
2021-07-07 19:19:04 -07:00
|
|
|
Bitmap *SpriteCache::operator [] (sprkey_t index) {
|
2020-11-21 19:31:48 +00:00
|
|
|
// invalid sprite slot
|
|
|
|
if (index < 0 || (size_t)index >= _spriteData.size())
|
|
|
|
return nullptr;
|
|
|
|
|
|
|
|
// Externally added sprite, don't put it into MRU list
|
|
|
|
if (_spriteData[index].IsExternalSprite())
|
|
|
|
return _spriteData[index].Image;
|
|
|
|
|
|
|
|
// Sprite exists in file but is not in mem, load it
|
|
|
|
if ((_spriteData[index].Image == nullptr) && _spriteData[index].IsAssetSprite())
|
|
|
|
LoadSprite(index);
|
|
|
|
|
|
|
|
// Locked sprite that shouldn't be put into MRU list
|
|
|
|
if (_spriteData[index].IsLocked())
|
|
|
|
return _spriteData[index].Image;
|
|
|
|
|
|
|
|
if (_liststart < 0) {
|
|
|
|
_liststart = index;
|
|
|
|
_listend = index;
|
|
|
|
_mrulist[index] = END_OF_LIST;
|
|
|
|
_mrubacklink[index] = START_OF_LIST;
|
|
|
|
} else if (_listend != index) {
|
|
|
|
// this is the oldest element being bumped to newest, so update start link
|
|
|
|
if (index == _liststart) {
|
|
|
|
_liststart = _mrulist[index];
|
|
|
|
_mrubacklink[_liststart] = START_OF_LIST;
|
|
|
|
}
|
|
|
|
// already in list, link previous to next
|
|
|
|
else if (_mrulist[index] > 0) {
|
|
|
|
_mrulist[_mrubacklink[index]] = _mrulist[index];
|
|
|
|
_mrubacklink[_mrulist[index]] = _mrubacklink[index];
|
|
|
|
}
|
|
|
|
|
|
|
|
// set this as the newest element in the list
|
|
|
|
_mrulist[index] = END_OF_LIST;
|
|
|
|
_mrulist[_listend] = index;
|
|
|
|
_mrubacklink[index] = _listend;
|
|
|
|
_listend = index;
|
|
|
|
}
|
|
|
|
|
|
|
|
return _spriteData[index].Image;
|
|
|
|
}
|
|
|
|
|
|
|
|
void SpriteCache::DisposeOldest() {
|
|
|
|
if (_liststart < 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
sprkey_t sprnum = _liststart;
|
|
|
|
|
|
|
|
if ((_spriteData[sprnum].Image != nullptr) && !_spriteData[sprnum].IsLocked()) {
|
|
|
|
// Free the memory
|
|
|
|
// Sprites that are not from the game resources should not normally be in a MRU list;
|
|
|
|
// if such is met here there's something wrong with the internal cache logic!
|
|
|
|
if (!_spriteData[sprnum].IsAssetSprite()) {
|
|
|
|
quitprintf("SpriteCache::DisposeOldest: attempted to remove sprite %d that was added externally or does not exist", sprnum);
|
|
|
|
}
|
|
|
|
_cacheSize -= _spriteData[sprnum].Size;
|
|
|
|
|
|
|
|
delete _spriteData[sprnum].Image;
|
|
|
|
_spriteData[sprnum].Image = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_liststart == _listend) {
|
|
|
|
// there was one huge sprite, removing it has now emptied the cache completely
|
|
|
|
if (_cacheSize > 0) {
|
|
|
|
Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Error, "SPRITE CACHE ERROR: Sprite cache should be empty, but still has %d bytes", _cacheSize);
|
|
|
|
}
|
|
|
|
_mrulist[_liststart] = 0;
|
|
|
|
_liststart = -1;
|
|
|
|
_listend = -1;
|
|
|
|
} else {
|
|
|
|
sprkey_t oldstart = _liststart;
|
|
|
|
_liststart = _mrulist[_liststart];
|
|
|
|
_mrulist[oldstart] = 0;
|
|
|
|
_mrubacklink[_liststart] = START_OF_LIST;
|
|
|
|
if (oldstart == _liststart) {
|
|
|
|
// Somehow, we have got a recursive link to itself, so we
|
|
|
|
// the game will freeze (since it is not actually freeing any
|
|
|
|
// memory)
|
|
|
|
// There must be a bug somewhere causing this, but for now
|
|
|
|
// let's just reset the cache
|
|
|
|
Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Error, "RUNTIME CACHE ERROR: CACHE INCONSISTENT: RESETTING\n\tAt size %d (of %d), start %d end %d fwdlink=%d",
|
2021-07-07 19:19:04 -07:00
|
|
|
_cacheSize, _maxCacheSize, oldstart, _listend, _liststart);
|
2020-11-21 19:31:48 +00:00
|
|
|
DisposeAll();
|
|
|
|
}
|
|
|
|
}
|
2020-11-21 08:30:51 -08:00
|
|
|
|
|
|
|
#ifdef DEBUG_SPRITECACHE
|
2020-11-21 19:31:48 +00:00
|
|
|
Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Debug, "DisposeOldest: disposed %d, size now %d KB", sprnum, _cacheSize / 1024);
|
2020-11-21 08:30:51 -08:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2020-11-21 19:31:48 +00:00
|
|
|
void SpriteCache::DisposeAll() {
|
|
|
|
_liststart = -1;
|
|
|
|
_listend = -1;
|
|
|
|
for (size_t i = 0; i < _spriteData.size(); ++i) {
|
|
|
|
if (!_spriteData[i].IsLocked() && // not locked
|
2021-07-07 19:19:04 -07:00
|
|
|
_spriteData[i].IsAssetSprite()) // sprite from game resource
|
|
|
|
{
|
2020-11-21 19:31:48 +00:00
|
|
|
delete _spriteData[i].Image;
|
|
|
|
_spriteData[i].Image = nullptr;
|
|
|
|
}
|
|
|
|
_mrulist[i] = 0;
|
|
|
|
_mrubacklink[i] = 0;
|
|
|
|
}
|
|
|
|
_cacheSize = _lockedSize;
|
2020-11-21 08:30:51 -08:00
|
|
|
}
|
|
|
|
|
2020-11-21 19:31:48 +00:00
|
|
|
void SpriteCache::Precache(sprkey_t index) {
|
|
|
|
if (index < 0 || (size_t)index >= _spriteData.size())
|
|
|
|
return;
|
2020-11-21 08:30:51 -08:00
|
|
|
|
2020-11-21 19:31:48 +00:00
|
|
|
soff_t sprSize = 0;
|
2020-11-21 08:30:51 -08:00
|
|
|
|
2020-11-21 19:31:48 +00:00
|
|
|
if (_spriteData[index].Image == nullptr)
|
|
|
|
sprSize = LoadSprite(index);
|
|
|
|
else if (!_spriteData[index].IsLocked())
|
|
|
|
sprSize = _spriteData[index].Size;
|
2020-11-21 08:30:51 -08:00
|
|
|
|
2020-11-21 19:31:48 +00:00
|
|
|
// make sure locked sprites can't fill the cache
|
|
|
|
_maxCacheSize += sprSize;
|
|
|
|
_lockedSize += sprSize;
|
2020-11-21 08:30:51 -08:00
|
|
|
|
2020-11-21 19:31:48 +00:00
|
|
|
_spriteData[index].Flags |= SPRCACHEFLAG_LOCKED;
|
2020-11-21 08:30:51 -08:00
|
|
|
|
|
|
|
#ifdef DEBUG_SPRITECACHE
|
2020-11-21 19:31:48 +00:00
|
|
|
Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Debug, "Precached %d", index);
|
2020-11-21 08:30:51 -08:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2020-11-21 19:31:48 +00:00
|
|
|
sprkey_t SpriteCache::GetDataIndex(sprkey_t index) {
|
|
|
|
return (_spriteData[index].Flags & SPRCACHEFLAG_REMAPPED) == 0 ? index : 0;
|
|
|
|
}
|
|
|
|
|
2021-07-07 19:19:04 -07:00
|
|
|
void SpriteFile::SeekToSprite(sprkey_t index) {
|
2020-11-21 19:31:48 +00:00
|
|
|
// If we didn't just load the previous sprite, seek to it
|
2021-07-07 19:19:04 -07:00
|
|
|
if (index != _curPos) {
|
2020-11-21 19:31:48 +00:00
|
|
|
_stream->Seek(_spriteData[index].Offset, kSeekBegin);
|
2021-07-07 19:19:04 -07:00
|
|
|
_curPos = index;
|
|
|
|
}
|
2020-11-21 19:31:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
size_t SpriteCache::LoadSprite(sprkey_t index) {
|
|
|
|
int hh = 0;
|
|
|
|
|
|
|
|
while (_cacheSize > _maxCacheSize) {
|
|
|
|
DisposeOldest();
|
|
|
|
hh++;
|
|
|
|
if (hh > 1000) {
|
|
|
|
Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Error, "RUNTIME CACHE ERROR: STUCK IN FREE_UP_MEM; RESETTING CACHE");
|
|
|
|
DisposeAll();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (index < 0 || (size_t)index >= _spriteData.size())
|
|
|
|
quit("sprite cache array index out of bounds");
|
|
|
|
|
|
|
|
sprkey_t load_index = GetDataIndex(index);
|
2021-07-07 19:19:04 -07:00
|
|
|
Bitmap *image;
|
|
|
|
HError err = _file.LoadSprite(load_index, image);
|
|
|
|
if (!image) {
|
|
|
|
Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Warn,
|
|
|
|
"LoadSprite: failed to load sprite %d:\n%s\n - remapping to sprite 0.", index,
|
|
|
|
err ? err->FullMessage().GetCStr() : "Sprite does not exist.");
|
2020-11-21 19:31:48 +00:00
|
|
|
RemapSpriteToSprite0(index);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-05-25 07:52:55 -07:00
|
|
|
// update the stored width/height
|
2021-07-07 19:19:04 -07:00
|
|
|
_sprInfos[index].Width = image->GetWidth();
|
|
|
|
_sprInfos[index].Height = image->GetHeight();
|
2021-05-25 07:52:55 -07:00
|
|
|
_spriteData[index].Image = image;
|
|
|
|
|
2020-11-21 19:31:48 +00:00
|
|
|
// Stop it adding the sprite to the used list just because it's loaded
|
|
|
|
// TODO: this messy hack is required, because initialize_sprite calls operator[]
|
|
|
|
// which puts the sprite to the MRU list.
|
|
|
|
_spriteData[index].Flags |= SPRCACHEFLAG_LOCKED;
|
|
|
|
|
|
|
|
// TODO: this is ugly: asks the engine to convert the sprite using its own knowledge.
|
|
|
|
// And engine assigns new bitmap using SpriteCache::SubstituteBitmap().
|
|
|
|
// Perhaps change to the callback function pointer?
|
|
|
|
initialize_sprite(index);
|
|
|
|
|
|
|
|
if (index != 0) // leave sprite 0 locked
|
|
|
|
_spriteData[index].Flags &= ~SPRCACHEFLAG_LOCKED;
|
|
|
|
|
|
|
|
// we need to store this because the main program might
|
|
|
|
// alter spritewidth/height if it resizes stuff
|
2021-07-07 19:19:04 -07:00
|
|
|
size_t size = _sprInfos[index].Width * _sprInfos[index].Height *
|
|
|
|
_spriteData[index].Image->GetBPP();
|
2020-11-21 19:31:48 +00:00
|
|
|
_spriteData[index].Size = size;
|
|
|
|
_cacheSize += size;
|
2020-11-21 08:30:51 -08:00
|
|
|
|
|
|
|
#ifdef DEBUG_SPRITECACHE
|
2021-06-08 20:14:23 -07:00
|
|
|
Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Debug, "Loaded %d, size now %zu KB", index, _cacheSize / 1024);
|
2020-11-21 08:30:51 -08:00
|
|
|
#endif
|
|
|
|
|
2020-11-21 19:31:48 +00:00
|
|
|
return size;
|
2020-11-21 08:30:51 -08:00
|
|
|
}
|
|
|
|
|
2020-11-21 19:31:48 +00:00
|
|
|
void SpriteCache::RemapSpriteToSprite0(sprkey_t index) {
|
|
|
|
_sprInfos[index].Flags = _sprInfos[0].Flags;
|
|
|
|
_sprInfos[index].Width = _sprInfos[0].Width;
|
|
|
|
_sprInfos[index].Height = _sprInfos[0].Height;
|
|
|
|
_spriteData[index].Image = nullptr;
|
|
|
|
_spriteData[index].Size = _spriteData[0].Size;
|
|
|
|
_spriteData[index].Flags |= SPRCACHEFLAG_REMAPPED;
|
2020-11-21 08:30:51 -08:00
|
|
|
#ifdef DEBUG_SPRITECACHE
|
2020-11-21 19:31:48 +00:00
|
|
|
Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Debug, "RemapSpriteToSprite0: %d", index);
|
2020-11-21 08:30:51 -08:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *spriteFileSig = " Sprite File ";
|
|
|
|
|
2021-07-07 19:19:04 -07:00
|
|
|
int SpriteFile::SaveToFile(const String &save_to_file,
|
|
|
|
const std::vector<Bitmap *> &sprites,
|
|
|
|
SpriteFile *read_from_file,
|
|
|
|
bool compressOutput, SpriteFileIndex &index) {
|
|
|
|
std::unique_ptr<Stream> output(File::CreateFile(save_to_file));
|
2020-11-21 19:31:48 +00:00
|
|
|
if (output == nullptr)
|
|
|
|
return -1;
|
|
|
|
|
2020-11-22 17:57:29 -08:00
|
|
|
int spriteFileIDCheck = g_system->getMillis();
|
2020-11-21 19:31:48 +00:00
|
|
|
|
|
|
|
// sprite file version
|
|
|
|
output->WriteInt16(kSprfVersion_Current);
|
|
|
|
|
|
|
|
output->WriteArray(spriteFileSig, strlen(spriteFileSig), 1);
|
|
|
|
|
|
|
|
output->WriteInt8(compressOutput ? 1 : 0);
|
|
|
|
output->WriteInt32(spriteFileIDCheck);
|
|
|
|
|
2021-07-07 19:19:04 -07:00
|
|
|
sprkey_t lastslot = read_from_file ? read_from_file->GetTopmostSprite() : 0;
|
|
|
|
lastslot = std::max(lastslot, FindTopmostSprite(sprites));
|
2020-11-21 19:31:48 +00:00
|
|
|
output->WriteInt32(lastslot);
|
|
|
|
|
|
|
|
// allocate buffers to store the indexing info
|
|
|
|
sprkey_t numsprits = lastslot + 1;
|
2021-02-16 19:45:02 -08:00
|
|
|
std::vector<int16_t> spritewidths, spriteheights;
|
2020-11-21 19:31:48 +00:00
|
|
|
std::vector<soff_t> spriteoffs;
|
|
|
|
spritewidths.resize(numsprits);
|
|
|
|
spriteheights.resize(numsprits);
|
|
|
|
spriteoffs.resize(numsprits);
|
2021-07-07 19:19:04 -07:00
|
|
|
std::unique_ptr<Bitmap> temp_bmp; // for disposing temp sprites
|
|
|
|
std::vector<char> membuf; // for loading raw sprite data
|
2020-11-21 19:31:48 +00:00
|
|
|
|
2021-07-07 19:19:04 -07:00
|
|
|
const bool diff_compress =
|
|
|
|
read_from_file && read_from_file->IsFileCompressed() != compressOutput;
|
2020-11-21 19:31:48 +00:00
|
|
|
|
|
|
|
for (sprkey_t i = 0; i <= lastslot; ++i) {
|
2021-07-07 19:19:04 -07:00
|
|
|
soff_t sproff = output->GetPosition();
|
|
|
|
|
|
|
|
Bitmap *image = (size_t)i < sprites.size() ? sprites[i] : nullptr;
|
2020-11-21 19:31:48 +00:00
|
|
|
|
2021-07-07 19:19:04 -07:00
|
|
|
// if compression setting is different, load the sprite into memory
|
|
|
|
// (otherwise we will be able to simply copy bytes from one file to another
|
|
|
|
if ((image == nullptr) && diff_compress) {
|
|
|
|
read_from_file->LoadSprite(i, image);
|
|
|
|
temp_bmp.reset(image);
|
|
|
|
}
|
2020-11-21 19:31:48 +00:00
|
|
|
|
2021-07-07 19:19:04 -07:00
|
|
|
// if managed to load an image - save it according the new compression settings
|
|
|
|
if (image != nullptr) {
|
2020-11-21 19:31:48 +00:00
|
|
|
// image in memory -- write it out
|
2021-07-07 19:19:04 -07:00
|
|
|
int bpp = image->GetColorDepth() / 8;
|
|
|
|
spriteoffs[i] = sproff;
|
2020-11-21 19:31:48 +00:00
|
|
|
spritewidths[i] = image->GetWidth();
|
|
|
|
spriteheights[i] = image->GetHeight();
|
2021-07-07 19:19:04 -07:00
|
|
|
output->WriteInt16(bpp);
|
2020-11-21 19:31:48 +00:00
|
|
|
output->WriteInt16(spritewidths[i]);
|
|
|
|
output->WriteInt16(spriteheights[i]);
|
|
|
|
|
|
|
|
if (compressOutput) {
|
2021-05-25 07:52:55 -07:00
|
|
|
soff_t lenloc = output->GetPosition();
|
2020-11-21 19:31:48 +00:00
|
|
|
// write some space for the length data
|
|
|
|
output->WriteInt32(0);
|
|
|
|
|
2021-07-07 19:35:33 -07:00
|
|
|
rle_compress(image, output.get());
|
2020-11-21 19:31:48 +00:00
|
|
|
|
2021-05-25 07:52:55 -07:00
|
|
|
soff_t fileSizeSoFar = output->GetPosition();
|
2020-11-21 19:31:48 +00:00
|
|
|
// write the length of the compressed data
|
|
|
|
output->Seek(lenloc, kSeekBegin);
|
|
|
|
output->WriteInt32((fileSizeSoFar - lenloc) - 4);
|
|
|
|
output->Seek(0, kSeekEnd);
|
|
|
|
} else {
|
2021-07-07 19:19:04 -07:00
|
|
|
output->WriteArray(image->GetDataForWriting(), spritewidths[i] * bpp, spriteheights[i]);
|
2020-11-21 19:31:48 +00:00
|
|
|
}
|
|
|
|
continue;
|
2021-07-07 19:19:04 -07:00
|
|
|
} else if (diff_compress) {
|
2020-11-21 19:31:48 +00:00
|
|
|
// sprite doesn't exist
|
|
|
|
output->WriteInt16(0); // colour depth
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2021-07-07 19:19:04 -07:00
|
|
|
// Not in memory - and same compression option;
|
|
|
|
// Directly copy the sprite bytes from the input file to the output
|
|
|
|
Size metric;
|
|
|
|
int bpp;
|
|
|
|
read_from_file->LoadSpriteData(i, metric, bpp, membuf);
|
2020-11-21 19:31:48 +00:00
|
|
|
|
2021-07-07 19:19:04 -07:00
|
|
|
output->WriteInt16(bpp);
|
|
|
|
if (bpp == 0)
|
|
|
|
continue; // empty slot
|
2020-11-21 19:31:48 +00:00
|
|
|
|
2021-07-07 19:19:04 -07:00
|
|
|
spriteoffs[i] = sproff;
|
|
|
|
spritewidths[i] = metric.Width;
|
|
|
|
spriteheights[i] = metric.Height;
|
|
|
|
output->WriteInt16(metric.Width);
|
|
|
|
output->WriteInt16(metric.Height);
|
|
|
|
if (compressOutput)
|
|
|
|
output->WriteInt32(membuf.size());
|
|
|
|
if (membuf.size() == 0)
|
|
|
|
continue; // bad data?
|
|
|
|
output->Write(&membuf[0], membuf.size());
|
2020-11-21 19:31:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
index.SpriteFileIDCheck = spriteFileIDCheck;
|
|
|
|
index.LastSlot = lastslot;
|
|
|
|
index.SpriteCount = numsprits;
|
|
|
|
index.Widths = spritewidths;
|
|
|
|
index.Heights = spriteheights;
|
|
|
|
index.Offsets = spriteoffs;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-07-07 19:19:04 -07:00
|
|
|
int SpriteCache::SaveToFile(const String &filename, bool compressOutput, SpriteFileIndex &index) {
|
|
|
|
std::vector<Bitmap *> sprites;
|
|
|
|
for (const auto &data : _spriteData) {
|
|
|
|
// NOTE: this is a horrible hack:
|
|
|
|
// because Editor expects slightly different RGB order, it swaps colors
|
|
|
|
// when loading them (call to initialize_sprite), so here we basically
|
|
|
|
// unfix that fix to save the data in a way that engine will expect.
|
|
|
|
// TODO: perhaps adjust the editor to NOT need this?!
|
|
|
|
pre_save_sprite(data.Image);
|
|
|
|
sprites.push_back(data.Image);
|
|
|
|
}
|
|
|
|
return _file.SaveToFile(filename, sprites, &_file, compressOutput, index);
|
|
|
|
}
|
|
|
|
|
|
|
|
int SpriteFile::SaveSpriteIndex(const String &filename, const SpriteFileIndex &index) {
|
2020-11-21 19:31:48 +00:00
|
|
|
// write the sprite index file
|
|
|
|
Stream *out = File::CreateFile(filename);
|
|
|
|
if (!out)
|
|
|
|
return -1;
|
|
|
|
// write "SPRINDEX" id
|
|
|
|
out->WriteArray(spindexid, strlen(spindexid), 1);
|
|
|
|
// write version
|
|
|
|
out->WriteInt32(kSpridxfVersion_Current);
|
|
|
|
out->WriteInt32(index.SpriteFileIDCheck);
|
|
|
|
// write last sprite number and num sprites, to verify that
|
|
|
|
// it matches the spr file
|
|
|
|
out->WriteInt32(index.LastSlot);
|
|
|
|
out->WriteInt32(index.SpriteCount);
|
|
|
|
if (index.SpriteCount > 0) {
|
|
|
|
out->WriteArrayOfInt16(&index.Widths.front(), index.Widths.size());
|
|
|
|
out->WriteArrayOfInt16(&index.Heights.front(), index.Heights.size());
|
|
|
|
out->WriteArrayOfInt64(&index.Offsets.front(), index.Offsets.size());
|
|
|
|
}
|
|
|
|
delete out;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-06-06 19:28:59 -07:00
|
|
|
HError SpriteCache::InitFile(const String &filename, const String &sprindex_filename) {
|
2021-07-07 19:19:04 -07:00
|
|
|
std::vector<Size> metrics;
|
|
|
|
HError err = _file.OpenFile(filename, sprindex_filename, metrics);
|
|
|
|
if (!err)
|
|
|
|
return err;
|
2020-11-21 19:31:48 +00:00
|
|
|
|
2021-07-07 19:19:04 -07:00
|
|
|
// Initialize sprite infos
|
|
|
|
size_t newsize = metrics.size();
|
|
|
|
_sprInfos.resize(newsize);
|
|
|
|
_spriteData.resize(newsize);
|
|
|
|
_mrulist.resize(newsize);
|
|
|
|
_mrubacklink.resize(newsize);
|
|
|
|
for (size_t i = 0; i < metrics.size(); ++i) {
|
|
|
|
if (!metrics[i].IsNull()) {
|
|
|
|
// Existing sprite
|
|
|
|
_spriteData[i].Flags = SPRCACHEFLAG_ISASSET;
|
|
|
|
_spriteData[i].Image = nullptr;
|
|
|
|
get_new_size_for_sprite(i, metrics[i].Width, metrics[i].Height, _sprInfos[i].Width, _sprInfos[i].Height);
|
|
|
|
} else {
|
|
|
|
// Handle empty slot: remap to sprite 0
|
|
|
|
if (i > 0) // FIXME: optimize
|
|
|
|
InitNullSpriteParams(i);
|
|
|
|
}
|
2020-11-21 19:31:48 +00:00
|
|
|
}
|
2021-07-07 19:19:04 -07:00
|
|
|
return HError::None();
|
2020-11-21 19:31:48 +00:00
|
|
|
}
|
|
|
|
|
2021-07-07 19:19:04 -07:00
|
|
|
HError SpriteFile::RebuildSpriteIndex(Stream *in, sprkey_t topmost,
|
|
|
|
SpriteFileVersion vers, std::vector<Size> &metrics) {
|
2020-11-21 19:31:48 +00:00
|
|
|
for (sprkey_t i = 0; i <= topmost; ++i) {
|
|
|
|
_spriteData[i].Offset = in->GetPosition();
|
|
|
|
|
|
|
|
int coldep = in->ReadInt16();
|
|
|
|
|
|
|
|
if (coldep == 0) {
|
|
|
|
if (in->EOS())
|
|
|
|
break;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (in->EOS())
|
|
|
|
break;
|
|
|
|
|
|
|
|
if ((size_t)i >= _spriteData.size())
|
|
|
|
break;
|
|
|
|
|
|
|
|
int wdd = in->ReadInt16();
|
|
|
|
int htt = in->ReadInt16();
|
2021-07-07 19:19:04 -07:00
|
|
|
metrics[i].Width = wdd;
|
|
|
|
metrics[i].Height = htt;
|
2020-11-21 19:31:48 +00:00
|
|
|
|
|
|
|
size_t spriteDataSize;
|
|
|
|
if (vers == kSprfVersion_Compressed) {
|
|
|
|
spriteDataSize = in->ReadInt32();
|
|
|
|
} else if (vers >= kSprfVersion_Last32bit) {
|
|
|
|
spriteDataSize = this->_compressed ? in->ReadInt32() : wdd * coldep * htt;
|
|
|
|
} else {
|
|
|
|
spriteDataSize = wdd * coldep * htt;
|
|
|
|
}
|
|
|
|
in->Seek(spriteDataSize);
|
|
|
|
}
|
|
|
|
return HError::None();
|
|
|
|
}
|
|
|
|
|
2021-07-07 19:19:04 -07:00
|
|
|
bool SpriteFile::LoadSpriteIndexFile(const String &filename, int expectedFileID,
|
|
|
|
soff_t spr_initial_offs, sprkey_t topmost, std::vector<Size> &metrics) {
|
2021-05-25 07:52:55 -07:00
|
|
|
Stream *fidx = _GP(AssetMgr)->OpenAsset(filename);
|
2020-11-21 19:31:48 +00:00
|
|
|
if (fidx == nullptr) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
char buffer[9];
|
|
|
|
// check "SPRINDEX" id
|
|
|
|
fidx->ReadArray(&buffer[0], strlen(spindexid), 1);
|
|
|
|
buffer[8] = 0;
|
|
|
|
if (strcmp(buffer, spindexid)) {
|
|
|
|
delete fidx;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// check version
|
|
|
|
SpriteIndexFileVersion vers = (SpriteIndexFileVersion)fidx->ReadInt32();
|
|
|
|
if (vers < kSpridxfVersion_Initial || vers > kSpridxfVersion_Current) {
|
|
|
|
delete fidx;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (vers >= kSpridxfVersion_Last32bit) {
|
|
|
|
if (fidx->ReadInt32() != expectedFileID) {
|
|
|
|
delete fidx;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sprkey_t topmost_index = fidx->ReadInt32();
|
|
|
|
// end index+1 should be the same as num sprites
|
|
|
|
if (fidx->ReadInt32() != topmost_index + 1) {
|
|
|
|
delete fidx;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (topmost_index != topmost) {
|
|
|
|
delete fidx;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
sprkey_t numsprits = topmost_index + 1;
|
2021-07-07 19:19:04 -07:00
|
|
|
std::vector<int16_t> rspritewidths; rspritewidths.resize(numsprits);
|
|
|
|
std::vector<int16_t> rspriteheights; rspriteheights.resize(numsprits);
|
|
|
|
std::vector<soff_t> spriteoffs; spriteoffs.resize(numsprits);
|
2020-11-21 19:31:48 +00:00
|
|
|
|
|
|
|
fidx->ReadArrayOfInt16(&rspritewidths[0], numsprits);
|
|
|
|
fidx->ReadArrayOfInt16(&rspriteheights[0], numsprits);
|
|
|
|
if (vers <= kSpridxfVersion_Last32bit) {
|
|
|
|
for (sprkey_t i = 0; i < numsprits; ++i)
|
|
|
|
spriteoffs[i] = fidx->ReadInt32();
|
2021-07-07 19:19:04 -07:00
|
|
|
} else // large file support
|
|
|
|
{
|
|
|
|
fidx->ReadArrayOfInt64(&spriteoffs[0], numsprits);
|
2020-11-21 19:31:48 +00:00
|
|
|
}
|
2021-07-07 19:19:04 -07:00
|
|
|
delete fidx;
|
2020-11-21 19:31:48 +00:00
|
|
|
|
|
|
|
for (sprkey_t i = 0; i <= topmost_index; ++i) {
|
|
|
|
if (spriteoffs[i] != 0) {
|
|
|
|
_spriteData[i].Offset = spriteoffs[i] + spr_initial_offs;
|
2021-07-07 19:19:04 -07:00
|
|
|
metrics[i].Width = rspritewidths[i];
|
|
|
|
metrics[i].Height = rspriteheights[i];
|
2020-11-21 19:31:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void SpriteCache::DetachFile() {
|
2021-07-07 19:19:04 -07:00
|
|
|
_file.Reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool SpriteFile::IsFileCompressed() const {
|
|
|
|
return _compressed;
|
|
|
|
}
|
|
|
|
|
|
|
|
sprkey_t SpriteFile::GetTopmostSprite() const {
|
|
|
|
return (sprkey_t)_spriteData.size() - 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
void SpriteFile::Reset() {
|
2020-11-21 19:31:48 +00:00
|
|
|
_stream.reset();
|
2021-07-07 19:19:04 -07:00
|
|
|
_curPos = -2;
|
|
|
|
}
|
|
|
|
|
|
|
|
HAGSError SpriteFile::LoadSprite(sprkey_t index, Shared::Bitmap *&sprite) {
|
|
|
|
sprite = nullptr;
|
|
|
|
if (index < 0 || (size_t)index >= _spriteData.size())
|
|
|
|
new Error(String::FromFormat("LoadSprite: slot index %d out of bounds (%d - %d).",
|
|
|
|
index, 0, _spriteData.size() - 1));
|
|
|
|
|
2021-07-29 20:32:05 -07:00
|
|
|
if (_spriteData[index].Offset == 0)
|
|
|
|
return HError::None(); // sprite is not in file
|
|
|
|
|
2021-07-07 19:19:04 -07:00
|
|
|
SeekToSprite(index);
|
|
|
|
_curPos = -2; // mark undefined pos
|
|
|
|
|
|
|
|
int coldep = _stream->ReadInt16();
|
|
|
|
if (coldep == 0) { // empty slot, this is normal
|
|
|
|
return HError::None();
|
|
|
|
}
|
|
|
|
|
|
|
|
int wdd = _stream->ReadInt16();
|
|
|
|
int htt = _stream->ReadInt16();
|
|
|
|
Bitmap *image = BitmapHelper::CreateBitmap(wdd, htt, coldep * 8);
|
|
|
|
if (image == nullptr) {
|
|
|
|
return new Error(String::FromFormat("LoadSprite: failed to allocate bitmap %d (%dx%d%d).",
|
|
|
|
index, wdd, htt, coldep * 8));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_compressed) {
|
|
|
|
size_t data_size = _stream->ReadInt32();
|
|
|
|
if (data_size == 0) {
|
|
|
|
delete image;
|
|
|
|
return new Error(String::FromFormat("LoadSprite: bad compressed data for sprite %d.", index));
|
|
|
|
}
|
2021-07-07 19:35:33 -07:00
|
|
|
rle_decompress(image, _stream.get());
|
2021-07-07 19:19:04 -07:00
|
|
|
} else {
|
|
|
|
if (coldep == 1) {
|
|
|
|
for (int h = 0; h < htt; ++h)
|
|
|
|
_stream->ReadArray(&image->GetScanLineForWriting(h)[0], coldep, wdd);
|
|
|
|
} else if (coldep == 2) {
|
|
|
|
for (int h = 0; h < htt; ++h)
|
|
|
|
_stream->ReadArrayOfInt16((int16_t *)&image->GetScanLineForWriting(h)[0], wdd);
|
|
|
|
} else {
|
|
|
|
for (int h = 0; h < htt; ++h)
|
|
|
|
_stream->ReadArrayOfInt32((int32_t *)&image->GetScanLineForWriting(h)[0], wdd);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sprite = image;
|
|
|
|
_curPos = index + 1; // mark correct pos
|
|
|
|
return HError::None();
|
|
|
|
}
|
|
|
|
|
|
|
|
HError SpriteFile::LoadSpriteData(sprkey_t index, Size &metric, int &bpp,
|
2021-07-29 20:32:05 -07:00
|
|
|
std::vector<char> &data) {
|
|
|
|
metric = Size();
|
|
|
|
bpp = 0;
|
|
|
|
|
2021-07-07 19:19:04 -07:00
|
|
|
if (index < 0 || (size_t)index >= _spriteData.size())
|
|
|
|
new Error(String::FromFormat("LoadSprite: slot index %d out of bounds (%d - %d).",
|
|
|
|
index, 0, _spriteData.size() - 1));
|
|
|
|
|
2021-07-29 20:32:05 -07:00
|
|
|
if (_spriteData[index].Offset == 0)
|
|
|
|
return HError::None(); // sprite is not in file
|
|
|
|
|
2021-07-07 19:19:04 -07:00
|
|
|
SeekToSprite(index);
|
|
|
|
_curPos = -2; // mark undefined pos
|
|
|
|
|
|
|
|
int coldep = _stream->ReadInt16();
|
|
|
|
if (coldep == 0) { // empty slot, this is normal
|
|
|
|
metric = Size();
|
|
|
|
bpp = 0;
|
|
|
|
data.resize(0);
|
|
|
|
return HError::None();
|
|
|
|
}
|
|
|
|
|
|
|
|
int width = _stream->ReadInt16();
|
|
|
|
int height = _stream->ReadInt16();
|
|
|
|
|
|
|
|
size_t data_size;
|
|
|
|
if (_compressed)
|
|
|
|
data_size = _stream->ReadInt32();
|
|
|
|
else
|
|
|
|
data_size = width * height * coldep;
|
|
|
|
data.resize(data_size);
|
|
|
|
_stream->Read(&data[0], data_size);
|
|
|
|
metric = Size(width, height);
|
|
|
|
bpp = coldep;
|
|
|
|
_curPos = index + 1; // mark correct pos
|
|
|
|
return HError::None();
|
2020-11-21 19:31:48 +00:00
|
|
|
}
|
|
|
|
|
2021-07-07 19:19:04 -07:00
|
|
|
HAGSError SpriteFile::OpenFile(const String &filename, const String &sprindex_filename,
|
|
|
|
std::vector<Size> &metrics) {
|
|
|
|
SpriteFileVersion vers;
|
|
|
|
char buff[20];
|
|
|
|
soff_t spr_initial_offs = 0;
|
|
|
|
int spriteFileID = 0;
|
|
|
|
|
2021-05-25 07:52:55 -07:00
|
|
|
_stream.reset(_GP(AssetMgr)->OpenAsset(filename));
|
2020-11-21 19:31:48 +00:00
|
|
|
if (_stream == nullptr)
|
2021-07-07 19:19:04 -07:00
|
|
|
return new Error(String::FromFormat("Failed to open spriteset file '%s'.", filename.GetCStr()));
|
2020-11-21 19:31:48 +00:00
|
|
|
|
2021-07-07 19:19:04 -07:00
|
|
|
spr_initial_offs = _stream->GetPosition();
|
|
|
|
|
|
|
|
vers = (SpriteFileVersion)_stream->ReadInt16();
|
|
|
|
// read the "Sprite File" signature
|
|
|
|
_stream->ReadArray(&buff[0], 13, 1);
|
|
|
|
|
|
|
|
if (vers < kSprfVersion_Uncompressed || vers > kSprfVersion_Current) {
|
|
|
|
_stream.reset();
|
|
|
|
return new Error(String::FromFormat("Unsupported spriteset format (requested %d, supported %d - %d).", vers, kSprfVersion_Uncompressed, kSprfVersion_Current));
|
|
|
|
}
|
|
|
|
|
|
|
|
// unknown version
|
|
|
|
buff[13] = 0;
|
|
|
|
if (strcmp(buff, spriteFileSig)) {
|
|
|
|
_stream.reset();
|
2021-10-31 15:10:19 +01:00
|
|
|
return new Error("Unknown spriteset format.");
|
2021-07-07 19:19:04 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (vers == kSprfVersion_Uncompressed) {
|
|
|
|
this->_compressed = false;
|
|
|
|
} else if (vers == kSprfVersion_Compressed) {
|
|
|
|
this->_compressed = true;
|
|
|
|
} else if (vers >= kSprfVersion_Last32bit) {
|
|
|
|
this->_compressed = (_stream->ReadInt8() == 1);
|
|
|
|
spriteFileID = _stream->ReadInt32();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (vers < kSprfVersion_Compressed) {
|
|
|
|
// skip the palette
|
|
|
|
_stream->Seek(256 * 3); // sizeof(RGB) * 256
|
|
|
|
}
|
|
|
|
|
|
|
|
sprkey_t topmost;
|
|
|
|
if (vers < kSprfVersion_HighSpriteLimit)
|
|
|
|
topmost = (uint16_t)_stream->ReadInt16();
|
|
|
|
else
|
|
|
|
topmost = _stream->ReadInt32();
|
|
|
|
if (vers < kSprfVersion_Uncompressed)
|
|
|
|
topmost = 200;
|
|
|
|
|
|
|
|
_spriteData.resize(topmost + 1);
|
|
|
|
metrics.resize(topmost + 1);
|
|
|
|
|
|
|
|
// if there is a sprite index file, use it
|
|
|
|
if (LoadSpriteIndexFile(sprindex_filename, spriteFileID,
|
|
|
|
spr_initial_offs, topmost, metrics)) {
|
|
|
|
// Succeeded
|
|
|
|
return HError::None();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Failed, index file is invalid; index sprites manually
|
|
|
|
return RebuildSpriteIndex(_stream.get(), topmost, vers, metrics);
|
2020-11-21 08:30:51 -08:00
|
|
|
}
|
2020-11-21 16:24:18 -08:00
|
|
|
|
|
|
|
} // namespace AGS3
|