diff --git a/engines/ags/engine/ac/sprite_cache_engine.cpp b/engines/ags/engine/ac/sprite_cache_engine.cpp index 31a0a9e68c5..fdcefb8e871 100644 --- a/engines/ags/engine/ac/sprite_cache_engine.cpp +++ b/engines/ags/engine/ac/sprite_cache_engine.cpp @@ -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; diff --git a/engines/ags/globals.cpp b/engines/ags/globals.cpp index 2e42791721d..69b5b78ac9a 100644 --- a/engines/ags/globals.cpp +++ b/engines/ags/globals.cpp @@ -220,7 +220,7 @@ Globals::Globals() { _guis = new std::vector(); _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(); diff --git a/engines/ags/globals.h b/engines/ags/globals.h index 15cde98d733..f15e523ff27 100644 --- a/engines/ags/globals.h +++ b/engines/ags/globals.h @@ -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 diff --git a/engines/ags/module.mk b/engines/ags/module.mk index dab643747ff..257df04c895 100644 --- a/engines/ags/module.mk +++ b/engines/ags/module.mk @@ -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 \ diff --git a/engines/ags/shared/ac/sprite_cache.cpp b/engines/ags/shared/ac/sprite_cache.cpp index 59440303950..60b6b145f26 100644 --- a/engines/ags/shared/ac/sprite_cache.cpp +++ b/engines/ags/shared/ac/sprite_cache.cpp @@ -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 &sprInfos) : _sprInfos(sprInfos) { Init(); @@ -110,14 +92,6 @@ size_t SpriteCache::GetSpriteSlotCount() const { return _spriteData.size(); } -sprkey_t SpriteFile::FindTopmostSprite(const std::vector &sprites) { - sprkey_t topmost = -1; - for (sprkey_t i = 0; i < static_cast(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 &sprites, - SpriteFile *read_from_file, - bool compressOutput, SpriteFileIndex &index) { - std::unique_ptr 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 spritewidths, spriteheights; - std::vector spriteoffs; - spritewidths.resize(numsprits); - spriteheights.resize(numsprits); - spriteoffs.resize(numsprits); - std::unique_ptr temp_bmp; // for disposing temp sprites - std::vector 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 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 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 &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 &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 rspritewidths; rspritewidths.resize(numsprits); - std::vector rspriteheights; rspriteheights.resize(numsprits); - std::vector 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 &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 &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 diff --git a/engines/ags/shared/ac/sprite_cache.h b/engines/ags/shared/ac/sprite_cache.h index 2b19e88edc1..fb830b963e2 100644 --- a/engines/ags/shared/ac/sprite_cache.h +++ b/engines/ags/shared/ac/sprite_cache.h @@ -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 Widths; - std::vector Heights; - std::vector 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 &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 &metrics); - // Rebuilds sprite index from the main sprite file - HAGSError RebuildSpriteIndex(AGS::Shared::Stream *in, sprkey_t topmost, SpriteFileVersion vers, - std::vector &metrics); - - HAGSError LoadSprite(sprkey_t index, Shared::Bitmap *&sprite); - HAGSError LoadSpriteData(sprkey_t index, Size &metric, int &bpp, std::vector &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 &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 &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 _spriteData; - std::unique_ptr _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 diff --git a/engines/ags/shared/ac/sprite_file.cpp b/engines/ags/shared/ac/sprite_file.cpp new file mode 100644 index 00000000000..aef315b54ec --- /dev/null +++ b/engines/ags/shared/ac/sprite_file.cpp @@ -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 . + * + */ + +#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 &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 &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 rspritewidths; rspritewidths.resize(numsprits); + std::vector rspriteheights; rspriteheights.resize(numsprits); + std::vector 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 &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 &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 &sprites) { + sprkey_t topmost = -1; + for (sprkey_t i = 0; i < static_cast(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 &sprites, + SpriteFile *read_from_file, + bool compressOutput, SpriteFileIndex &index) { + std::unique_ptr 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 spritewidths, spriteheights; + std::vector spriteoffs; + spritewidths.resize(numsprits); + spriteheights.resize(numsprits); + spriteoffs.resize(numsprits); + std::unique_ptr temp_bmp; // for disposing temp sprites + std::vector 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 diff --git a/engines/ags/shared/ac/sprite_file.h b/engines/ags/shared/ac/sprite_file.h new file mode 100644 index 00000000000..90cea21d35f --- /dev/null +++ b/engines/ags/shared/ac/sprite_file.h @@ -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 . + * + */ + +//============================================================================= +// +// 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 Widths; + std::vector Heights; + std::vector 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 &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 &metrics); + // Rebuilds sprite index from the main sprite file + HError RebuildSpriteIndex(Stream *in, sprkey_t topmost, SpriteFileVersion vers, + std::vector &metrics); + + HError LoadSprite(sprkey_t index, Bitmap *&sprite); + HError LoadSpriteData(sprkey_t index, Size &metric, int &bpp, std::vector &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 &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 &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 _spriteData; + std::unique_ptr _stream; // the sprite stream + bool _compressed; // are sprites compressed + sprkey_t _curPos; // current stream position (sprite slot) +}; + +} // namespace Shared +} // namespace AGS +} // namespace AGS3 + +#endif