AGS: Picked a sprite file writing code into SpriteFileWriter class

From upstream 62a87664fc9249bad2886b44d10affc4d60071c3
This commit is contained in:
Paul Gilbert 2022-03-16 22:25:30 -07:00
parent ddb38177f9
commit fc9473d175
3 changed files with 165 additions and 98 deletions

View File

@ -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

View File

@ -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<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) {
@ -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<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;
}
int SaveSpriteFile(const String &save_to_file,
const std::vector<Bitmap *> &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<int16_t> spritewidths, spriteheights;
std::vector<soff_t> spriteoffs;
spritewidths.resize(numsprits);
spriteheights.resize(numsprits);
spriteoffs.resize(numsprits);
SpriteFileWriter writer(output);
writer.Begin(compressOutput, lastslot);
std::unique_ptr<Bitmap> temp_bmp; // for disposing temp sprites
std::vector<char> 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<Stream> &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

View File

@ -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<int16_t> Widths;
std::vector<int16_t> Heights;
std::vector<soff_t> 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<Size> &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<Size> &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<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);
@ -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<Stream> &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<char> &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<Stream> &_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<char> _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<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
int SaveSpriteIndex(const String &filename, const SpriteFileIndex &index);
} // namespace Shared
} // namespace AGS
} // namespace AGS3