From fc9473d175ee0643aa763b8dfdb614829cf40885 Mon Sep 17 00:00:00 2001 From: Paul Gilbert Date: Wed, 16 Mar 2022 22:25:30 -0700 Subject: [PATCH] AGS: Picked a sprite file writing code into SpriteFileWriter class From upstream 62a87664fc9249bad2886b44d10affc4d60071c3 --- engines/ags/shared/ac/sprite_cache.cpp | 6 +- engines/ags/shared/ac/sprite_file.cpp | 183 ++++++++++++++----------- engines/ags/shared/ac/sprite_file.h | 74 +++++++--- 3 files changed, 165 insertions(+), 98 deletions(-) diff --git a/engines/ags/shared/ac/sprite_cache.cpp b/engines/ags/shared/ac/sprite_cache.cpp index 60b6b145f26..a90f311241d 100644 --- a/engines/ags/shared/ac/sprite_cache.cpp +++ b/engines/ags/shared/ac/sprite_cache.cpp @@ -105,7 +105,7 @@ void SpriteCache::Init() { } void SpriteCache::Reset() { - _file.Reset(); + _file.Close(); // 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) { @@ -434,7 +434,7 @@ int SpriteCache::SaveToFile(const String &filename, bool compressOutput, SpriteF pre_save_sprite(data.Image); sprites.push_back(data.Image); } - return _file.SaveToFile(filename, sprites, &_file, compressOutput, index); + return SaveSpriteFile(filename, sprites, &_file, compressOutput, index); } HError SpriteCache::InitFile(const String &filename, const String &sprindex_filename) { @@ -465,7 +465,7 @@ HError SpriteCache::InitFile(const String &filename, const String &sprindex_file } void SpriteCache::DetachFile() { - _file.Reset(); + _file.Close(); } } // namespace Shared diff --git a/engines/ags/shared/ac/sprite_file.cpp b/engines/ags/shared/ac/sprite_file.cpp index aef315b54ec..2aa4806b6d5 100644 --- a/engines/ags/shared/ac/sprite_file.cpp +++ b/engines/ags/shared/ac/sprite_file.cpp @@ -25,6 +25,7 @@ #include "ags/shared/gfx/bitmap.h" #include "ags/shared/util/compress.h" #include "ags/shared/util/file.h" +#include "ags/shared/util/memory_stream.h" #include "ags/shared/util/stream.h" namespace AGS3 { @@ -109,7 +110,7 @@ HError SpriteFile::OpenFile(const String &filename, const String &sprindex_filen return RebuildSpriteIndex(_stream.get(), topmost, vers, metrics); } -void SpriteFile::Reset() { +void SpriteFile::Close() { _stream.reset(); _curPos = -2; } @@ -312,14 +313,6 @@ HError SpriteFile::LoadSpriteData(sprkey_t index, Size &metric, int &bpp, 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) { @@ -328,7 +321,16 @@ void SpriteFile::SeekToSprite(sprkey_t index) { } } -int SpriteFile::SaveToFile(const String &save_to_file, +// Finds the topmost occupied slot index. Warning: may be slow. +static sprkey_t 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; +} + +int SaveSpriteFile(const String &save_to_file, const std::vector &sprites, SpriteFile *read_from_file, bool compressOutput, SpriteFileIndex &index) { @@ -336,27 +338,12 @@ int SpriteFile::SaveToFile(const String &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); + SpriteFileWriter writer(output); + writer.Begin(compressOutput, lastslot); + std::unique_ptr temp_bmp; // for disposing temp sprites std::vector membuf; // for loading raw sprite data @@ -364,8 +351,6 @@ int SpriteFile::SaveToFile(const String &save_to_file, 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 @@ -377,34 +362,11 @@ int SpriteFile::SaveToFile(const String &save_to_file, // 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]); - } + writer.WriteBitmap(image); continue; } else if (diff_compress) { // sprite doesn't exist - output->WriteInt16(0); // colour depth + writer.WriteEmptySlot(); continue; } @@ -413,33 +375,19 @@ int SpriteFile::SaveToFile(const String &save_to_file, Size metric; int bpp; read_from_file->LoadSpriteData(i, metric, bpp, membuf); - - output->WriteInt16(bpp); - if (bpp == 0) + if (bpp == 0) { + writer.WriteEmptySlot(); 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()); + } + writer.WriteSpriteData(membuf, metric.Width, metric.Height, bpp); } + writer.Finalize(); - index.SpriteFileIDCheck = spriteFileIDCheck; - index.LastSlot = lastslot; - index.SpriteCount = numsprits; - index.Widths = spritewidths; - index.Heights = spriteheights; - index.Offsets = spriteoffs; + index = writer.GetIndex(); return 0; } -int SpriteFile::SaveSpriteIndex(const String &filename, const SpriteFileIndex &index) { +int SaveSpriteIndex(const String &filename, const SpriteFileIndex &index) { // write the sprite index file Stream *out = File::CreateFile(filename); if (!out) @@ -451,9 +399,9 @@ int SpriteFile::SaveSpriteIndex(const String &filename, const SpriteFileIndex &i 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->WriteInt32(index.GetLastSlot()); + out->WriteInt32(index.GetCount()); + if (index.GetCount() > 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()); @@ -462,6 +410,83 @@ int SpriteFile::SaveSpriteIndex(const String &filename, const SpriteFileIndex &i return 0; } +SpriteFileWriter::SpriteFileWriter(std::unique_ptr &out) : _out(out) { +} + +void SpriteFileWriter::Begin(bool compressed, sprkey_t last_slot) { + if (!_out) return; + _index.SpriteFileIDCheck = g_system->getMillis(); + _compress = compressed; + + // sprite file version + _out->WriteInt16(kSprfVersion_Current); + _out->WriteArray(spriteFileSig, strlen(spriteFileSig), 1); + _out->WriteInt8(_compress ? 1 : 0); + _out->WriteInt32(_index.SpriteFileIDCheck); + + // Remember and write provided "last slot" index, + // but if it's not set (< 0) then we will have to return back later + // and write correct one; this is done in Finalize(). + _lastSlotPos = _out->GetPosition(); + _out->WriteInt32(last_slot); + + if (last_slot >= 0) { // allocate buffers to store the indexing info + sprkey_t numsprits = last_slot + 1; + _index.Offsets.reserve(numsprits); + _index.Widths.reserve(numsprits); + _index.Heights.reserve(numsprits); + } +} + +void SpriteFileWriter::WriteBitmap(Bitmap *image) { + if (!_out) return; + int bpp = image->GetColorDepth() / 8; + int w = image->GetWidth(); + int h = image->GetHeight(); + if (_compress) { + MemoryStream mems(_membuf, kStream_Write); + rle_compress(image, &mems); + WriteSpriteData(_membuf, w, h, bpp); + _membuf.clear(); + } else { + WriteSpriteData((const char *)image->GetData(), w * h * bpp, w, h, bpp); + } +} + +void SpriteFileWriter::WriteEmptySlot() { + if (!_out) return; + soff_t sproff = _out->GetPosition(); + _out->WriteInt16(0); // write invalid color depth to mark empty slot + _index.Offsets.push_back(sproff); + _index.Widths.push_back(0); + _index.Heights.push_back(0); +} + +void SpriteFileWriter::WriteSpriteData(const char *pbuf, size_t len, + int w, int h, int bpp) { + if (!_out) return; + soff_t sproff = _out->GetPosition(); + _index.Offsets.push_back(sproff); + _index.Widths.push_back(w); + _index.Heights.push_back(h); + _out->WriteInt16(bpp); + _out->WriteInt16(w); + _out->WriteInt16(h); + // if not compressed, then the data size could be calculated from the + // image metrics, therefore no need to write one + if (_compress) + _out->WriteInt32(len); + if (len == 0) return; // bad data? + _out->Write(pbuf, len); // write data itself +} + +void SpriteFileWriter::Finalize() { + if (!_out || _lastSlotPos < 0) return; + _out->Seek(_lastSlotPos, kSeekBegin); + _out->WriteInt32(_index.GetLastSlot()); + _out.reset(); +} + } // namespace Shared } // namespace AGS } // namespace AGS3 diff --git a/engines/ags/shared/ac/sprite_file.h b/engines/ags/shared/ac/sprite_file.h index 90cea21d35f..8052aff5f50 100644 --- a/engines/ags/shared/ac/sprite_file.h +++ b/engines/ags/shared/ac/sprite_file.h @@ -21,7 +21,10 @@ //============================================================================= // -// SpriteFile class handles sprite file loading and streaming. +// SpriteFile class handles sprite file parsing and streaming sprites. +// SpriteFileWriter manages writing sprites into the output stream one by one, +// accumulating index information, and may therefore be suitable for a variety +// of situations. // //============================================================================= @@ -29,7 +32,9 @@ #define AGS_SHARED_AC_SPRITE_FILE_H #include "ags/shared/core/types.h" +#include "ags/lib/std/memory.h" #include "ags/lib/std/vector.h" +#include "ags/shared/util/stream.h" #include "ags/globals.h" namespace AGS3 { @@ -61,14 +66,16 @@ 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; + + inline size_t GetCount() const { return Offsets.size(); } + inline sprkey_t GetLastSlot() const { return (sprkey_t)GetCount() - 1; } }; - +// SpriteFile opens a sprite file for reading, reports general information, +// and lets read sprites in any order. class SpriteFile { public: // Standart sprite file and sprite index names @@ -79,7 +86,8 @@ public: // Loads sprite reference information and inits sprite stream HError OpenFile(const String &filename, const String &sprindex_filename, std::vector &metrics); - void Reset(); + // Closes stream; no reading will be possible unless opened again + void Close(); // Tells if bitmaps in the file are compressed bool IsFileCompressed() const; @@ -93,21 +101,12 @@ public: HError RebuildSpriteIndex(Stream *in, sprkey_t topmost, SpriteFileVersion vers, std::vector &metrics); + // Loads an image data and creates a ready bitmap HError LoadSprite(sprkey_t index, Bitmap *&sprite); + // Loads an image data into the buffer, reports the bitmap metrics and color depth 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); @@ -124,6 +123,49 @@ private: sprkey_t _curPos; // current stream position (sprite slot) }; +// SpriteFileWriter class writes a sprite file in a requested format. +// Start using it by calling Begin, write ready bitmaps or copy raw sprite data +// over slot by slot, then call Finalize to let it close the format correctly. +class SpriteFileWriter { +public: + SpriteFileWriter(std::unique_ptr &out); + ~SpriteFileWriter() {} + + // Get the sprite index, accumulated after write + const SpriteFileIndex &GetIndex() const { return _index; } + + // Initializes new sprite file format + void Begin(bool compress, sprkey_t last_slot = -1); + // Writes a bitmap into file, compressing if necessary + void WriteBitmap(Bitmap *image); + // Writes an empty slot marker + void WriteEmptySlot(); + // Writes a raw sprite data without additional processing + void WriteSpriteData(const char *pbuf, size_t len, int w, int h, int bpp); + void WriteSpriteData(const std::vector &buf, int w, int h, int bpp) + { WriteSpriteData(&buf[0], buf.size(), w, h, bpp); } + // Finalizes current format; no further writing is possible after this + void Finalize(); + +private: + std::unique_ptr &_out; + bool _compress = false; + soff_t _lastSlotPos = -1; // last slot save position in file + // sprite index accumulated on write for reporting back to user + SpriteFileIndex _index; + // compression buffer + std::vector _membuf; +}; + +// 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?) +int SaveSpriteFile(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 +int SaveSpriteIndex(const String &filename, const SpriteFileIndex &index); + } // namespace Shared } // namespace AGS } // namespace AGS3