AGS: Moved SpriteFile class to a separate code unit for convenience

From upstream 393ef2c24bbc835d8e7a7229d40f638a6f172304
This commit is contained in:
Paul Gilbert 2022-03-16 19:52:02 -07:00
parent b20f2e34c2
commit ea65fe3c69
8 changed files with 617 additions and 540 deletions

View File

@ -25,13 +25,6 @@
//
//=============================================================================
// Headers, as they are in sprcache.cpp
#ifdef _MANAGED
// ensure this doesn't get compiled to .NET IL
#pragma unmanaged
#pragma warning (disable: 4996 4312) // disable deprecation warnings
#endif
#include "ags/shared/ac/game_struct_defines.h"
#include "ags/shared/ac/sprite_cache.h"
#include "ags/shared/util/compress.h"
@ -42,7 +35,7 @@ namespace AGS3 {
// Engine-specific implementation split out of sprcache.cpp
//=============================================================================
void SpriteCache::InitNullSpriteParams(sprkey_t index) {
void AGS::Shared::SpriteCache::InitNullSpriteParams(sprkey_t index) {
// make it a blue cup, to avoid crashes
_sprInfos[index].Width = _sprInfos[0].Width;
_sprInfos[index].Height = _sprInfos[0].Height;

View File

@ -220,7 +220,7 @@ Globals::Globals() {
_guis = new std::vector<AGS::Shared::GUIMain>();
_play = new GameState();
_game = new GameSetupStruct();
_spriteset = new SpriteCache(_game->SpriteInfos);
_spriteset = new AGS::Shared::SpriteCache(_game->SpriteInfos);
_thisroom = new AGS::Shared::RoomStruct();
_troom = new RoomStatus();
_usetup = new GameSetup();

View File

@ -75,6 +75,7 @@ class GUITextBox;
struct InteractionVariable;
struct PlaneScaling;
class RoomStruct;
class SpriteCache;
struct Translation;
} // namespace Shared
@ -100,7 +101,6 @@ class EngineExports;
class Navigation;
class SplitLines;
class SpriteCache;
class TTFFontRenderer;
class WFNFontRenderer;
@ -709,7 +709,7 @@ public:
GameSetupStruct *_game;
GameState *_play;
SpriteCache *_spriteset;
AGS::Shared::SpriteCache *_spriteset;
AGS::Shared::RoomStruct *_thisroom;
RoomStatus *_troom; // used for non-saveable rooms, eg. intro

View File

@ -39,6 +39,7 @@ MODULE_OBJS = \
shared/ac/keycode.o \
shared/ac/mouse_cursor.o \
shared/ac/sprite_cache.o \
shared/ac/sprite_file.o \
shared/ac/view.o \
shared/ac/words_dictionary.o \
shared/core/asset.o \

View File

@ -25,23 +25,14 @@
//
//=============================================================================
#ifdef _MANAGED
// ensure this doesn't get compiled to .NET IL
#pragma unmanaged
#pragma warning (disable: 4996 4312) // disable deprecation warnings
#endif
#include "common/system.h"
#include "ags/shared/util/stream.h"
#include "ags/lib/std/algorithm.h"
#include "ags/shared/ac/sprite_cache.h"
#include "ags/shared/ac/common.h" // quit
#include "ags/shared/ac/game_struct_defines.h"
#include "ags/shared/ac/sprite_cache.h"
#include "ags/shared/core/asset_manager.h"
#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"
#include "ags/globals.h"
namespace AGS3 {
@ -56,18 +47,15 @@ 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?
const char *const SpriteFile::DefaultSpriteFileName = "acsprset.spr";
const char *const SpriteFile::DefaultSpriteIndexName = "sprindex.dat";
SpriteInfo::SpriteInfo()
: Flags(0)
, Width(0)
, Height(0) {
}
namespace AGS {
namespace Shared {
SpriteCache::SpriteData::SpriteData()
: Size(0)
, Flags(0)
@ -79,12 +67,6 @@ SpriteCache::SpriteData::~SpriteData() {
// (some of these bitmaps may be assigned from outside of the cache)
}
SpriteFile::SpriteFile() {
_compressed = false;
_curPos = -2;
}
SpriteCache::SpriteCache(std::vector<SpriteInfo> &sprInfos)
: _sprInfos(sprInfos) {
Init();
@ -110,14 +92,6 @@ size_t SpriteCache::GetSpriteSlotCount() const {
return _spriteData.size();
}
sprkey_t SpriteFile::FindTopmostSprite(const std::vector<Bitmap *> &sprites) {
sprkey_t topmost = -1;
for (sprkey_t i = 0; i < static_cast<sprkey_t>(sprites.size()); ++i)
if (sprites[i])
topmost = i;
return topmost;
}
void SpriteCache::SetMaxCacheSize(size_t size) {
_maxCacheSize = size;
}
@ -379,14 +353,6 @@ sprkey_t SpriteCache::GetDataIndex(sprkey_t index) {
return (_spriteData[index].Flags & SPRCACHEFLAG_REMAPPED) == 0 ? index : 0;
}
void SpriteFile::SeekToSprite(sprkey_t index) {
// If we didn't just load the previous sprite, seek to it
if (index != _curPos) {
_stream->Seek(_spriteData[index].Offset, kSeekBegin);
_curPos = index;
}
}
size_t SpriteCache::LoadSprite(sprkey_t index) {
int hh = 0;
@ -457,119 +423,6 @@ void SpriteCache::RemapSpriteToSprite0(sprkey_t index) {
#endif
}
const char *spriteFileSig = " Sprite File ";
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));
if (output == nullptr)
return -1;
int spriteFileIDCheck = g_system->getMillis();
// sprite file version
output->WriteInt16(kSprfVersion_Current);
output->WriteArray(spriteFileSig, strlen(spriteFileSig), 1);
output->WriteInt8(compressOutput ? 1 : 0);
output->WriteInt32(spriteFileIDCheck);
sprkey_t lastslot = read_from_file ? read_from_file->GetTopmostSprite() : 0;
lastslot = std::max(lastslot, FindTopmostSprite(sprites));
output->WriteInt32(lastslot);
// allocate buffers to store the indexing info
sprkey_t numsprits = lastslot + 1;
std::vector<int16_t> spritewidths, spriteheights;
std::vector<soff_t> spriteoffs;
spritewidths.resize(numsprits);
spriteheights.resize(numsprits);
spriteoffs.resize(numsprits);
std::unique_ptr<Bitmap> temp_bmp; // for disposing temp sprites
std::vector<char> membuf; // for loading raw sprite data
const bool diff_compress =
read_from_file && read_from_file->IsFileCompressed() != compressOutput;
for (sprkey_t i = 0; i <= lastslot; ++i) {
soff_t sproff = output->GetPosition();
Bitmap *image = (size_t)i < sprites.size() ? sprites[i] : nullptr;
// 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);
}
// if managed to load an image - save it according the new compression settings
if (image != nullptr) {
// image in memory -- write it out
int bpp = image->GetColorDepth() / 8;
spriteoffs[i] = sproff;
spritewidths[i] = image->GetWidth();
spriteheights[i] = image->GetHeight();
output->WriteInt16(bpp);
output->WriteInt16(spritewidths[i]);
output->WriteInt16(spriteheights[i]);
if (compressOutput) {
soff_t lenloc = output->GetPosition();
// write some space for the length data
output->WriteInt32(0);
rle_compress(image, output.get());
soff_t fileSizeSoFar = output->GetPosition();
// write the length of the compressed data
output->Seek(lenloc, kSeekBegin);
output->WriteInt32((fileSizeSoFar - lenloc) - 4);
output->Seek(0, kSeekEnd);
} else {
output->WriteArray(image->GetDataForWriting(), spritewidths[i] * bpp, spriteheights[i]);
}
continue;
} else if (diff_compress) {
// sprite doesn't exist
output->WriteInt16(0); // colour depth
continue;
}
// 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);
output->WriteInt16(bpp);
if (bpp == 0)
continue; // empty slot
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());
}
index.SpriteFileIDCheck = spriteFileIDCheck;
index.LastSlot = lastslot;
index.SpriteCount = numsprits;
index.Widths = spritewidths;
index.Heights = spriteheights;
index.Offsets = spriteoffs;
return 0;
}
int SpriteCache::SaveToFile(const String &filename, bool compressOutput, SpriteFileIndex &index) {
std::vector<Bitmap *> sprites;
for (const auto &data : _spriteData) {
@ -584,29 +437,6 @@ int SpriteCache::SaveToFile(const String &filename, bool compressOutput, SpriteF
return _file.SaveToFile(filename, sprites, &_file, compressOutput, index);
}
int SpriteFile::SaveSpriteIndex(const String &filename, const SpriteFileIndex &index) {
// 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;
}
HError SpriteCache::InitFile(const String &filename, const String &sprindex_filename) {
std::vector<Size> metrics;
HError err = _file.OpenFile(filename, sprindex_filename, metrics);
@ -634,277 +464,10 @@ HError SpriteCache::InitFile(const String &filename, const String &sprindex_file
return HError::None();
}
HError SpriteFile::RebuildSpriteIndex(Stream *in, sprkey_t topmost,
SpriteFileVersion vers, std::vector<Size> &metrics) {
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();
metrics[i].Width = wdd;
metrics[i].Height = htt;
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();
}
bool SpriteFile::LoadSpriteIndexFile(const String &filename, int expectedFileID,
soff_t spr_initial_offs, sprkey_t topmost, std::vector<Size> &metrics) {
Stream *fidx = _GP(AssetMgr)->OpenAsset(filename);
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;
std::vector<int16_t> rspritewidths; rspritewidths.resize(numsprits);
std::vector<int16_t> rspriteheights; rspriteheights.resize(numsprits);
std::vector<soff_t> spriteoffs; spriteoffs.resize(numsprits);
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();
} else // large file support
{
fidx->ReadArrayOfInt64(&spriteoffs[0], numsprits);
}
delete fidx;
for (sprkey_t i = 0; i <= topmost_index; ++i) {
if (spriteoffs[i] != 0) {
_spriteData[i].Offset = spriteoffs[i] + spr_initial_offs;
metrics[i].Width = rspritewidths[i];
metrics[i].Height = rspriteheights[i];
}
}
return true;
}
void SpriteCache::DetachFile() {
_file.Reset();
}
bool SpriteFile::IsFileCompressed() const {
return _compressed;
}
sprkey_t SpriteFile::GetTopmostSprite() const {
return (sprkey_t)_spriteData.size() - 1;
}
void SpriteFile::Reset() {
_stream.reset();
_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));
if (_spriteData[index].Offset == 0)
return HError::None(); // sprite is not in file
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));
}
rle_decompress(image, _stream.get());
} 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,
std::vector<char> &data) {
metric = Size();
bpp = 0;
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));
if (_spriteData[index].Offset == 0)
return HError::None(); // sprite is not in file
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();
}
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;
_stream.reset(_GP(AssetMgr)->OpenAsset(filename));
if (_stream == nullptr)
return new Error(String::FromFormat("Failed to open spriteset file '%s'.", filename.GetCStr()));
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();
return new Error("Unknown spriteset format.");
}
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);
}
} // namespace Shared
} // namespace AGS
} // namespace AGS3

View File

@ -43,6 +43,7 @@
#include "ags/lib/std/memory.h"
#include "ags/lib/std/vector.h"
#include "ags/shared/ac/sprite_file.h"
#include "ags/shared/core/platform.h"
#include "ags/shared/util/error.h"
#include "ags/shared/util/geometry.h"
@ -76,91 +77,10 @@ struct SpriteInfo;
#define DEFAULTCACHESIZE_KB (128 * 1024)
#endif
// TODO: research old version differences
enum SpriteFileVersion {
kSprfVersion_Uncompressed = 4,
kSprfVersion_Compressed = 5,
kSprfVersion_Last32bit = 6,
kSprfVersion_64bit = 10,
kSprfVersion_HighSpriteLimit = 11,
kSprfVersion_Current = kSprfVersion_HighSpriteLimit
};
struct SpriteInfo;
enum SpriteIndexFileVersion {
kSpridxfVersion_Initial = 1,
kSpridxfVersion_Last32bit = 2,
kSpridxfVersion_64bit = 10,
kSpridxfVersion_HighSpriteLimit = 11,
kSpridxfVersion_Current = kSpridxfVersion_HighSpriteLimit
};
typedef int32_t sprkey_t;
// SpriteFileIndex contains sprite file's table of contents
struct SpriteFileIndex {
int SpriteFileIDCheck = 0; // tag matching sprite file and index file
sprkey_t LastSlot = -1;
size_t SpriteCount = 0u;
std::vector<int16_t> Widths;
std::vector<int16_t> Heights;
std::vector<soff_t> Offsets;
};
class SpriteFile {
public:
// Standart sprite file and sprite index names
static const char *const DefaultSpriteFileName;
static const char *const DefaultSpriteIndexName;
SpriteFile();
// Loads sprite reference information and inits sprite stream
HAGSError OpenFile(const Shared::String &filename, const Shared::String &sprindex_filename,
std::vector<Size> &metrics);
void Reset();
// Tells if bitmaps in the file are compressed
bool IsFileCompressed() const;
// Tells the highest known sprite index
sprkey_t GetTopmostSprite() const;
// Loads sprite index file
bool LoadSpriteIndexFile(const Shared::String &filename, int expectedFileID,
soff_t spr_initial_offs, sprkey_t topmost, std::vector<Size> &metrics);
// Rebuilds sprite index from the main sprite file
HAGSError RebuildSpriteIndex(AGS::Shared::Stream *in, sprkey_t topmost, SpriteFileVersion vers,
std::vector<Size> &metrics);
HAGSError LoadSprite(sprkey_t index, Shared::Bitmap *&sprite);
HAGSError LoadSpriteData(sprkey_t index, Size &metric, int &bpp, std::vector<char> &data);
// Saves all sprites to file; fills in index data for external use
// TODO: refactor to be able to save main file and index file separately (separate function for gather data?)
static int SaveToFile(const Shared::String &save_to_file,
const std::vector<Shared::Bitmap *> &sprites, // available sprites (may contain nullptrs)
SpriteFile *read_from_file, // optional file to read missing sprites from
bool compressOutput, SpriteFileIndex &index);
// Saves sprite index table in a separate file
static int SaveSpriteIndex(const Shared::String &filename, const SpriteFileIndex &index);
private:
// Finds the topmost occupied slot index. Warning: may be slow.
static sprkey_t FindTopmostSprite(const std::vector<Shared::Bitmap *> &sprites);
// Seek stream to sprite
void SeekToSprite(sprkey_t index);
// Internal sprite reference
struct SpriteRef {
soff_t Offset = 0; // data offset
size_t Size = 0; // cache size of element, in bytes
};
// Array of sprite references
std::vector<SpriteRef> _spriteData;
std::unique_ptr<Shared::Stream> _stream; // the sprite stream
bool _compressed; // are sprites compressed
sprkey_t _curPos; // current stream position (sprite slot)
};
namespace AGS {
namespace Shared {
class SpriteCache {
public:
@ -172,7 +92,7 @@ public:
~SpriteCache();
// Loads sprite reference information and inits sprite stream
HAGSError InitFile(const Shared::String &filename, const Shared::String &sprindex_filename);
HError InitFile(const Shared::String &filename, const Shared::String &sprindex_filename);
// Saves current cache contents to the file
int SaveToFile(const Shared::String &filename, bool compressOutput, SpriteFileIndex &index);
// Closes an active sprite file stream
@ -277,6 +197,8 @@ private:
void InitNullSpriteParams(sprkey_t index);
};
} // namespace Shared
} // namespace AGS
} // namespace AGS3
#endif

View File

@ -0,0 +1,467 @@
/* 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 "ags/shared/ac/sprite_file.h"
#include "ags/lib/std/algorithm.h"
#include "ags/shared/core/asset_manager.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"
namespace AGS3 {
namespace AGS {
namespace Shared {
static const char *spriteFileSig = " Sprite File ";
static const char *spindexid = "SPRINDEX";
// TODO: should not be part of SpriteFile, but rather some asset management class?
const char *SpriteFile::DefaultSpriteFileName = "acsprset.spr";
const char *SpriteFile::DefaultSpriteIndexName = "sprindex.dat";
SpriteFile::SpriteFile() {
_compressed = false;
_curPos = -2;
}
HError 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;
_stream.reset(_GP(AssetMgr)->OpenAsset(filename));
if (_stream == nullptr)
return new Error(String::FromFormat("Failed to open spriteset file '%s'.", filename.GetCStr()));
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();
return new Error("Uknown spriteset format.");
}
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);
}
void SpriteFile::Reset() {
_stream.reset();
_curPos = -2;
}
bool SpriteFile::IsFileCompressed() const {
return _compressed;
}
sprkey_t SpriteFile::GetTopmostSprite() const {
return (sprkey_t)_spriteData.size() - 1;
}
bool SpriteFile::LoadSpriteIndexFile(const String &filename, int expectedFileID,
soff_t spr_initial_offs, sprkey_t topmost, std::vector<Size> &metrics) {
Stream *fidx = _GP(AssetMgr)->OpenAsset(filename);
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;
std::vector<int16_t> rspritewidths; rspritewidths.resize(numsprits);
std::vector<int16_t> rspriteheights; rspriteheights.resize(numsprits);
std::vector<soff_t> spriteoffs; spriteoffs.resize(numsprits);
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();
} else // large file support
{
fidx->ReadArrayOfInt64(&spriteoffs[0], numsprits);
}
delete fidx;
for (sprkey_t i = 0; i <= topmost_index; ++i) {
if (spriteoffs[i] != 0) {
_spriteData[i].Offset = spriteoffs[i] + spr_initial_offs;
metrics[i].Width = rspritewidths[i];
metrics[i].Height = rspriteheights[i];
}
}
return true;
}
HError SpriteFile::RebuildSpriteIndex(Stream *in, sprkey_t topmost,
SpriteFileVersion vers, std::vector<Size> &metrics) {
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();
metrics[i].Width = wdd;
metrics[i].Height = htt;
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();
}
HError 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));
if (_spriteData[index].Offset == 0)
return HError::None(); // sprite is not in file
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));
}
rle_decompress(image, _stream.get());
} 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,
std::vector<char> &data) {
metric = Size();
bpp = 0;
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));
if (_spriteData[index].Offset == 0)
return HError::None(); // sprite is not in file
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();
}
sprkey_t SpriteFile::FindTopmostSprite(const std::vector<Bitmap *> &sprites) {
sprkey_t topmost = -1;
for (sprkey_t i = 0; i < static_cast<sprkey_t>(sprites.size()); ++i)
if (sprites[i])
topmost = i;
return topmost;
}
void SpriteFile::SeekToSprite(sprkey_t index) {
// If we didn't just load the previous sprite, seek to it
if (index != _curPos) {
_stream->Seek(_spriteData[index].Offset, kSeekBegin);
_curPos = index;
}
}
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));
if (output == nullptr)
return -1;
int spriteFileIDCheck = g_system->getMillis();
// sprite file version
output->WriteInt16(kSprfVersion_Current);
output->WriteArray(spriteFileSig, strlen(spriteFileSig), 1);
output->WriteInt8(compressOutput ? 1 : 0);
output->WriteInt32(spriteFileIDCheck);
sprkey_t lastslot = read_from_file ? read_from_file->GetTopmostSprite() : 0;
lastslot = std::max(lastslot, FindTopmostSprite(sprites));
output->WriteInt32(lastslot);
// allocate buffers to store the indexing info
sprkey_t numsprits = lastslot + 1;
std::vector<int16_t> spritewidths, spriteheights;
std::vector<soff_t> spriteoffs;
spritewidths.resize(numsprits);
spriteheights.resize(numsprits);
spriteoffs.resize(numsprits);
std::unique_ptr<Bitmap> temp_bmp; // for disposing temp sprites
std::vector<char> membuf; // for loading raw sprite data
const bool diff_compress =
read_from_file && read_from_file->IsFileCompressed() != compressOutput;
for (sprkey_t i = 0; i <= lastslot; ++i) {
soff_t sproff = output->GetPosition();
Bitmap *image = (size_t)i < sprites.size() ? sprites[i] : nullptr;
// 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);
}
// if managed to load an image - save it according the new compression settings
if (image != nullptr) {
// image in memory -- write it out
int bpp = image->GetColorDepth() / 8;
spriteoffs[i] = sproff;
spritewidths[i] = image->GetWidth();
spriteheights[i] = image->GetHeight();
output->WriteInt16(bpp);
output->WriteInt16(spritewidths[i]);
output->WriteInt16(spriteheights[i]);
if (compressOutput) {
soff_t lenloc = output->GetPosition();
// write some space for the length data
output->WriteInt32(0);
rle_compress(image, output.get());
soff_t fileSizeSoFar = output->GetPosition();
// write the length of the compressed data
output->Seek(lenloc, kSeekBegin);
output->WriteInt32((fileSizeSoFar - lenloc) - 4);
output->Seek(0, kSeekEnd);
} else {
output->WriteArray(image->GetDataForWriting(), spritewidths[i] * bpp, spriteheights[i]);
}
continue;
} else if (diff_compress) {
// sprite doesn't exist
output->WriteInt16(0); // colour depth
continue;
}
// 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);
output->WriteInt16(bpp);
if (bpp == 0)
continue; // empty slot
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());
}
index.SpriteFileIDCheck = spriteFileIDCheck;
index.LastSlot = lastslot;
index.SpriteCount = numsprits;
index.Widths = spritewidths;
index.Heights = spriteheights;
index.Offsets = spriteoffs;
return 0;
}
int SpriteFile::SaveSpriteIndex(const String &filename, const SpriteFileIndex &index) {
// 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;
}
} // namespace Shared
} // namespace AGS
} // namespace AGS3

View File

@ -0,0 +1,131 @@
/* 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/>.
*
*/
//=============================================================================
//
// SpriteFile class handles sprite file loading and streaming.
//
//=============================================================================
#ifndef AGS_SHARED_AC_SPRITE_FILE_H
#define AGS_SHARED_AC_SPRITE_FILE_H
#include "ags/shared/core/types.h"
#include "ags/lib/std/vector.h"
#include "ags/globals.h"
namespace AGS3 {
namespace AGS {
namespace Shared {
class Bitmap;
// TODO: research old version differences
enum SpriteFileVersion {
kSprfVersion_Uncompressed = 4,
kSprfVersion_Compressed = 5,
kSprfVersion_Last32bit = 6,
kSprfVersion_64bit = 10,
kSprfVersion_HighSpriteLimit = 11,
kSprfVersion_Current = kSprfVersion_HighSpriteLimit
};
enum SpriteIndexFileVersion {
kSpridxfVersion_Initial = 1,
kSpridxfVersion_Last32bit = 2,
kSpridxfVersion_64bit = 10,
kSpridxfVersion_HighSpriteLimit = 11,
kSpridxfVersion_Current = kSpridxfVersion_HighSpriteLimit
};
typedef int32_t sprkey_t;
// SpriteFileIndex contains sprite file's table of contents
struct SpriteFileIndex {
int SpriteFileIDCheck = 0; // tag matching sprite file and index file
sprkey_t LastSlot = -1;
size_t SpriteCount = 0u;
std::vector<int16_t> Widths;
std::vector<int16_t> Heights;
std::vector<soff_t> Offsets;
};
class SpriteFile {
public:
// Standart sprite file and sprite index names
static const char *DefaultSpriteFileName;
static const char *DefaultSpriteIndexName;
SpriteFile();
// Loads sprite reference information and inits sprite stream
HError OpenFile(const String &filename, const String &sprindex_filename,
std::vector<Size> &metrics);
void Reset();
// Tells if bitmaps in the file are compressed
bool IsFileCompressed() const;
// Tells the highest known sprite index
sprkey_t GetTopmostSprite() const;
// Loads sprite index file
bool LoadSpriteIndexFile(const String &filename, int expectedFileID,
soff_t spr_initial_offs, sprkey_t topmost, std::vector<Size> &metrics);
// Rebuilds sprite index from the main sprite file
HError RebuildSpriteIndex(Stream *in, sprkey_t topmost, SpriteFileVersion vers,
std::vector<Size> &metrics);
HError LoadSprite(sprkey_t index, Bitmap *&sprite);
HError LoadSpriteData(sprkey_t index, Size &metric, int &bpp, std::vector<char> &data);
// Saves all sprites to file; fills in index data for external use
// TODO: refactor to be able to save main file and index file separately (separate function for gather data?)
static int SaveToFile(const String &save_to_file,
const std::vector<Bitmap *> &sprites, // available sprites (may contain nullptrs)
SpriteFile *read_from_file, // optional file to read missing sprites from
bool compressOutput, SpriteFileIndex &index);
// Saves sprite index table in a separate file
static int SaveSpriteIndex(const String &filename, const SpriteFileIndex &index);
private:
// Finds the topmost occupied slot index. Warning: may be slow.
static sprkey_t FindTopmostSprite(const std::vector<Bitmap *> &sprites);
// Seek stream to sprite
void SeekToSprite(sprkey_t index);
// Internal sprite reference
struct SpriteRef {
soff_t Offset = 0; // data offset
size_t Size = 0; // cache size of element, in bytes
};
// Array of sprite references
std::vector<SpriteRef> _spriteData;
std::unique_ptr<Stream> _stream; // the sprite stream
bool _compressed; // are sprites compressed
sprkey_t _curPos; // current stream position (sprite slot)
};
} // namespace Shared
} // namespace AGS
} // namespace AGS3
#endif