[Scummvm-git-logs] scummvm master -> b615ca27b3d8fd77fb6ed01b630906cd6c306582

dreammaster noreply at scummvm.org
Thu Mar 31 05:14:10 UTC 2022


This automated email contains information about 12 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .

Summary:
77e4a7f520 AGS: Tidy some code in SpriteFile class
3c81cbaa1c AGS: In SpriteFile split WriteRawData() and WriteSpriteData()
8c17e3cc7e AGS: rle_(de)compress works with arbitrary buffers, not Bitmaps
6912f58a2f AGS: SpriteFile's extended format, let store as indexed bitmaps
4392fa5e2d AGS: Simplified savecompressed_allegro() and renamed for clarity
02540a3b47 AGS: In save_lzw() replaced the use of temp file with memory buffer
dc7609a79c AGS: Removed lzwexpand_to_mem(), tidied load_lzw()
eecd211bb6 AGS: Don't call quit() from lzw functions
897724f110 AGS: Split VectorStream from MemoryStream and allow write C-buffer
6d4a816fb4 AGS: SpriteFile supports compression type selection (RLE/LZW)
0f6ca133d2 AGS: fixed gcc compilation of lzw
b615ca27b3 AGS: Fix String::ClipRight causing access violation for empty string


Commit: 77e4a7f520a61f07b71c0248a3ac524cf760165f
    https://github.com/scummvm/scummvm/commit/77e4a7f520a61f07b71c0248a3ac524cf760165f
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-03-30T22:13:17-07:00

Commit Message:
AGS: Tidy some code in SpriteFile class

>From upstream 923c4b3e9ab23359c57233802acf57430843a308

Changed paths:
    engines/ags/shared/ac/sprite_cache.h
    engines/ags/shared/ac/sprite_file.cpp
    engines/ags/shared/ac/sprite_file.h


diff --git a/engines/ags/shared/ac/sprite_cache.h b/engines/ags/shared/ac/sprite_cache.h
index fb830b963e2..fbb94593aae 100644
--- a/engines/ags/shared/ac/sprite_cache.h
+++ b/engines/ags/shared/ac/sprite_cache.h
@@ -153,7 +153,6 @@ private:
 	void        DisposeOldest();
 
 	// Information required for the sprite streaming
-	// TODO: split into sprite cache and sprite stream data
 	struct SpriteData {
 		size_t          Size; // to track cache size
 		uint32_t        Flags;
diff --git a/engines/ags/shared/ac/sprite_file.cpp b/engines/ags/shared/ac/sprite_file.cpp
index ef08fd907ff..6439293b2b1 100644
--- a/engines/ags/shared/ac/sprite_file.cpp
+++ b/engines/ags/shared/ac/sprite_file.cpp
@@ -74,18 +74,15 @@ HError SpriteFile::OpenFile(const String &filename, const String &sprindex_filen
 		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) {
+		_compressed = false;
 		// skip the palette
 		_stream->Seek(256 * 3); // sizeof(RGB) * 256
+	} else if (vers == kSprfVersion_Compressed) {
+		_compressed = true;
+	} else if (vers >= kSprfVersion_Last32bit) {
+		_compressed = (_stream->ReadInt8() == 1);
+		spriteFileID = _stream->ReadInt32();
 	}
 
 	sprkey_t topmost;
@@ -191,37 +188,17 @@ bool SpriteFile::LoadSpriteIndexFile(const String &filename, int expectedFileID,
 
 HError SpriteFile::RebuildSpriteIndex(Stream *in, sprkey_t topmost,
 	SpriteFileVersion vers, std::vector<Size> &metrics) {
-	for (sprkey_t i = 0; i <= topmost; ++i) {
+	topmost = std::min(topmost, (sprkey_t)_spriteData.size() - 1);
+	for (sprkey_t i = 0; !in->EOS() && (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);
+		int bpp = in->ReadInt16();
+		if (bpp == 0) continue; // empty slot
+		int w = in->ReadInt16();
+		int h = in->ReadInt16();
+		metrics[i].Width = w;
+		metrics[i].Height = h;
+		size_t data_sz = _compressed ? in->ReadInt32() : w * h * bpp;
+		in->Seek(data_sz); // skip image data
 	}
 	return HError::None();
 }
@@ -238,17 +215,16 @@ HError SpriteFile::LoadSprite(sprkey_t index, Shared::Bitmap *&sprite) {
 	SeekToSprite(index);
 	_curPos = -2; // mark undefined pos
 
-	int coldep = _stream->ReadInt16();
-	if (coldep == 0) { // empty slot, this is normal
+	int bpp = _stream->ReadInt16();
+	if (bpp == 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);
+	int w = _stream->ReadInt16();
+	int h = _stream->ReadInt16();
+	Bitmap *image = BitmapHelper::CreateBitmap(w, h, bpp * 8);
 	if (image == nullptr) {
 		return new Error(String::FromFormat("LoadSprite: failed to allocate bitmap %d (%dx%d%d).",
-			index, wdd, htt, coldep * 8));
+			index, w, h, bpp * 8));
 	}
 
 	if (_compressed) {
@@ -258,27 +234,28 @@ HError SpriteFile::LoadSprite(sprkey_t index, Shared::Bitmap *&sprite) {
 			return new Error(String::FromFormat("LoadSprite: bad compressed data for sprite %d.", index));
 		}
 		rle_decompress(image, _stream.get());
+		// TODO: test that not more than data_size was read!
 	} 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);
+		switch (bpp) {
+		case 1: _stream->Read(image->GetDataForWriting(), w * h); break;
+		case 2: _stream->ReadArrayOfInt16(
+			reinterpret_cast<int16_t *>(image->GetDataForWriting()), w *h); break;
+		case 4: _stream->ReadArrayOfInt32(
+			reinterpret_cast<int32_t *>(image->GetDataForWriting()), w *h); break;
+		default: assert(0); break;
 		}
 	}
+
 	sprite = image;
 	_curPos = index + 1; // mark correct pos
 	return HError::None();
 }
 
 HError SpriteFile::LoadSpriteData(sprkey_t index, Size &metric, int &bpp,
-		std::vector<uint8_t> &data) {
+	std::vector<uint8_t> &data) {
 	metric = Size();
 	bpp = 0;
+	data.resize(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));
@@ -289,26 +266,17 @@ HError SpriteFile::LoadSpriteData(sprkey_t index, Size &metric, int &bpp,
 	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);
+	bpp = _stream->ReadInt16();
+	if (bpp == 0) { // empty slot, this is normal
 		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;
+	int w = _stream->ReadInt16();
+	int h = _stream->ReadInt16();
+	size_t data_size = _compressed ? _stream->ReadInt32() : w * h * bpp;
 	data.resize(data_size);
 	_stream->Read(&data[0], data_size);
-	metric = Size(width, height);
-	bpp = coldep;
+	metric = Size(w, h);
+
 	_curPos = index + 1; // mark correct pos
 	return HError::None();
 }
@@ -402,9 +370,9 @@ int SaveSpriteIndex(const String &filename, const SpriteFileIndex &index) {
 	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());
+		out->WriteArrayOfInt16(&index.Widths[0], index.Widths.size());
+		out->WriteArrayOfInt16(&index.Heights[0], index.Heights.size());
+		out->WriteArrayOfInt64(&index.Offsets[0], index.Offsets.size());
 	}
 	delete out;
 	return 0;
@@ -440,7 +408,7 @@ void SpriteFileWriter::Begin(bool compressed, sprkey_t last_slot) {
 
 void SpriteFileWriter::WriteBitmap(Bitmap *image) {
 	if (!_out) return;
-	int bpp = image->GetColorDepth() / 8;
+	int bpp = image->GetBPP();
 	int w = image->GetWidth();
 	int h = image->GetHeight();
 	if (_compress) {
diff --git a/engines/ags/shared/ac/sprite_file.h b/engines/ags/shared/ac/sprite_file.h
index 2699964e909..a12ed80ccfd 100644
--- a/engines/ags/shared/ac/sprite_file.h
+++ b/engines/ags/shared/ac/sprite_file.h
@@ -113,7 +113,8 @@ private:
 	// Internal sprite reference
 	struct SpriteRef {
 		soff_t Offset = 0; // data offset
-		size_t Size = 0;   // cache size of element, in bytes
+		size_t RawSize = 0; // file size of element, in bytes
+		// TODO: RawSize is currently unused, due to incompleteness of spriteindex format
 	};
 
 	// Array of sprite references


Commit: 3c81cbaa1c9bc583c5f9846e9577882ea548340e
    https://github.com/scummvm/scummvm/commit/3c81cbaa1c9bc583c5f9846e9577882ea548340e
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-03-30T22:13:17-07:00

Commit Message:
AGS: In SpriteFile split WriteRawData() and WriteSpriteData()

>From upstream eab89574be9a577ea13e4d90e7032a6d6c44c903

Changed paths:
    engines/ags/shared/ac/sprite_file.cpp
    engines/ags/shared/ac/sprite_file.h


diff --git a/engines/ags/shared/ac/sprite_file.cpp b/engines/ags/shared/ac/sprite_file.cpp
index 6439293b2b1..878f65ccec1 100644
--- a/engines/ags/shared/ac/sprite_file.cpp
+++ b/engines/ags/shared/ac/sprite_file.cpp
@@ -251,10 +251,8 @@ HError SpriteFile::LoadSprite(sprkey_t index, Shared::Bitmap *&sprite) {
 	return HError::None();
 }
 
-HError SpriteFile::LoadSpriteData(sprkey_t index, Size &metric, int &bpp,
-	std::vector<uint8_t> &data) {
-	metric = Size();
-	bpp = 0;
+HError SpriteFile::LoadRawData(sprkey_t index, SpriteDatHeader &hdr, std::vector<uint8_t> &data) {
+	hdr = SpriteDatHeader();
 	data.resize(0);
 	if (index < 0 || (size_t)index >= _spriteData.size())
 		new Error(String::FromFormat("LoadSprite: slot index %d out of bounds (%d - %d).",
@@ -266,16 +264,20 @@ HError SpriteFile::LoadSpriteData(sprkey_t index, Size &metric, int &bpp,
 	SeekToSprite(index);
 	_curPos = -2; // mark undefined pos
 
-	bpp = _stream->ReadInt16();
+	int bpp = _stream->ReadInt16();
 	if (bpp == 0) { // empty slot, this is normal
 		return HError::None();
 	}
 	int w = _stream->ReadInt16();
 	int h = _stream->ReadInt16();
-	size_t data_size = _compressed ? _stream->ReadInt32() : w * h * bpp;
+	size_t data_size = w * h * bpp;
+	if (_compressed) {
+		data_size = _stream->ReadInt32() + sizeof(int32_t);
+		_stream->Seek(-4);
+	}
 	data.resize(data_size);
 	_stream->Read(&data[0], data_size);
-	metric = Size(w, h);
+	hdr = SpriteDatHeader(bpp, w, h);
 
 	_curPos = index + 1; // mark correct pos
 	return HError::None();
@@ -340,14 +342,13 @@ int SaveSpriteFile(const String &save_to_file,
 
 		// 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);
-		if (bpp == 0) {
+		SpriteDatHeader hdr;
+		read_from_file->LoadRawData(i, hdr, membuf);
+		if (hdr.BPP == 0) { // empty slot
 			writer.WriteEmptySlot();
-			continue; // empty slot
+			continue;
 		}
-		writer.WriteSpriteData(&membuf[0], membuf.size(), metric.Width, metric.Height, bpp);
+		writer.WriteRawData(hdr, &membuf[0], membuf.size());
 	}
 	writer.Finalize();
 
@@ -411,13 +412,39 @@ void SpriteFileWriter::WriteBitmap(Bitmap *image) {
 	int bpp = image->GetBPP();
 	int w = image->GetWidth();
 	int h = image->GetHeight();
+	const SpriteDatHeader hdr(bpp, w, h);
 	if (_compress) {
 		MemoryStream mems(_membuf, kStream_Write);
 		rle_compress(image, &mems);
-		WriteSpriteData(&_membuf[0], _membuf.size(), w, h, bpp);
+		// write image data as a plain byte array
+		WriteSpriteData(hdr, &_membuf[0], _membuf.size(), 1);
 		_membuf.clear();
 	} else {
-		WriteSpriteData(image->GetData(), w * h * bpp, w, h, bpp);
+		WriteSpriteData(hdr, image->GetData(), w * h * bpp, bpp);
+	}
+}
+
+void SpriteFileWriter::WriteSpriteData(const SpriteDatHeader &hdr,
+	const uint8_t *im_data, size_t im_data_sz, int im_bpp) {
+	// Add index entry and write resulting data to the stream
+	soff_t sproff = _out->GetPosition();
+	_index.Offsets.push_back(sproff);
+	_index.Widths.push_back(hdr.Width);
+	_index.Heights.push_back(hdr.Height);
+	_out->WriteInt16(hdr.BPP);
+	_out->WriteInt16(hdr.Width);
+	_out->WriteInt16(hdr.Height);
+	// if not compressed, then the data size is supposed to be calculated
+	// from the image metrics
+	if (_compress)
+		_out->WriteInt32(im_data_sz);
+	switch (im_bpp) {
+	case 1: _out->Write(im_data, im_data_sz); break;
+	case 2: _out->WriteArrayOfInt16(reinterpret_cast<const int16_t *>(im_data),
+		im_data_sz / sizeof(int16_t)); break;
+	case 4: _out->WriteArrayOfInt32(reinterpret_cast<const int32_t *>(im_data),
+		im_data_sz / sizeof(int32_t)); break;
+	default: assert(0); break;
 	}
 }
 
@@ -430,22 +457,16 @@ void SpriteFileWriter::WriteEmptySlot() {
 	_index.Heights.push_back(0);
 }
 
-void SpriteFileWriter::WriteSpriteData(const uint8_t *pbuf, size_t len,
-	int w, int h, int bpp) {
+void SpriteFileWriter::WriteRawData(const SpriteDatHeader &hdr, const uint8_t *data, size_t data_sz) {
 	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
+	_index.Widths.push_back(hdr.Width);
+	_index.Heights.push_back(hdr.Height);
+	_out->WriteInt16(hdr.BPP);
+	_out->WriteInt16(hdr.Width);
+	_out->WriteInt16(hdr.Height);
+	_out->Write(data, data_sz);
 }
 
 void SpriteFileWriter::Finalize() {
diff --git a/engines/ags/shared/ac/sprite_file.h b/engines/ags/shared/ac/sprite_file.h
index a12ed80ccfd..4fde6949e72 100644
--- a/engines/ags/shared/ac/sprite_file.h
+++ b/engines/ags/shared/ac/sprite_file.h
@@ -74,6 +74,18 @@ struct SpriteFileIndex {
 	inline sprkey_t GetLastSlot() const { return (sprkey_t)GetCount() - 1; }
 };
 
+// Invidual sprite data header (as read from the file)
+struct SpriteDatHeader {
+	int BPP = 0; // color depth (bytes per pixel)
+	int Width = 0;
+	int Height = 0;
+
+	SpriteDatHeader() = default;
+	SpriteDatHeader(int bpp, int w = 0, int h = 0)
+		: BPP(bpp), Width(w), Height(h) {
+	}
+};
+
 // SpriteFile opens a sprite file for reading, reports general information,
 // and lets read sprites in any order.
 class SpriteFile {
@@ -103,8 +115,8 @@ public:
 
 	// 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<uint8_t> &data);
+	// Loads a raw sprite element data into the buffer, stores header info separately
+	HError      LoadRawData(sprkey_t index, SpriteDatHeader &hdr, std::vector<uint8_t> &data);
 
 private:
 	// Seek stream to sprite
@@ -141,13 +153,16 @@ public:
     void WriteBitmap(Bitmap *image);
     // Writes an empty slot marker
     void WriteEmptySlot();
-    // Writes a raw sprite data without additional processing
-	void WriteSpriteData(const uint8_t *pbuf, size_t len, int w, int h, int bpp);
-    // Finalizes current format; no further writing is possible after this
-    void Finalize();
+	// Writes a raw sprite data without any additional processing
+	void WriteRawData(const SpriteDatHeader &hdr, const uint8_t *data, size_t data_sz);
+	// Finalizes current format; no further writing is possible after this
+	void Finalize();
 
 private:
-    std::unique_ptr<Stream> &_out;
+	// Writes prepared image data in a proper file format, following explicit data_bpp rule
+	void WriteSpriteData(const SpriteDatHeader &hdr, const uint8_t *im_data, size_t im_data_sz, int im_bpp);
+
+	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


Commit: 8c17e3cc7e6cd8e753be5dcf87b7fd415f5ac9af
    https://github.com/scummvm/scummvm/commit/8c17e3cc7e6cd8e753be5dcf87b7fd415f5ac9af
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-03-30T22:13:17-07:00

Commit Message:
AGS: rle_(de)compress works with arbitrary buffers, not Bitmaps

>From upstream d3be1ce673455ed6bf6f715e0adad4be44eee2bd

Changed paths:
    engines/ags/shared/ac/sprite_file.cpp
    engines/ags/shared/util/compress.cpp
    engines/ags/shared/util/compress.h


diff --git a/engines/ags/shared/ac/sprite_file.cpp b/engines/ags/shared/ac/sprite_file.cpp
index 878f65ccec1..ba58ef4d186 100644
--- a/engines/ags/shared/ac/sprite_file.cpp
+++ b/engines/ags/shared/ac/sprite_file.cpp
@@ -233,7 +233,7 @@ HError SpriteFile::LoadSprite(sprkey_t index, Shared::Bitmap *&sprite) {
 			delete image;
 			return new Error(String::FromFormat("LoadSprite: bad compressed data for sprite %d.", index));
 		}
-		rle_decompress(image, _stream.get());
+		rle_decompress(image->GetDataForWriting(), w * h * bpp, bpp, _stream.get());
 		// TODO: test that not more than data_size was read!
 	} else {
 		switch (bpp) {
@@ -415,7 +415,7 @@ void SpriteFileWriter::WriteBitmap(Bitmap *image) {
 	const SpriteDatHeader hdr(bpp, w, h);
 	if (_compress) {
 		MemoryStream mems(_membuf, kStream_Write);
-		rle_compress(image, &mems);
+		rle_compress(image->GetData(), w * h * bpp, bpp, &mems);
 		// write image data as a plain byte array
 		WriteSpriteData(hdr, &_membuf[0], _membuf.size(), 1);
 		_membuf.clear();
diff --git a/engines/ags/shared/util/compress.cpp b/engines/ags/shared/util/compress.cpp
index 98154396aa5..268f3fb51c2 100644
--- a/engines/ags/shared/util/compress.cpp
+++ b/engines/ags/shared/util/compress.cpp
@@ -43,17 +43,18 @@ using namespace AGS::Shared;
 // RLE
 //-----------------------------------------------------------------------------
 
-void cpackbitl(const uint8_t *line, int size, Stream *out) {
-	int cnt = 0;                  // bytes encoded
+static void cpackbitl(const uint8_t *line, size_t size, Stream *out) {
+	size_t cnt = 0;               // bytes encoded
 
 	while (cnt < size) {
+		// note that the algorithm below requires signed operations
 		int i = cnt;
 		int j = i + 1;
 		int jmax = i + 126;
-		if (jmax >= size)
+		if ((size_t)jmax >= size)
 			jmax = size - 1;
 
-		if (i == size - 1) {        //................last byte alone
+		if (i == (int)size - 1) {        //................last byte alone
 			out->WriteInt8(0);
 			out->WriteInt8(line[i]);
 			cnt++;
@@ -71,24 +72,25 @@ void cpackbitl(const uint8_t *line, int size, Stream *out) {
 				j++;
 
 			out->WriteInt8(j - i);
-			out->WriteArray(line + i, j - i + 1, 1);
+			out->Write(line + i, j - i + 1);
 			cnt += j - i + 1;
 
 		}
 	} // end while
 }
 
-void cpackbitl16(const uint16_t *line, int size, Stream *out) {
-	int cnt = 0;                  // bytes encoded
+static void cpackbitl16(const uint16_t *line, size_t size, Stream *out) {
+	size_t cnt = 0;               // bytes encoded
 
 	while (cnt < size) {
+		// note that the algorithm below requires signed operations
 		int i = cnt;
 		int j = i + 1;
 		int jmax = i + 126;
-		if (jmax >= size)
+		if ((size_t)jmax >= size)
 			jmax = size - 1;
 
-		if (i == size - 1) {        //................last byte alone
+		if (i == (int)size - 1) {        //................last byte alone
 			out->WriteInt8(0);
 			out->WriteInt16(line[i]);
 			cnt++;
@@ -113,17 +115,18 @@ void cpackbitl16(const uint16_t *line, int size, Stream *out) {
 	} // end while
 }
 
-void cpackbitl32(const uint32_t *line, int size, Stream *out) {
-	int cnt = 0;                  // bytes encoded
+static void cpackbitl32(const uint32_t *line, size_t size, Stream *out) {
+	size_t cnt = 0;               // bytes encoded
 
 	while (cnt < size) {
+		// note that the algorithm below requires signed operations
 		int i = cnt;
 		int j = i + 1;
 		int jmax = i + 126;
-		if (jmax >= size)
+		if ((size_t)jmax >= size)
 			jmax = size - 1;
 
-		if (i == size - 1) {        //................last byte alone
+		if (i == (int)size - 1) {        //................last byte alone
 			out->WriteInt8(0);
 			out->WriteInt32(line[i]);
 			cnt++;
@@ -178,15 +181,15 @@ void csavecompressed(Stream *out, const unsigned char *tobesaved, const RGB pala
 	free(ress);
 }
 
-int cunpackbitl(uint8_t *line, int size, Stream *in) {
-	int n = 0;                    // number of bytes decoded
+static int cunpackbitl(uint8_t *line, size_t size, Stream *in) {
+	size_t n = 0;                  // number of bytes decoded
 
 	while (n < size) {
 		int ix = in->ReadByte();     // get index byte
 		if (in->HasErrors())
 			break;
 
-		int8_t cx = ix;
+		char cx = ix;
 		if (cx == -128)
 			cx = 0;
 
@@ -215,15 +218,15 @@ int cunpackbitl(uint8_t *line, int size, Stream *in) {
 	return in->HasErrors() ? -1 : 0;
 }
 
-int cunpackbitl16(uint16_t *line, int size, Stream *in) {
-	int n = 0;                    // number of bytes decoded
+static int cunpackbitl16(uint16_t *line, size_t size, Stream *in) {
+	size_t n = 0;                  // number of bytes decoded
 
 	while (n < size) {
 		int ix = in->ReadByte();     // get index byte
 		if (in->HasErrors())
 			break;
 
-		int8_t cx = ix;
+		char cx = ix;
 		if (cx == -128)
 			cx = 0;
 
@@ -252,15 +255,15 @@ int cunpackbitl16(uint16_t *line, int size, Stream *in) {
 	return in->HasErrors() ? -1 : 0;
 }
 
-int cunpackbitl32(uint32_t *line, int size, Stream *in) {
-	int n = 0;                    // number of bytes decoded
+static int cunpackbitl32(uint32_t *line, size_t size, Stream *in) {
+	size_t n = 0;                  // number of bytes decoded
 
 	while (n < size) {
 		int ix = in->ReadByte();     // get index byte
 		if (in->HasErrors())
 			break;
 
-		int8_t cx = ix;
+		char cx = ix;
 		if (cx == -128)
 			cx = 0;
 
@@ -289,31 +292,21 @@ int cunpackbitl32(uint32_t *line, int size, Stream *in) {
 	return in->HasErrors() ? -1 : 0;
 }
 
-void rle_compress(Bitmap *bmp, Shared::Stream *out) {
-	const int depth = bmp->GetBPP();
-	if (depth == 1) {
-		for (int y = 0; y < bmp->GetHeight(); y++)
-			cpackbitl(&bmp->GetScanLineForWriting(y)[0], bmp->GetWidth(), out);
-	} else if (depth == 2) {
-		for (int y = 0; y < bmp->GetHeight(); y++)
-			cpackbitl16((uint16_t *)&bmp->GetScanLine(y)[0], bmp->GetWidth(), out);
-	} else {
-		for (int y = 0; y < bmp->GetHeight(); y++)
-			cpackbitl32((uint32_t *)&bmp->GetScanLine(y)[0], bmp->GetWidth(), out);
+void rle_compress(const uint8_t *data, size_t data_sz, int image_bpp, Stream *out) {
+	switch (image_bpp) {
+	case 1: cpackbitl(data, data_sz, out); break;
+	case 2: cpackbitl16(reinterpret_cast<const uint16_t *>(data), data_sz / sizeof(uint16_t), out); break;
+	case 4: cpackbitl32(reinterpret_cast<const uint32_t *>(data), data_sz / sizeof(uint32_t), out); break;
+	default: assert(0); break;
 	}
 }
 
-void rle_decompress(Bitmap *bmp, Shared::Stream *in) {
-	const int depth = bmp->GetBPP();
-	if (depth == 1) {
-		for (int y = 0; y < bmp->GetHeight(); y++)
-			cunpackbitl(&bmp->GetScanLineForWriting(y)[0], bmp->GetWidth(), in);
-	} else if (depth == 2) {
-		for (int y = 0; y < bmp->GetHeight(); y++)
-			cunpackbitl16((uint16_t *)&bmp->GetScanLineForWriting(y)[0], bmp->GetWidth(), in);
-	} else {
-		for (int y = 0; y < bmp->GetHeight(); y++)
-			cunpackbitl32((uint32_t *)&bmp->GetScanLineForWriting(y)[0], bmp->GetWidth(), in);
+void rle_decompress(uint8_t *data, size_t data_sz, int image_bpp, Stream *in) {
+	switch (image_bpp) {
+	case 1: cunpackbitl(data, data_sz, in); break;
+	case 2: cunpackbitl16(reinterpret_cast<uint16_t *>(data), data_sz / sizeof(uint16_t), in); break;
+	case 4: cunpackbitl32(reinterpret_cast<uint32_t *>(data), data_sz / sizeof(uint32_t), in); break;
+	default: assert(0); break;
 	}
 }
 
diff --git a/engines/ags/shared/util/compress.h b/engines/ags/shared/util/compress.h
index 42223fd443b..d9472c69c7e 100644
--- a/engines/ags/shared/util/compress.h
+++ b/engines/ags/shared/util/compress.h
@@ -36,8 +36,8 @@ class Bitmap;
 
 using namespace AGS; // FIXME later
 
-void rle_compress(Shared::Bitmap *, Shared::Stream *);
-void rle_decompress(Shared::Bitmap *, Shared::Stream *);
+void rle_compress(const uint8_t *data, size_t data_sz, int image_bpp, Shared::Stream *out);
+void rle_decompress(uint8_t *data, size_t data_sz, int image_bpp, Shared::Stream *in);
 
 // LZW compression
 void save_lzw(Shared::Stream *out, const Shared::Bitmap *bmpp, const RGB *pall);


Commit: 6912f58a2fecd49c42be8a94fb89bd8f7b8c87cf
    https://github.com/scummvm/scummvm/commit/6912f58a2fecd49c42be8a94fb89bd8f7b8c87cf
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-03-30T22:13:17-07:00

Commit Message:
AGS: SpriteFile's extended format, let store as indexed bitmaps

>From upstream 34f32e3b71b2f8d0f7480aa04a2fcd38d77fb83b

Changed paths:
    engines/ags/shared/ac/sprite_cache.cpp
    engines/ags/shared/ac/sprite_cache.h
    engines/ags/shared/ac/sprite_file.cpp
    engines/ags/shared/ac/sprite_file.h


diff --git a/engines/ags/shared/ac/sprite_cache.cpp b/engines/ags/shared/ac/sprite_cache.cpp
index e80de98edee..b03fcb26773 100644
--- a/engines/ags/shared/ac/sprite_cache.cpp
+++ b/engines/ags/shared/ac/sprite_cache.cpp
@@ -423,7 +423,7 @@ void SpriteCache::RemapSpriteToSprite0(sprkey_t index) {
 #endif
 }
 
-int SpriteCache::SaveToFile(const String &filename, bool compressOutput, SpriteFileIndex &index) {
+int SpriteCache::SaveToFile(const String &filename, int store_flags, bool compress, SpriteFileIndex &index) {
 	std::vector<Bitmap *> sprites;
 	for (const auto &data : _spriteData) {
 		// NOTE: this is a horrible hack:
@@ -434,7 +434,7 @@ int SpriteCache::SaveToFile(const String &filename, bool compressOutput, SpriteF
 		pre_save_sprite(data.Image);
 		sprites.push_back(data.Image);
 	}
-	return SaveSpriteFile(filename, sprites, &_file, compressOutput, index);
+	return SaveSpriteFile(filename, sprites, &_file, store_flags, compress, index);
 }
 
 HError SpriteCache::InitFile(const String &filename, const String &sprindex_filename) {
diff --git a/engines/ags/shared/ac/sprite_cache.h b/engines/ags/shared/ac/sprite_cache.h
index fbb94593aae..d759639c5d8 100644
--- a/engines/ags/shared/ac/sprite_cache.h
+++ b/engines/ags/shared/ac/sprite_cache.h
@@ -94,10 +94,13 @@ public:
 	// Loads sprite reference information and inits sprite stream
 	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);
+	int         SaveToFile(const Shared::String &filename, int store_flags, bool compress, SpriteFileIndex &index);
 	// Closes an active sprite file stream
 	void        DetachFile();
 
+	inline int GetStoreFlags() const {
+		return _file.GetStoreFlags();
+	}
 	inline bool IsFileCompressed() const {
 		return _file.IsFileCompressed();
 	}
diff --git a/engines/ags/shared/ac/sprite_file.cpp b/engines/ags/shared/ac/sprite_file.cpp
index ba58ef4d186..34bc74af5cf 100644
--- a/engines/ags/shared/ac/sprite_file.cpp
+++ b/engines/ags/shared/ac/sprite_file.cpp
@@ -39,6 +39,106 @@ static const char *spindexid = "SPRINDEX";
 const char *SpriteFile::DefaultSpriteFileName = "acsprset.spr";
 const char *SpriteFile::DefaultSpriteIndexName = "sprindex.dat";
 
+// Image buffer pointer, a helper struct that eases switching
+// between intermediate buffers when loading, saving or converting an image.
+template <typename T> struct ImBufferPtrT {
+	T        Buf = nullptr;
+	size_t   Size = 0;
+	int      BPP = 1; // byte per pixel
+
+	ImBufferPtrT() = default;
+	ImBufferPtrT(T buf, size_t sz, int bpp) : Buf(buf), Size(sz), BPP(bpp) {
+	}
+};
+typedef ImBufferPtrT<uint8_t *> ImBufferPtr;
+typedef ImBufferPtrT<const uint8_t *> ImBufferCPtr;
+
+
+// Finds the given color's index in the palette, or returns -1 if such color is not there
+static size_t lookup_palette(uint32_t col, uint32_t palette[256], uint32_t ncols) {
+	for (size_t i = 0; i < ncols; ++i)
+		if (palette[i] == col) return i;
+	return (size_t)-1;
+}
+
+// Converts a 16/32-bit image into the indexed 8-bit pixel data with palette;
+// NOTE: the palette will contain colors in the same format as the source image.
+// only succeeds if the total number of colors used in the image is < 257.
+static bool CreateIndexedBitmap(const Bitmap *image, std::vector<uint8_t> &dst_data,
+	uint32_t palette[256], uint32_t &pal_count) {
+	const int src_bpp = image->GetBPP();
+	const size_t src_size = image->GetWidth() * image->GetHeight() * image->GetBPP();
+	const size_t dst_size = image->GetWidth() * image->GetHeight();
+	dst_data.resize(dst_size);
+	const uint8_t *src = image->GetData(), *src_end = src + src_size;
+	uint8_t *dst = &dst_data[0], *dst_end = dst + dst_size;
+	pal_count = 0;
+
+	for (; src < src_end && dst < dst_end; src += src_bpp) {
+		uint32_t col = 0;
+		size_t pal_n = 0;
+		switch (src_bpp) {
+		case 2:
+			col = *((const uint16_t *)src);
+			pal_n = lookup_palette(col, palette, pal_count);
+			break;
+		case 4:
+			col = *((const uint32_t *)src);
+			pal_n = lookup_palette(col, palette, pal_count);
+			break;
+		default: assert(0); break;
+		}
+
+		if (pal_n == -1) {
+			if (pal_count == 256) return false;
+			pal_n = pal_count;
+			palette[pal_count++] = col;
+		}
+		*(dst++) = (uint8_t)pal_n;
+	}
+	return true;
+}
+
+// Unpacks an indexed image's pixel data into the 16/32-bit image;
+// NOTE: the palette is expected to contain colors in the same format as the destination.
+static void UnpackIndexedBitmap(Bitmap *image, const uint8_t *data, size_t data_size,
+	uint32_t *palette, uint32_t pal_count) {
+	assert(pal_count > 0);
+	if (pal_count == 0) return; // meaningless
+	const uint8_t bpp = image->GetBPP();
+	const size_t dst_size = image->GetWidth() * image->GetHeight() * image->GetBPP();
+	uint8_t *dst = image->GetDataForWriting(), *dst_end = dst + dst_size;
+	for (size_t p = 0; (p < data_size) && (dst < dst_end); ++p, dst += bpp) {
+		uint8_t index = data[p];
+		assert(index < pal_count);
+		uint32_t color = (index < pal_count) ? palette[index] : palette[0];
+		switch (bpp) {
+		case 2: *((uint16_t *)dst) = color; break;
+		case 4: *((uint32_t *)dst) = color; break;
+		default: assert(0); break;
+		}
+	}
+}
+
+
+static inline SpriteFormat PaletteFormatForBPP(int bpp) {
+	switch (bpp) {
+	case 1: return kSprFmt_PaletteRgb888;
+	case 2: return kSprFmt_PaletteRgb565;
+	case 4: return kSprFmt_PaletteArgb8888;
+	}
+	return kSprFmt_Undefined;
+}
+
+static inline uint8_t GetPaletteBPP(SpriteFormat fmt) {
+	switch (fmt) {
+	case kSprFmt_PaletteRgb888: return 3;
+	case kSprFmt_PaletteArgb8888: return 4;
+	case kSprFmt_PaletteRgb565: return 2;
+	}
+	return 0; // means no palette
+}
+
 
 SpriteFile::SpriteFile() {
 	_compressed = false;
@@ -47,7 +147,6 @@ SpriteFile::SpriteFile() {
 
 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;
@@ -58,13 +157,14 @@ HError SpriteFile::OpenFile(const String &filename, const String &sprindex_filen
 
 	spr_initial_offs = _stream->GetPosition();
 
-	vers = (SpriteFileVersion)_stream->ReadInt16();
+	_version = (SpriteFileVersion)_stream->ReadInt16();
 	// read the "Sprite File" signature
 	_stream->ReadArray(&buff[0], 13, 1);
 
-	if (vers < kSprfVersion_Uncompressed || vers > kSprfVersion_Current) {
+	if (_version < kSprfVersion_Uncompressed || _version > kSprfVersion_Current) {
 		_stream.reset();
-		return new Error(String::FromFormat("Unsupported spriteset format (requested %d, supported %d - %d).", vers, kSprfVersion_Uncompressed, kSprfVersion_Current));
+		return new Error(String::FromFormat("Unsupported spriteset format (requested %d, supported %d - %d).", _version,
+			kSprfVersion_Uncompressed, kSprfVersion_Current));
 	}
 
 	// unknown version
@@ -74,28 +174,37 @@ HError SpriteFile::OpenFile(const String &filename, const String &sprindex_filen
 		return new Error("Uknown spriteset format.");
 	}
 
-	if (vers < kSprfVersion_Compressed) {
+	_storeFlags = 0;
+	if (_version < kSprfVersion_Compressed) {
 		_compressed = false;
 		// skip the palette
 		_stream->Seek(256 * 3); // sizeof(RGB) * 256
-	} else if (vers == kSprfVersion_Compressed) {
+	} else if (_version == kSprfVersion_Compressed) {
 		_compressed = true;
-	} else if (vers >= kSprfVersion_Last32bit) {
+	} else if (_version >= kSprfVersion_Last32bit) {
 		_compressed = (_stream->ReadInt8() == 1);
 		spriteFileID = _stream->ReadInt32();
 	}
 
 	sprkey_t topmost;
-	if (vers < kSprfVersion_HighSpriteLimit)
+	if (_version < kSprfVersion_HighSpriteLimit)
 		topmost = (uint16_t)_stream->ReadInt16();
 	else
 		topmost = _stream->ReadInt32();
-	if (vers < kSprfVersion_Uncompressed)
+	if (_version < kSprfVersion_Uncompressed)
 		topmost = 200;
 
 	_spriteData.resize(topmost + 1);
 	metrics.resize(topmost + 1);
 
+	// Version 12+: read global store flags
+	if (_version >= kSprfVersion_StorageFormats) {
+		_storeFlags = _stream->ReadInt8();
+		_stream->ReadInt8(); // reserved
+		_stream->ReadInt8();
+		_stream->ReadInt8();
+	}
+
 	// if there is a sprite index file, use it
 	if (LoadSpriteIndexFile(sprindex_filename, spriteFileID,
 		spr_initial_offs, topmost, metrics)) {
@@ -104,7 +213,7 @@ HError SpriteFile::OpenFile(const String &filename, const String &sprindex_filen
 	}
 
 	// Failed, index file is invalid; index sprites manually
-	return RebuildSpriteIndex(_stream.get(), topmost, vers, metrics);
+	return RebuildSpriteIndex(_stream.get(), topmost, _version, metrics);
 }
 
 void SpriteFile::Close() {
@@ -112,6 +221,10 @@ void SpriteFile::Close() {
 	_curPos = -2;
 }
 
+int SpriteFile::GetStoreFlags() const {
+	return _storeFlags;
+}
+
 bool SpriteFile::IsFileCompressed() const {
 	return _compressed;
 }
@@ -186,18 +299,36 @@ bool SpriteFile::LoadSpriteIndexFile(const String &filename, int expectedFileID,
 	return true;
 }
 
+static inline void ReadSprHeader(SpriteDatHeader &hdr, Stream *in,
+	const SpriteFileVersion ver, int gl_compress) {
+	int bpp = in->ReadInt8();
+	SpriteFormat sformat = (SpriteFormat)in->ReadInt8();
+	// note we MUST read first 2 * int8 before skipping rest
+	if (bpp == 0) {
+		hdr = SpriteDatHeader(); return;
+	} // empty slot
+	int compress = gl_compress, pal_count = 0;
+	if (ver >= kSprfVersion_StorageFormats) {
+		pal_count = (uint8_t)in->ReadInt8() + 1; // saved as (count - 1)
+		compress = in->ReadInt8();
+	}
+	int w = in->ReadInt16();
+	int h = in->ReadInt16();
+	hdr = SpriteDatHeader(bpp, sformat, pal_count, compress, w, h);
+}
+
 HError SpriteFile::RebuildSpriteIndex(Stream *in, sprkey_t topmost,
 	SpriteFileVersion vers, std::vector<Size> &metrics) {
 	topmost = std::min(topmost, (sprkey_t)_spriteData.size() - 1);
 	for (sprkey_t i = 0; !in->EOS() && (i <= topmost); ++i) {
 		_spriteData[i].Offset = in->GetPosition();
-		int bpp = in->ReadInt16();
-		if (bpp == 0) continue; // empty slot
-		int w = in->ReadInt16();
-		int h = in->ReadInt16();
-		metrics[i].Width = w;
-		metrics[i].Height = h;
-		size_t data_sz = _compressed ? in->ReadInt32() : w * h * bpp;
+		SpriteDatHeader hdr;
+		ReadSprHeader(hdr, _stream.get(), _version, _compressed ? 1 : 0);
+		if (hdr.BPP == 0) return HError::None(); // empty slot, this is normal
+		int pal_bpp = GetPaletteBPP(hdr.SFormat);
+		if (pal_bpp > 0) in->Seek(hdr.PalCount * pal_bpp); // skip palette
+		size_t data_sz = ((_version >= kSprfVersion_StorageFormats) || _compressed) ?
+			(uint32_t)in->ReadInt32() : hdr.Width * hdr.Height * hdr.BPP;
 		in->Seek(data_sz); // skip image data
 	}
 	return HError::None();
@@ -215,36 +346,55 @@ HError SpriteFile::LoadSprite(sprkey_t index, Shared::Bitmap *&sprite) {
 	SeekToSprite(index);
 	_curPos = -2; // mark undefined pos
 
-	int bpp = _stream->ReadInt16();
-	if (bpp == 0) { // empty slot, this is normal
-		return HError::None();
-	}
-	int w = _stream->ReadInt16();
-	int h = _stream->ReadInt16();
+	SpriteDatHeader hdr;
+	ReadSprHeader(hdr, _stream.get(), _version, _compressed ? 1 : 0);
+	if (hdr.BPP == 0) return HError::None(); // empty slot, this is normal
+	int bpp = hdr.BPP, w = hdr.Width, h = hdr.Height;
 	Bitmap *image = BitmapHelper::CreateBitmap(w, h, bpp * 8);
 	if (image == nullptr) {
 		return new Error(String::FromFormat("LoadSprite: failed to allocate bitmap %d (%dx%d%d).",
 			index, w, h, bpp * 8));
 	}
-
+	ImBufferPtr im_data(image->GetDataForWriting(), w * h * bpp, bpp);
+	// (Optional) Handle storage options, reverse
+	std::vector<uint8_t> indexed_buf;
+	uint32_t palette[256];
+	uint32_t pal_bpp = GetPaletteBPP(hdr.SFormat);
+	if (pal_bpp > 0) { // read palette if format assumes one
+		switch (pal_bpp) {
+		case 2: for (uint32_t i = 0; i < hdr.PalCount; ++i) palette[i] = _stream->ReadInt16(); break;
+		case 4: for (uint32_t i = 0; i < hdr.PalCount; ++i) palette[i] = _stream->ReadInt32(); break;
+		default: assert(0); break;
+		}
+		indexed_buf.resize(w * h);
+		im_data = ImBufferPtr(&indexed_buf[0], indexed_buf.size(), 1);
+	}
+	// (Optional) Decompress the image data into the temp buffer
+	size_t in_data_size = ((_version >= kSprfVersion_StorageFormats) || _compressed) ?
+		(uint32_t)_stream->ReadInt32() : (w * h * bpp);
 	if (_compressed) {
-		size_t data_size = _stream->ReadInt32();
-		if (data_size == 0) {
+		if (in_data_size == 0) {
 			delete image;
 			return new Error(String::FromFormat("LoadSprite: bad compressed data for sprite %d.", index));
 		}
-		rle_decompress(image->GetDataForWriting(), w * h * bpp, bpp, _stream.get());
+		rle_decompress(im_data.Buf, im_data.Size, im_data.BPP, _stream.get());
 		// TODO: test that not more than data_size was read!
-	} else {
-		switch (bpp) {
-		case 1: _stream->Read(image->GetDataForWriting(), w * h); break;
+	}
+	// Otherwise (no compression) read directly
+	else {
+		switch (im_data.BPP) {
+		case 1: _stream->Read(im_data.Buf, im_data.Size); break;
 		case 2: _stream->ReadArrayOfInt16(
-			reinterpret_cast<int16_t *>(image->GetDataForWriting()), w *h); break;
+			reinterpret_cast<int16_t *>(im_data.Buf), im_data.Size / sizeof(int16_t)); break;
 		case 4: _stream->ReadArrayOfInt32(
-			reinterpret_cast<int32_t *>(image->GetDataForWriting()), w *h); break;
-		default: assert(0); break;
+			reinterpret_cast<int32_t *>(im_data.Buf), im_data.Size / sizeof(int32_t)); break;
+		default: assert(0);
 		}
 	}
+	// Finally revert storage options
+	if (pal_bpp > 0) {
+		UnpackIndexedBitmap(image, im_data.Buf, im_data.Size, palette, hdr.PalCount);
+	}
 
 	sprite = image;
 	_curPos = index + 1; // mark correct pos
@@ -264,20 +414,19 @@ HError SpriteFile::LoadRawData(sprkey_t index, SpriteDatHeader &hdr, std::vector
 	SeekToSprite(index);
 	_curPos = -2; // mark undefined pos
 
-	int bpp = _stream->ReadInt16();
-	if (bpp == 0) { // empty slot, this is normal
-		return HError::None();
-	}
-	int w = _stream->ReadInt16();
-	int h = _stream->ReadInt16();
-	size_t data_size = w * h * bpp;
-	if (_compressed) {
-		data_size = _stream->ReadInt32() + sizeof(int32_t);
-		_stream->Seek(-4);
-	}
+	ReadSprHeader(hdr, _stream.get(), _version, _compressed ? 1 : 0);
+	if (hdr.BPP == 0) return HError::None(); // empty slot, this is normal
+	size_t data_size = 0;
+	soff_t data_pos = _stream->GetPosition();
+	// Optional palette
+	data_size += hdr.PalCount * GetPaletteBPP(hdr.SFormat);
+	if ((_version >= kSprfVersion_StorageFormats) || _compressed)
+		data_size += (uint32_t)_stream->ReadInt32() + sizeof(uint32_t);
+	else
+		data_size += hdr.Width * hdr.Height * hdr.BPP;
 	data.resize(data_size);
+	_stream->Seek(data_pos);
 	_stream->Read(&data[0], data_size);
-	hdr = SpriteDatHeader(bpp, w, h);
 
 	_curPos = index + 1; // mark correct pos
 	return HError::None();
@@ -291,6 +440,7 @@ void SpriteFile::SeekToSprite(sprkey_t index) {
 	}
 }
 
+
 // Finds the topmost occupied slot index. Warning: may be slow.
 static sprkey_t FindTopmostSprite(const std::vector<Bitmap *> &sprites) {
 	sprkey_t topmost = -1;
@@ -303,7 +453,7 @@ static sprkey_t FindTopmostSprite(const std::vector<Bitmap *> &sprites) {
 int SaveSpriteFile(const String &save_to_file,
 	const std::vector<Bitmap *> &sprites,
 	SpriteFile *read_from_file,
-	bool compressOutput, SpriteFileIndex &index) {
+	int store_flags, bool compress, SpriteFileIndex &index) {
 	std::unique_ptr<Stream> output(File::CreateFile(save_to_file));
 	if (output == nullptr)
 		return -1;
@@ -312,13 +462,16 @@ int SaveSpriteFile(const String &save_to_file,
 	lastslot = std::max(lastslot, FindTopmostSprite(sprites));
 
 	SpriteFileWriter writer(output);
-	writer.Begin(compressOutput, lastslot);
+	writer.Begin(store_flags, compress, lastslot);
 
 	std::unique_ptr<Bitmap> temp_bmp; // for disposing temp sprites
 	std::vector<uint8_t> membuf; // for loading raw sprite data
+	std::vector<uint32_t> palette;
 
 	const bool diff_compress =
-		read_from_file && read_from_file->IsFileCompressed() != compressOutput;
+		read_from_file &&
+		(read_from_file->IsFileCompressed() != compress ||
+			read_from_file->GetStoreFlags() != store_flags);
 
 	for (sprkey_t i = 0; i <= lastslot; ++i) {
 		Bitmap *image = (size_t)i < sprites.size() ? sprites[i] : nullptr;
@@ -382,10 +535,11 @@ int SaveSpriteIndex(const String &filename, const SpriteFileIndex &index) {
 SpriteFileWriter::SpriteFileWriter(std::unique_ptr<Stream> &out) : _out(out) {
 }
 
-void SpriteFileWriter::Begin(bool compressed, sprkey_t last_slot) {
+void SpriteFileWriter::Begin(int store_flags, bool compress, sprkey_t last_slot) {
 	if (!_out) return;
 	_index.SpriteFileIDCheck = g_system->getMillis();
-	_compress = compressed;
+	_storeFlags = store_flags;
+	_compress = compress;
 
 	// sprite file version
 	_out->WriteInt16(kSprfVersion_Current);
@@ -399,6 +553,11 @@ void SpriteFileWriter::Begin(bool compressed, sprkey_t last_slot) {
 	_lastSlotPos = _out->GetPosition();
 	_out->WriteInt32(last_slot);
 
+	_out->WriteInt8(_storeFlags);
+	_out->WriteInt8(0); // reserved
+	_out->WriteInt8(0);
+	_out->WriteInt8(0);
+
 	if (last_slot >= 0) { // allocate buffers to store the indexing info
 		sprkey_t numsprits = last_slot + 1;
 		_index.Offsets.reserve(numsprits);
@@ -412,32 +571,62 @@ void SpriteFileWriter::WriteBitmap(Bitmap *image) {
 	int bpp = image->GetBPP();
 	int w = image->GetWidth();
 	int h = image->GetHeight();
-	const SpriteDatHeader hdr(bpp, w, h);
+	ImBufferCPtr im_data(image->GetData(), w * h * bpp, bpp);
+	// (Optional) Handle storage options
+	std::vector<uint8_t> indexed_buf;
+	uint32_t palette[256];
+	uint32_t pal_count = 0;
+	SpriteFormat sformat = kSprFmt_Undefined;
+	int compress = 0;
+	if ((_storeFlags & kSprStore_OptimizeForSize) != 0 && (image->GetBPP() > 1)) { // Try to store this sprite as an indexed bitmap
+		if (CreateIndexedBitmap(image, indexed_buf, palette, pal_count) && pal_count > 0) {
+			sformat = PaletteFormatForBPP(image->GetBPP());
+			im_data = ImBufferCPtr(&indexed_buf[0], indexed_buf.size(), 1);
+		}
+	}
+	// (Optional) Compress the image data into the temp buffer
 	if (_compress) {
+		compress = 1;
 		MemoryStream mems(_membuf, kStream_Write);
-		rle_compress(image->GetData(), w * h * bpp, bpp, &mems);
-		// write image data as a plain byte array
-		WriteSpriteData(hdr, &_membuf[0], _membuf.size(), 1);
-		_membuf.clear();
-	} else {
-		WriteSpriteData(hdr, image->GetData(), w * h * bpp, bpp);
+		rle_compress(im_data.Buf, im_data.Size, im_data.BPP, &mems);
+		// mark to write as a plain byte array
+		im_data = ImBufferCPtr(&_membuf[0], _membuf.size(), 1);
 	}
+	// Write the final data
+	SpriteDatHeader hdr(bpp, sformat, pal_count, compress, w, h);
+	WriteSpriteData(hdr, im_data.Buf, im_data.Size, im_data.BPP, palette);
+	_membuf.clear();
+}
+
+static inline void WriteSprHeader(const SpriteDatHeader &hdr, Stream *out) {
+	out->WriteInt8(hdr.BPP);
+	out->WriteInt8(hdr.SFormat);
+	out->WriteInt8(hdr.PalCount > 0 ? (uint8_t)(hdr.PalCount - 1) : 0);
+	out->WriteInt8(hdr.Compress);
+	out->WriteInt16(hdr.Width);
+	out->WriteInt16(hdr.Height);
 }
 
 void SpriteFileWriter::WriteSpriteData(const SpriteDatHeader &hdr,
-	const uint8_t *im_data, size_t im_data_sz, int im_bpp) {
+	const uint8_t *im_data, size_t im_data_sz, int im_bpp,
+	const uint32_t palette[256]) {
 	// Add index entry and write resulting data to the stream
 	soff_t sproff = _out->GetPosition();
 	_index.Offsets.push_back(sproff);
 	_index.Widths.push_back(hdr.Width);
 	_index.Heights.push_back(hdr.Height);
-	_out->WriteInt16(hdr.BPP);
-	_out->WriteInt16(hdr.Width);
-	_out->WriteInt16(hdr.Height);
-	// if not compressed, then the data size is supposed to be calculated
-	// from the image metrics
-	if (_compress)
-		_out->WriteInt32(im_data_sz);
+	WriteSprHeader(hdr, _out.get());
+	// write palette, if available
+	int pal_bpp = GetPaletteBPP(hdr.SFormat);
+	if (pal_bpp > 0) {
+		assert(hdr.PalCount > 0);
+		switch (pal_bpp) {
+		case 2: for (uint32_t i = 0; i < hdr.PalCount; ++i) _out->WriteInt16(palette[i]);
+		case 4: for (uint32_t i = 0; i < hdr.PalCount; ++i) _out->WriteInt32(palette[i]);
+		}
+	}
+	// write the image pixel data
+	_out->WriteInt32(im_data_sz);
 	switch (im_bpp) {
 	case 1: _out->Write(im_data, im_data_sz); break;
 	case 2: _out->WriteArrayOfInt16(reinterpret_cast<const int16_t *>(im_data),
@@ -463,9 +652,7 @@ void SpriteFileWriter::WriteRawData(const SpriteDatHeader &hdr, const uint8_t *d
 	_index.Offsets.push_back(sproff);
 	_index.Widths.push_back(hdr.Width);
 	_index.Heights.push_back(hdr.Height);
-	_out->WriteInt16(hdr.BPP);
-	_out->WriteInt16(hdr.Width);
-	_out->WriteInt16(hdr.Height);
+	WriteSprHeader(hdr, _out.get());
 	_out->Write(data, data_sz);
 }
 
diff --git a/engines/ags/shared/ac/sprite_file.h b/engines/ags/shared/ac/sprite_file.h
index 4fde6949e72..26e000aa21d 100644
--- a/engines/ags/shared/ac/sprite_file.h
+++ b/engines/ags/shared/ac/sprite_file.h
@@ -50,7 +50,8 @@ enum SpriteFileVersion {
 	kSprfVersion_Last32bit = 6,
 	kSprfVersion_64bit = 10,
 	kSprfVersion_HighSpriteLimit = 11,
-	kSprfVersion_Current = kSprfVersion_HighSpriteLimit
+	kSprfVersion_StorageFormats = 12,
+	kSprfVersion_Current = kSprfVersion_StorageFormats
 };
 
 enum SpriteIndexFileVersion {
@@ -61,6 +62,24 @@ enum SpriteIndexFileVersion {
 	kSpridxfVersion_Current = kSpridxfVersion_HighSpriteLimit
 };
 
+// Instructions to how the sprites are allowed to be stored
+enum SpriteStorage {
+	// When possible convert the sprite into another format for less disk space
+	// e.g. save 16/32-bit images as 8-bit colormaps with palette
+	kSprStore_OptimizeForSize = 0x01
+};
+
+// Format in which the sprite's pixel data is stored
+enum SpriteFormat {
+	kSprFmt_Undefined = 0, // undefined, or keep as-is
+	// Encoded as a 8-bit colormap with palette of 24-bit RGB values
+	kSprFmt_PaletteRgb888 = 32,
+	// Encoded as a 8-bit colormap with palette of 32-bit ARGB values
+	kSprFmt_PaletteArgb8888 = 33,
+	// Encoded as a 8-bit colormap with palette of 16-bit RGB565 values
+	kSprFmt_PaletteRgb565 = 34
+};
+
 typedef int32_t sprkey_t;
 
 // SpriteFileIndex contains sprite file's table of contents
@@ -76,13 +95,18 @@ struct SpriteFileIndex {
 
 // Invidual sprite data header (as read from the file)
 struct SpriteDatHeader {
-	int BPP = 0; // color depth (bytes per pixel)
-	int Width = 0;
-	int Height = 0;
+	int BPP = 0; // color depth (bytes per pixel); or input format
+	SpriteFormat SFormat = kSprFmt_Undefined; // storage format
+	uint32_t PalCount = 0; // palette length, if applicable to storage format
+	int Compress = 0; // compression type
+	int Width = 0; // sprite's width
+	int Height = 0; // sprite's height
 
 	SpriteDatHeader() = default;
-	SpriteDatHeader(int bpp, int w = 0, int h = 0)
-		: BPP(bpp), Width(w), Height(h) {
+	SpriteDatHeader(int bpp, SpriteFormat sformat = kSprFmt_Undefined,
+		uint32_t pal_count = 0, int compress = 0, int w = 0, int h = 0)
+		: BPP(bpp), SFormat(sformat), PalCount(pal_count),
+		Compress(compress), Width(w), Height(h) {
 	}
 };
 
@@ -101,6 +125,7 @@ public:
 	// Closes stream; no reading will be possible unless opened again
 	void        Close();
 
+	int         GetStoreFlags() const;
 	// Tells if bitmaps in the file are compressed
 	bool        IsFileCompressed() const;
 	// Tells the highest known sprite index
@@ -117,6 +142,8 @@ public:
 	HError      LoadSprite(sprkey_t index, Bitmap *&sprite);
 	// Loads a raw sprite element data into the buffer, stores header info separately
 	HError      LoadRawData(sprkey_t index, SpriteDatHeader &hdr, std::vector<uint8_t> &data);
+	HError      LoadSpriteData(sprkey_t index, SpriteDatHeader &hdr, std::vector<uint8_t> &data,
+		std::vector<uint32_t> &palette);
 
 private:
 	// Seek stream to sprite
@@ -132,6 +159,8 @@ private:
 	// Array of sprite references
 	std::vector<SpriteRef> _spriteData;
 	std::unique_ptr<Stream> _stream; // the sprite stream
+	SpriteFileVersion _version = kSprfVersion_Current;
+	int _storeFlags = 0; // storage flags, specify how sprites may be stored
 	bool _compressed; // are sprites compressed
 	sprkey_t _curPos; // current stream position (sprite slot)
 };
@@ -148,7 +177,7 @@ public:
     const SpriteFileIndex &GetIndex() const { return _index; }
 
     // Initializes new sprite file format
-    void Begin(bool compress, sprkey_t last_slot = -1);
+    void Begin(int store_flags, bool compress, sprkey_t last_slot = -1);
     // Writes a bitmap into file, compressing if necessary
     void WriteBitmap(Bitmap *image);
     // Writes an empty slot marker
@@ -160,9 +189,12 @@ public:
 
 private:
 	// Writes prepared image data in a proper file format, following explicit data_bpp rule
-	void WriteSpriteData(const SpriteDatHeader &hdr, const uint8_t *im_data, size_t im_data_sz, int im_bpp);
+	void WriteSpriteData(const SpriteDatHeader &hdr,
+		const uint8_t *im_data, size_t im_data_sz, int im_bpp,
+		const uint32_t palette[256]);
 
 	std::unique_ptr<Stream> &_out;
+	int _storeFlags = 0;
     bool _compress = false;
     soff_t _lastSlotPos = -1; // last slot save position in file
     // sprite index accumulated on write for reporting back to user
@@ -176,7 +208,7 @@ private:
 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);
+	int store_flags, bool compress, SpriteFileIndex &index);
 // Saves sprite index table in a separate file
 int SaveSpriteIndex(const String &filename, const SpriteFileIndex &index);
 


Commit: 4392fa5e2d42f3021f6eff5fbf8fb85de2c39379
    https://github.com/scummvm/scummvm/commit/4392fa5e2d42f3021f6eff5fbf8fb85de2c39379
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-03-30T22:13:17-07:00

Commit Message:
AGS: Simplified savecompressed_allegro() and renamed for clarity

>From upstream 9a6b55bfe78bf0e9f24b2e3c2d1b073f850fae50

Changed paths:
    engines/ags/shared/game/room_file.cpp
    engines/ags/shared/util/compress.cpp
    engines/ags/shared/util/compress.h


diff --git a/engines/ags/shared/game/room_file.cpp b/engines/ags/shared/game/room_file.cpp
index f52bfb9c615..9d74ec0e3b7 100644
--- a/engines/ags/shared/game/room_file.cpp
+++ b/engines/ags/shared/game/room_file.cpp
@@ -298,28 +298,28 @@ HError ReadMainBlock(RoomStruct *room, Stream *in, RoomFileVersion data_ver) {
 	if (data_ver >= kRoomVersion_pre114_5)
 		load_lzw(in, &mask, room->BackgroundBPP, room->Palette);
 	else
-		loadcompressed_allegro(in, &mask, room->Palette);
+		mask = load_rle_bitmap8(in);
 	room->BgFrames[0].Graphic.reset(mask);
 
 	update_polled_stuff_if_runtime();
 	// Mask bitmaps
 	if (data_ver >= kRoomVersion_255b) {
-		loadcompressed_allegro(in, &mask, room->Palette);
+		mask = load_rle_bitmap8(in);
 	} else if (data_ver >= kRoomVersion_114) {
 		// an old version - clear the 'shadow' area into a blank regions bmp
-		loadcompressed_allegro(in, &mask, room->Palette);
+		mask = load_rle_bitmap8(in);
 		delete mask;
 		mask = nullptr;
 	}
 	room->RegionMask.reset(mask);
 	update_polled_stuff_if_runtime();
-	loadcompressed_allegro(in, &mask, room->Palette);
+	mask = load_rle_bitmap8(in);
 	room->WalkAreaMask.reset(mask);
 	update_polled_stuff_if_runtime();
-	loadcompressed_allegro(in, &mask, room->Palette);
+	mask = load_rle_bitmap8(in);
 	room->WalkBehindMask.reset(mask);
 	update_polled_stuff_if_runtime();
-	loadcompressed_allegro(in, &mask, room->Palette);
+	mask = load_rle_bitmap8(in);
 	room->HotspotMask.reset(mask);
 	return HError::None();
 }
@@ -748,10 +748,10 @@ void WriteMainBlock(const RoomStruct *room, Stream *out) {
 		out->WriteInt32(room->Regions[i].Tint);
 
 	save_lzw(out, room->BgFrames[0].Graphic.get(), room->Palette);
-	savecompressed_allegro(out, room->RegionMask.get(), room->Palette);
-	savecompressed_allegro(out, room->WalkAreaMask.get(), room->Palette);
-	savecompressed_allegro(out, room->WalkBehindMask.get(), room->Palette);
-	savecompressed_allegro(out, room->HotspotMask.get(), room->Palette);
+	save_rle_bitmap8(out, room->RegionMask.get());
+	save_rle_bitmap8(out, room->WalkAreaMask.get());
+	save_rle_bitmap8(out, room->WalkBehindMask.get());
+	save_rle_bitmap8(out, room->HotspotMask.get());
 }
 
 void WriteCompSc3Block(const RoomStruct *room, Stream *out) {
diff --git a/engines/ags/shared/util/compress.cpp b/engines/ags/shared/util/compress.cpp
index 268f3fb51c2..8b93d3e1203 100644
--- a/engines/ags/shared/util/compress.cpp
+++ b/engines/ags/shared/util/compress.cpp
@@ -151,36 +151,6 @@ static void cpackbitl32(const uint32_t *line, size_t size, Stream *out) {
 	} // end while
 }
 
-
-void csavecompressed(Stream *out, const unsigned char *tobesaved, const RGB pala[256]) {
-	int widt, hit;
-	widt = *tobesaved++;
-	widt += (*tobesaved++) * 256;
-	hit = *tobesaved++;
-	hit += (*tobesaved++) * 256;
-	// Those were originally written as shorts, although they are ints
-	out->WriteInt16(widt);
-	out->WriteInt16(hit);
-
-	unsigned char *ress = (unsigned char *)malloc(widt + 1);
-	int ww;
-
-	for (ww = 0; ww < hit; ww++) {
-		for (int ss = 0; ss < widt; ss++)
-			(*ress++) = (*tobesaved++);
-
-		ress -= widt;
-		cpackbitl(ress, widt, out);
-	}
-
-	for (ww = 0; ww < 256; ww++) {
-		out->WriteInt8(pala[ww].r);
-		out->WriteInt8(pala[ww].g);
-		out->WriteInt8(pala[ww].b);
-	}
-	free(ress);
-}
-
 static int cunpackbitl(uint8_t *line, size_t size, Stream *in) {
 	size_t n = 0;                  // number of bytes decoded
 
@@ -310,6 +280,46 @@ void rle_decompress(uint8_t *data, size_t data_sz, int image_bpp, Stream *in) {
 	}
 }
 
+void save_rle_bitmap8(Stream *out, const Bitmap *bmp, const RGB(*pal)[256]) {
+	assert(bmp->GetBPP() == 1);
+	out->WriteInt16(static_cast<uint16_t>(bmp->GetWidth()));
+	out->WriteInt16(static_cast<uint16_t>(bmp->GetHeight()));
+	// Pack the pixels
+	cpackbitl(bmp->GetData(), bmp->GetWidth() * bmp->GetHeight(), out);
+	// Save palette
+	if (!pal) { // if no pal, write dummy palette, because we have to
+		out->WriteByteCount(0, 256 * 3);
+		return;
+	}
+	const RGB *ppal = *pal;
+	for (int i = 0; i < 256; ++i) {
+		out->WriteInt8(ppal[i].r);
+		out->WriteInt8(ppal[i].g);
+		out->WriteInt8(ppal[i].b);
+	}
+}
+
+Shared::Bitmap *load_rle_bitmap8(Stream *in, RGB(*pal)[256]) {
+	int w = in->ReadInt16();
+	int h = in->ReadInt16();
+	Bitmap *bmp = BitmapHelper::CreateBitmap(w, h, 8);
+	if (!bmp) return nullptr;
+	// Unpack the pixels
+	cunpackbitl(bmp->GetDataForWriting(), w * h, in);
+	// Load or skip the palette
+	if (!pal) {
+		in->Seek(3 * 256);
+		return bmp;
+	}
+	RGB *ppal = *pal;
+	for (int i = 0; i < 256; ++i) {
+		ppal[i].r = in->ReadInt8();
+		ppal[i].g = in->ReadInt8();
+		ppal[i].b = in->ReadInt8();
+	}
+	return bmp;
+}
+
 //-----------------------------------------------------------------------------
 // LZW
 //-----------------------------------------------------------------------------
@@ -399,7 +409,7 @@ void load_lzw(Stream *in, Bitmap **dst_bmp, int dst_bpp, RGB *pall) {
 
 	Bitmap *bmm = BitmapHelper::CreateBitmap((loptr[0] / dst_bpp), loptr[1], dst_bpp * 8);
 	if (bmm == nullptr)
-		quit("!load_room: not enough memory to load room background");
+		quit("load_room: not enough memory to load room background");
 
 	update_polled_stuff_if_runtime();
 
@@ -418,37 +428,4 @@ void load_lzw(Stream *in, Bitmap **dst_bmp, int dst_bpp, RGB *pall) {
 	*dst_bmp = bmm;
 }
 
-void savecompressed_allegro(Stream *out, const Bitmap *bmpp, const RGB *pall) {
-	unsigned char *wgtbl = (unsigned char *)malloc(bmpp->GetWidth() * bmpp->GetHeight() + 4);
-	short *sss = (short *)wgtbl;
-
-	sss[0] = bmpp->GetWidth();
-	sss[1] = bmpp->GetHeight();
-
-	memcpy(&wgtbl[4], bmpp->GetData(), bmpp->GetWidth() * bmpp->GetHeight());
-
-	csavecompressed(out, wgtbl, pall);
-	free(wgtbl);
-}
-
-void loadcompressed_allegro(Stream *in, Bitmap **bimpp, RGB *pall) {
-	short widd, hitt;
-	int   ii;
-
-	widd = in->ReadInt16();
-	hitt = in->ReadInt16();
-	Bitmap *bim = BitmapHelper::CreateBitmap(widd, hitt, 8);
-	if (bim == nullptr)
-		quit("!load_room: not enough memory to decompress masks");
-
-	for (ii = 0; ii < hitt; ii++) {
-		cunpackbitl(&bim->GetScanLineForWriting(ii)[0], widd, in);
-		if (ii % 20 == 0)
-			update_polled_stuff_if_runtime();
-	}
-
-	in->Seek(768);  // skip palette
-	*bimpp = bim;
-}
-
 } // namespace AGS3
diff --git a/engines/ags/shared/util/compress.h b/engines/ags/shared/util/compress.h
index d9472c69c7e..01f4e039375 100644
--- a/engines/ags/shared/util/compress.h
+++ b/engines/ags/shared/util/compress.h
@@ -38,12 +38,14 @@ using namespace AGS; // FIXME later
 
 void rle_compress(const uint8_t *data, size_t data_sz, int image_bpp, Shared::Stream *out);
 void rle_decompress(uint8_t *data, size_t data_sz, int image_bpp, Shared::Stream *in);
+// Packs a 8-bit bitmap using RLE compression, and writes into stream along with the palette
+void save_rle_bitmap8(Shared::Stream *out, const Shared::Bitmap *bmp, const RGB(*pal)[256] = nullptr);
+// Reads a 8-bit bitmap with palette from the stream and unpacks from RLE
+Shared::Bitmap *load_rle_bitmap8(Shared::Stream *in, RGB(*pal)[256] = nullptr);
 
 // LZW compression
 void save_lzw(Shared::Stream *out, const Shared::Bitmap *bmpp, const RGB *pall);
 void load_lzw(Shared::Stream *in, Shared::Bitmap **bmm, int dst_bpp, RGB *pall);
-void savecompressed_allegro(Shared::Stream *out, const Shared::Bitmap *bmpp, const RGB *pall);
-void loadcompressed_allegro(Shared::Stream *in, Shared::Bitmap **bimpp, RGB *pall);
 
 } // namespace AGS3
 


Commit: 02540a3b4762b7f3bc5b9a8e7679e15014a6abd7
    https://github.com/scummvm/scummvm/commit/02540a3b4762b7f3bc5b9a8e7679e15014a6abd7
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-03-30T22:13:17-07:00

Commit Message:
AGS: In save_lzw() replaced the use of temp file with memory buffer

>From upstream 49045c7c5b6550d7724c4ceda822fbf4c3fc05a1

Changed paths:
    engines/ags/shared/game/room_file.cpp
    engines/ags/shared/util/compress.cpp
    engines/ags/shared/util/compress.h
    engines/ags/shared/util/lzw.cpp


diff --git a/engines/ags/shared/game/room_file.cpp b/engines/ags/shared/game/room_file.cpp
index 9d74ec0e3b7..f8f235ada69 100644
--- a/engines/ags/shared/game/room_file.cpp
+++ b/engines/ags/shared/game/room_file.cpp
@@ -296,7 +296,7 @@ HError ReadMainBlock(RoomStruct *room, Stream *in, RoomFileVersion data_ver) {
 	// Primary background
 	Bitmap *mask = nullptr;
 	if (data_ver >= kRoomVersion_pre114_5)
-		load_lzw(in, &mask, room->BackgroundBPP, room->Palette);
+		load_lzw(in, &mask, room->BackgroundBPP, &room->Palette);
 	else
 		mask = load_rle_bitmap8(in);
 	room->BgFrames[0].Graphic.reset(mask);
@@ -390,7 +390,7 @@ HError ReadAnimBgBlock(RoomStruct *room, Stream *in, RoomFileVersion data_ver) {
 	for (size_t i = 1; i < room->BgFrameCount; ++i) {
 		update_polled_stuff_if_runtime();
 		Bitmap *frame = nullptr;
-		load_lzw(in, &frame, room->BackgroundBPP, room->BgFrames[i].Palette);
+		load_lzw(in, &frame, room->BackgroundBPP, &room->BgFrames[i].Palette);
 		room->BgFrames[i].Graphic.reset(frame);
 	}
 	return HError::None();
@@ -747,7 +747,7 @@ void WriteMainBlock(const RoomStruct *room, Stream *out) {
 	for (size_t i = 0; i < (size_t)MAX_ROOM_REGIONS; ++i)
 		out->WriteInt32(room->Regions[i].Tint);
 
-	save_lzw(out, room->BgFrames[0].Graphic.get(), room->Palette);
+	save_lzw(out, room->BgFrames[0].Graphic.get(), &room->Palette);
 	save_rle_bitmap8(out, room->RegionMask.get());
 	save_rle_bitmap8(out, room->WalkAreaMask.get());
 	save_rle_bitmap8(out, room->WalkBehindMask.get());
@@ -777,7 +777,7 @@ void WriteAnimBgBlock(const RoomStruct *room, Stream *out) {
 	for (size_t i = 0; i < room->BgFrameCount; ++i)
 		out->WriteInt8(room->BgFrames[i].IsPaletteShared ? 1 : 0);
 	for (size_t i = 1; i < room->BgFrameCount; ++i)
-		save_lzw(out, room->BgFrames[i].Graphic.get(), room->BgFrames[i].Palette);
+		save_lzw(out, room->BgFrames[i].Graphic.get(), &room->BgFrames[i].Palette);
 }
 
 void WritePropertiesBlock(const RoomStruct *room, Stream *out) {
diff --git a/engines/ags/shared/util/compress.cpp b/engines/ags/shared/util/compress.cpp
index 8b93d3e1203..805d021af76 100644
--- a/engines/ags/shared/util/compress.cpp
+++ b/engines/ags/shared/util/compress.cpp
@@ -19,17 +19,12 @@
  *
  */
 
-#ifdef _MANAGED
-// ensure this doesn't get compiled to .NET IL
-#pragma unmanaged
-#endif
-
+#include "ags/shared/util/compress.h"
 #include "ags/shared/ac/common.h"   // quit, update_polled_stuff
 #include "ags/shared/gfx/bitmap.h"
-#include "ags/shared/util/compress.h"
 #include "ags/shared/util/file.h"
 #include "ags/shared/util/lzw.h"
-#include "ags/shared/util/stream.h"
+#include "ags/shared/util/memory_stream.h"
 #include "ags/globals.h"
 #if AGS_PLATFORM_ENDIAN_BIG
 #include "ags/shared/util/bbop.h"
@@ -324,52 +319,61 @@ Shared::Bitmap *load_rle_bitmap8(Stream *in, RGB(*pal)[256]) {
 // LZW
 //-----------------------------------------------------------------------------
 
-const char *lztempfnm = "~aclzw.tmp";
-
-void save_lzw(Stream *out, const Bitmap *bmpp, const RGB *pall) {
-	// First write original bitmap into temporary file
-	Stream *lz_temp_s = File::OpenFileCI(lztempfnm, kFile_CreateAlways, kFile_Write);
-	lz_temp_s->WriteInt32(bmpp->GetWidth() * bmpp->GetBPP());
-	lz_temp_s->WriteInt32(bmpp->GetHeight());
-	lz_temp_s->WriteArray(bmpp->GetData(), bmpp->GetLineLength(), bmpp->GetHeight());
-	delete lz_temp_s;
+void save_lzw(Stream *out, const Bitmap *bmpp, const RGB(*pal)[256]) {
+	// First write original bitmap's info and data into the memory buffer
+	// NOTE: we must do this purely for backward compatibility with old room formats:
+	// because they also included bmp width and height into compressed data!
+	std::vector<uint8_t> membuf;
+	{
+		MemoryStream memws(membuf, kStream_Write);
+		int w = bmpp->GetWidth(), h = bmpp->GetHeight(), bpp = bmpp->GetBPP();
+		memws.WriteInt32(w * bpp); // stride
+		memws.WriteInt32(h);
+		switch (bpp) {
+		case 1: memws.Write(bmpp->GetData(), w * h * bpp); break;
+		case 2: memws.WriteArrayOfInt16(reinterpret_cast<const int16_t *>(bmpp->GetData()), w *h); break;
+		case 4: memws.WriteArrayOfInt32(reinterpret_cast<const int32_t *>(bmpp->GetData()), w *h); break;
+		default: assert(0); break;
+		}
+	}
 
-	// Now open same file for reading, and begin writing compressed data into required output stream
-	lz_temp_s = File::OpenFileCI(lztempfnm);
-	soff_t temp_sz = lz_temp_s->GetLength();
-	out->WriteArray(&pall[0], sizeof(RGB), 256);
-	out->WriteInt32(temp_sz);
-	soff_t gobacto = out->GetPosition();
+	// Open same buffer for reading, and begin writing compressed data into the output
+	MemoryStream mem_in(membuf);
+	// NOTE: old format saves full RGB struct here (4 bytes, including the filler)
+	if (pal)
+		out->WriteArray(*pal, sizeof(RGB), 256);
+	else
+		out->WriteByteCount(0, sizeof(RGB) * 256);
+	out->WriteInt32((uint32_t)mem_in.GetLength());
 
 	// reserve space for compressed size
-	out->WriteInt32(temp_sz);
-	lzwcompress(lz_temp_s, out);
+	soff_t cmpsz_at = out->GetPosition();
+	out->WriteInt32(0);
+	lzwcompress(&mem_in, out);
 	soff_t toret = out->GetPosition();
-	out->Seek(gobacto, kSeekBegin);
-	soff_t compressed_sz = (toret - gobacto) - 4;
-	out->WriteInt32(compressed_sz);      // write compressed size
-
-	// Delete temp file
-	delete lz_temp_s;
-	File::DeleteFile(lztempfnm);
-
-	// Seek back to the end of the output stream
+	out->Seek(cmpsz_at, kSeekBegin);
+	soff_t compressed_sz = (toret - cmpsz_at) - sizeof(uint32_t);
+	out->WriteInt32(compressed_sz); // write compressed size
+	// seek back to the end of the output stream
 	out->Seek(toret, kSeekBegin);
 }
 
-void load_lzw(Stream *in, Bitmap **dst_bmp, int dst_bpp, RGB *pall) {
+void load_lzw(Stream *in, Bitmap **dst_bmp, int dst_bpp, RGB(*pal)[256]) {
 	soff_t        uncompsiz;
 	int *loptr;
 	unsigned char *membuffer;
 	int           arin;
 
-	in->Read(&pall[0], sizeof(RGB) * 256);
+	// NOTE: old format saves full RGB struct here (4 bytes, including the filler)
+	if (pal)
+		in->Read(*pal, sizeof(RGB) * 256);
+	else
+		in->Seek(sizeof(RGB) * 256);
 	_G(maxsize) = in->ReadInt32();
 	uncompsiz = in->ReadInt32();
 
 	uncompsiz += in->GetPosition();
-	_G(outbytes) = 0;
-	_G(putbytes) = 0;
+	_G(outbytes) = 0; _G(putbytes) = 0;
 
 	update_polled_stuff_if_runtime();
 	membuffer = lzwexpand_to_mem(in);
@@ -381,20 +385,24 @@ void load_lzw(Stream *in, Bitmap **dst_bmp, int dst_bpp, RGB *pall) {
 	loptr[0] = BBOp::SwapBytesInt32(loptr[0]);
 	loptr[1] = BBOp::SwapBytesInt32(loptr[1]);
 	int bitmapNumPixels = loptr[0] * loptr[1] / dst_bpp;
-	switch (dst_bpp) { // bytes per pixel!
-	case 1: {
+	switch (dst_bpp) // bytes per pixel!
+	{
+	case 1:
+	{
 		// all done
 		break;
 	}
-	case 2: {
+	case 2:
+	{
 		short *sp = (short *)membuffer;
 		for (int i = 0; i < bitmapNumPixels; ++i) {
 			sp[i] = BBOp::SwapBytesInt16(sp[i]);
-		}
+}
 		// all done
 		break;
 	}
-	case 4: {
+	case 4:
+	{
 		int *ip = (int *)membuffer;
 		for (int i = 0; i < bitmapNumPixels; ++i) {
 			ip[i] = BBOp::SwapBytesInt32(ip[i]);
@@ -402,7 +410,7 @@ void load_lzw(Stream *in, Bitmap **dst_bmp, int dst_bpp, RGB *pall) {
 		// all done
 		break;
 	}
-	}
+  }
 #endif // AGS_PLATFORM_ENDIAN_BIG
 
 	update_polled_stuff_if_runtime();
diff --git a/engines/ags/shared/util/compress.h b/engines/ags/shared/util/compress.h
index 01f4e039375..cda9bd8708b 100644
--- a/engines/ags/shared/util/compress.h
+++ b/engines/ags/shared/util/compress.h
@@ -44,8 +44,10 @@ void save_rle_bitmap8(Shared::Stream *out, const Shared::Bitmap *bmp, const RGB(
 Shared::Bitmap *load_rle_bitmap8(Shared::Stream *in, RGB(*pal)[256] = nullptr);
 
 // LZW compression
-void save_lzw(Shared::Stream *out, const Shared::Bitmap *bmpp, const RGB *pall);
-void load_lzw(Shared::Stream *in, Shared::Bitmap **bmm, int dst_bpp, RGB *pall);
+// Saves bitmap with an optional palette compressed by LZW
+void save_lzw(Shared::Stream *out, const Shared::Bitmap *bmpp, const RGB(*pal)[256] = nullptr);
+// Loads bitmap decompressing
+void load_lzw(Shared::Stream *in, Shared::Bitmap **bmm, int dst_bpp, RGB(*pal)[256] = nullptr);
 
 } // namespace AGS3
 
diff --git a/engines/ags/shared/util/lzw.cpp b/engines/ags/shared/util/lzw.cpp
index d6efad5a35d..e8cd0ad9140 100644
--- a/engines/ags/shared/util/lzw.cpp
+++ b/engines/ags/shared/util/lzw.cpp
@@ -178,7 +178,7 @@ void lzwcompress(Stream *lzw_in, Stream *out) {
 			}
 
 			if (!((mask += mask) & 0xFF)) {
-				out->WriteArray(buf, size, 1);
+				out->Write(buf, size);
 				_G(outbytes) += size;
 				size = mask = 1;
 				buf[0] = 0;
@@ -188,7 +188,7 @@ void lzwcompress(Stream *lzw_in, Stream *out) {
 	} while (len > 0);
 
 	if (size > 1) {
-		out->WriteArray(buf, size, 1);
+		out->Write(buf, size);
 		_G(outbytes) += size;
 	}
 


Commit: dc7609a79c663dcfe3ee07298b61cb6c35f55e0f
    https://github.com/scummvm/scummvm/commit/dc7609a79c663dcfe3ee07298b61cb6c35f55e0f
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-03-30T22:13:18-07:00

Commit Message:
AGS: Removed lzwexpand_to_mem(), tidied load_lzw()

>From upstream 2b9f130e119f9cfc81d0257a446edc4e31df19c0

Changed paths:
    engines/ags/globals.h
    engines/ags/shared/util/compress.cpp
    engines/ags/shared/util/lzw.cpp
    engines/ags/shared/util/lzw.h
    engines/ags/shared/util/memory_stream.cpp
    engines/ags/shared/util/memory_stream.h


diff --git a/engines/ags/globals.h b/engines/ags/globals.h
index d6418494d56..e8ccca6985f 100644
--- a/engines/ags/globals.h
+++ b/engines/ags/globals.h
@@ -1054,7 +1054,7 @@ public:
 	char *_lzbuffer = nullptr;
 	int *_node = nullptr;
 	int _pos = 0;
-	long _outbytes = 0, _maxsize = 0, _putbytes = 0;
+	size_t _outbytes = 0, _maxsize = 0, _putbytes = 0;
 
 	/**@}*/
 
diff --git a/engines/ags/shared/util/compress.cpp b/engines/ags/shared/util/compress.cpp
index 805d021af76..5b4ea74cab0 100644
--- a/engines/ags/shared/util/compress.cpp
+++ b/engines/ags/shared/util/compress.cpp
@@ -359,79 +359,42 @@ void save_lzw(Stream *out, const Bitmap *bmpp, const RGB(*pal)[256]) {
 }
 
 void load_lzw(Stream *in, Bitmap **dst_bmp, int dst_bpp, RGB(*pal)[256]) {
-	soff_t        uncompsiz;
-	int *loptr;
-	unsigned char *membuffer;
-	int           arin;
-
+	*dst_bmp = nullptr;
 	// NOTE: old format saves full RGB struct here (4 bytes, including the filler)
 	if (pal)
 		in->Read(*pal, sizeof(RGB) * 256);
 	else
 		in->Seek(sizeof(RGB) * 256);
-	_G(maxsize) = in->ReadInt32();
-	uncompsiz = in->ReadInt32();
-
-	uncompsiz += in->GetPosition();
-	_G(outbytes) = 0; _G(putbytes) = 0;
-
-	update_polled_stuff_if_runtime();
-	membuffer = lzwexpand_to_mem(in);
-	update_polled_stuff_if_runtime();
+	const size_t uncomp_sz = in->ReadInt32();
+	const size_t comp_sz = in->ReadInt32();
+	const soff_t end_pos = in->GetPosition() + comp_sz;
 
-	loptr = (int *)&membuffer[0];
-	membuffer += 8;
-#if AGS_PLATFORM_ENDIAN_BIG
-	loptr[0] = BBOp::SwapBytesInt32(loptr[0]);
-	loptr[1] = BBOp::SwapBytesInt32(loptr[1]);
-	int bitmapNumPixels = loptr[0] * loptr[1] / dst_bpp;
-	switch (dst_bpp) // bytes per pixel!
-	{
-	case 1:
-	{
-		// all done
-		break;
-	}
-	case 2:
-	{
-		short *sp = (short *)membuffer;
-		for (int i = 0; i < bitmapNumPixels; ++i) {
-			sp[i] = BBOp::SwapBytesInt16(sp[i]);
-}
-		// all done
-		break;
-	}
-	case 4:
+	// First decompress data into the memory buffer
+	std::vector<uint8_t> membuf;
 	{
-		int *ip = (int *)membuffer;
-		for (int i = 0; i < bitmapNumPixels; ++i) {
-			ip[i] = BBOp::SwapBytesInt32(ip[i]);
-		}
-		// all done
-		break;
+		MemoryStream memws(membuf, kStream_Write);
+		lzwexpand(in, &memws, uncomp_sz);
 	}
-  }
-#endif // AGS_PLATFORM_ENDIAN_BIG
-
-	update_polled_stuff_if_runtime();
 
-	Bitmap *bmm = BitmapHelper::CreateBitmap((loptr[0] / dst_bpp), loptr[1], dst_bpp * 8);
+	// Open same buffer for reading and get params and pixels
+	MemoryStream mem_in(membuf);
+	int stride = mem_in.ReadInt32(); // width * bpp
+	int height = mem_in.ReadInt32();
+	Bitmap *bmm = BitmapHelper::CreateBitmap((stride / dst_bpp), height, dst_bpp * 8);
 	if (bmm == nullptr)
-		quit("load_room: not enough memory to load room background");
-
-	update_polled_stuff_if_runtime();
-
-	for (arin = 0; arin < loptr[1]; arin++)
-		memcpy(&bmm->GetScanLineForWriting(arin)[0], &membuffer[arin * loptr[0]], loptr[0]);
-
-	update_polled_stuff_if_runtime();
-
-	free(membuffer - 8);
-
-	if (in->GetPosition() != uncompsiz)
-		in->Seek(uncompsiz, kSeekBegin);
+		return; // out of mem?
+
+	size_t num_pixels = stride * height / dst_bpp;
+	uint8_t *bmp_data = bmm->GetDataForWriting();
+	switch (dst_bpp) {
+	case 1: mem_in.Read(bmp_data, num_pixels); break;
+	case 2: mem_in.ReadArrayOfInt16(reinterpret_cast<int16_t *>(bmp_data), num_pixels); break;
+	case 4: mem_in.ReadArrayOfInt32(reinterpret_cast<int32_t *>(bmp_data), num_pixels); break;
+	default: assert(0); break;
+	}
 
-	update_polled_stuff_if_runtime();
+	if (in->GetPosition() != end_pos)
+		in->Seek(end_pos, kSeekBegin);
 
 	*dst_bmp = bmm;
 }
diff --git a/engines/ags/shared/util/lzw.cpp b/engines/ags/shared/util/lzw.cpp
index e8cd0ad9140..f0d4cb7a7d2 100644
--- a/engines/ags/shared/util/lzw.cpp
+++ b/engines/ags/shared/util/lzw.cpp
@@ -195,8 +195,6 @@ void lzwcompress(Stream *lzw_in, Stream *out) {
 	free(_G(lzbuffer));
 }
 
-int expand_to_mem = 0;
-unsigned char *membfptr = nullptr;
 void myputc(int ccc, Stream *out) {
 	if (_G(maxsize) > 0) {
 		_G(putbytes)++;
@@ -205,21 +203,17 @@ void myputc(int ccc, Stream *out) {
 	}
 
 	_G(outbytes)++;
-	if (expand_to_mem) {
-		membfptr[0] = ccc;
-		membfptr++;
-	} else
-		out->WriteInt8(ccc);
+	out->WriteInt8(ccc);
 }
 
-void lzwexpand(Stream *lzw_in, Stream *out) {
+void lzwexpand(Stream *lzw_in, Stream *out, size_t out_size) {
 	int bits, ch, i, j, len, mask;
-	char *buf;
-	//  printf(" UnShrinking: %s ",filena);
-	_G(putbytes) = 0;
+	char *lzbuffer;
+	_G(outbytes) = 0; _G(putbytes) = 0;
+	_G(maxsize) = out_size;
 
-	buf = (char *)malloc(N);
-	if (buf == nullptr) {
+	lzbuffer = (char *)malloc(N);
+	if (lzbuffer == nullptr) {
 		quit("compress.cpp: unable to decompress: insufficient memory");
 	}
 	i = N - F;
@@ -237,13 +231,13 @@ void lzwexpand(Stream *lzw_in, Stream *out) {
 				j = (i - j - 1) & (N - 1);
 
 				while (len--) {
-					myputc(buf[i] = buf[j], out);
+					myputc(lzbuffer[i] = lzbuffer[j], out);
 					j = (j + 1) & (N - 1);
 					i = (i + 1) & (N - 1);
 				}
 			} else {
 				ch = lzw_in->ReadByte();
-				myputc(buf[i] = ch, out);
+				myputc(lzbuffer[i] = ch, out);
 				i = (i + 1) & (N - 1);
 			}
 
@@ -258,16 +252,7 @@ void lzwexpand(Stream *lzw_in, Stream *out) {
 			break;
 	}
 
-	free(buf);
-	expand_to_mem = 0;
-}
-
-unsigned char *lzwexpand_to_mem(Stream *in) {
-	unsigned char *membuff = (unsigned char *)malloc(_G(maxsize) + 10);
-	expand_to_mem = 1;
-	membfptr = membuff;
-	lzwexpand(in, nullptr);
-	return membuff;
+	free(lzbuffer);
 }
 
 } // namespace AGS3
diff --git a/engines/ags/shared/util/lzw.h b/engines/ags/shared/util/lzw.h
index 066af919e4d..feb86829a2b 100644
--- a/engines/ags/shared/util/lzw.h
+++ b/engines/ags/shared/util/lzw.h
@@ -33,7 +33,7 @@ class Stream;
 using namespace AGS; // FIXME later
 
 void lzwcompress(Shared::Stream *lzw_in, Shared::Stream *out);
-unsigned char *lzwexpand_to_mem(Shared::Stream *in);
+void lzwexpand(Shared::Stream *lzw_in, Shared::Stream *out, size_t out_size);
 
 } // namespace AGS3
 
diff --git a/engines/ags/shared/util/memory_stream.cpp b/engines/ags/shared/util/memory_stream.cpp
index b49aa6ebcc9..d9b2c55446c 100644
--- a/engines/ags/shared/util/memory_stream.cpp
+++ b/engines/ags/shared/util/memory_stream.cpp
@@ -35,6 +35,15 @@ MemoryStream::MemoryStream(const std::vector<uint8_t> &cbuf, DataEndianess strea
 	, _pos(0) {
 }
 
+MemoryStream::MemoryStream(const uint8_t *cbuf, size_t buf_sz, DataEndianess stream_endianess)
+	: DataStream(stream_endianess)
+	, _cbuf(cbuf)
+	, _len(buf_sz)
+	, _buf(nullptr)
+	, _mode(kStream_Read)
+	, _pos(0) {
+}
+
 MemoryStream::MemoryStream(const String &cbuf, DataEndianess stream_endianess)
 	: DataStream(stream_endianess)
 	, _cbuf(reinterpret_cast<const uint8_t *>(cbuf.GetCStr()))
diff --git a/engines/ags/shared/util/memory_stream.h b/engines/ags/shared/util/memory_stream.h
index 66889ca5ba1..245bbda2b74 100644
--- a/engines/ags/shared/util/memory_stream.h
+++ b/engines/ags/shared/util/memory_stream.h
@@ -47,6 +47,9 @@ public:
 	// Construct memory stream in the read-only mode over a const std::vector;
 	// vector must persist in memory until the stream is closed.
 	MemoryStream(const std::vector<uint8_t> &cbuf, DataEndianess stream_endianess = kLittleEndian);
+	// Construct memory stream in the read-only mode over a const C-buffer;
+	// buffer must persist in memory until the stream is closed.
+	MemoryStream(const uint8_t *cbuf, size_t buf_sz, DataEndianess stream_endianess = kLittleEndian);
 	// Construct memory stream in the read-only mode over a const String;
 	// String object must persist in memory until the stream is closed.
 	MemoryStream(const String &cbuf, DataEndianess stream_endianess = kLittleEndian);


Commit: eecd211bb69accd933e5785d4dc85077cea327be
    https://github.com/scummvm/scummvm/commit/eecd211bb69accd933e5785d4dc85077cea327be
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-03-30T22:13:18-07:00

Commit Message:
AGS: Don't call quit() from lzw functions

>From upstream e5d8375df05b90e1ef51bae054da840071175aa3

Changed paths:
    engines/ags/shared/util/lzw.cpp
    engines/ags/shared/util/lzw.h


diff --git a/engines/ags/shared/util/lzw.cpp b/engines/ags/shared/util/lzw.cpp
index f0d4cb7a7d2..4bc1cc9143a 100644
--- a/engines/ags/shared/util/lzw.cpp
+++ b/engines/ags/shared/util/lzw.cpp
@@ -25,6 +25,7 @@
 //
 //=============================================================================
 
+#include "ags/shared/util/lzw.h"
 #include "ags/shared/ac/common.h" // quit
 #include "ags/shared/util/stream.h"
 #include "ags/globals.h"
@@ -122,13 +123,13 @@ void _delete(int z) {
 	}
 }
 
-void lzwcompress(Stream *lzw_in, Stream *out) {
+bool lzwcompress(Stream *lzw_in, Stream *out) {
 	int ch, i, run, len, match, size, mask;
 	char buf[17];
 
 	_G(lzbuffer) = (char *)malloc(N + F + (N + 1 + N + N + 256) * sizeof(int));       // 28.5 k !
 	if (_G(lzbuffer) == nullptr) {
-		quit("unable to compress: out of memory");
+		return false;
 	}
 
 	_G(node) = (int *)(_G(lzbuffer) + N + F);
@@ -193,6 +194,7 @@ void lzwcompress(Stream *lzw_in, Stream *out) {
 	}
 
 	free(_G(lzbuffer));
+	return true;
 }
 
 void myputc(int ccc, Stream *out) {
@@ -206,7 +208,7 @@ void myputc(int ccc, Stream *out) {
 	out->WriteInt8(ccc);
 }
 
-void lzwexpand(Stream *lzw_in, Stream *out, size_t out_size) {
+bool lzwexpand(Stream *lzw_in, Stream *out, size_t out_size) {
 	int bits, ch, i, j, len, mask;
 	char *lzbuffer;
 	_G(outbytes) = 0; _G(putbytes) = 0;
@@ -214,7 +216,7 @@ void lzwexpand(Stream *lzw_in, Stream *out, size_t out_size) {
 
 	lzbuffer = (char *)malloc(N);
 	if (lzbuffer == nullptr) {
-		quit("compress.cpp: unable to decompress: insufficient memory");
+		return false;
 	}
 	i = N - F;
 
@@ -244,8 +246,10 @@ void lzwexpand(Stream *lzw_in, Stream *out, size_t out_size) {
 			if ((_G(putbytes) >= _G(maxsize)) && (_G(maxsize) > 0))
 				break;
 
-			if ((lzw_in->EOS()) && (_G(maxsize) > 0))
-				quit("Read error decompressing image - file is corrupt");
+			if ((lzw_in->EOS()) && (_G(maxsize) > 0)) {
+				free(lzbuffer);
+				return false;
+			}
 		}                           // end for mask
 
 		if ((_G(putbytes) >= _G(maxsize)) && (_G(maxsize) > 0))
@@ -253,6 +257,7 @@ void lzwexpand(Stream *lzw_in, Stream *out, size_t out_size) {
 	}
 
 	free(lzbuffer);
+	return true;
 }
 
 } // namespace AGS3
diff --git a/engines/ags/shared/util/lzw.h b/engines/ags/shared/util/lzw.h
index feb86829a2b..15a5f290c36 100644
--- a/engines/ags/shared/util/lzw.h
+++ b/engines/ags/shared/util/lzw.h
@@ -32,8 +32,8 @@ class Stream;
 
 using namespace AGS; // FIXME later
 
-void lzwcompress(Shared::Stream *lzw_in, Shared::Stream *out);
-void lzwexpand(Shared::Stream *lzw_in, Shared::Stream *out, size_t out_size);
+bool lzwcompress(Shared::Stream *lzw_in, Shared::Stream *out);
+bool lzwexpand(Shared::Stream *lzw_in, Shared::Stream *out, size_t out_size);
 
 } // namespace AGS3
 


Commit: 897724f110bbc68fac5d025951ac6fd417fa3835
    https://github.com/scummvm/scummvm/commit/897724f110bbc68fac5d025951ac6fd417fa3835
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-03-30T22:13:18-07:00

Commit Message:
AGS: Split VectorStream from MemoryStream and allow write C-buffer

>From upstream f8d1d99bcbdadfc6f1380d978d2c7261200a73c8

Changed paths:
    engines/ags/shared/ac/sprite_file.cpp
    engines/ags/shared/util/compress.cpp
    engines/ags/shared/util/memory_stream.cpp
    engines/ags/shared/util/memory_stream.h


diff --git a/engines/ags/shared/ac/sprite_file.cpp b/engines/ags/shared/ac/sprite_file.cpp
index 34bc74af5cf..2b69ae46b42 100644
--- a/engines/ags/shared/ac/sprite_file.cpp
+++ b/engines/ags/shared/ac/sprite_file.cpp
@@ -587,7 +587,7 @@ void SpriteFileWriter::WriteBitmap(Bitmap *image) {
 	// (Optional) Compress the image data into the temp buffer
 	if (_compress) {
 		compress = 1;
-		MemoryStream mems(_membuf, kStream_Write);
+		VectorStream mems(_membuf, kStream_Write);
 		rle_compress(im_data.Buf, im_data.Size, im_data.BPP, &mems);
 		// mark to write as a plain byte array
 		im_data = ImBufferCPtr(&_membuf[0], _membuf.size(), 1);
diff --git a/engines/ags/shared/util/compress.cpp b/engines/ags/shared/util/compress.cpp
index 5b4ea74cab0..4c3a981dd3b 100644
--- a/engines/ags/shared/util/compress.cpp
+++ b/engines/ags/shared/util/compress.cpp
@@ -325,7 +325,7 @@ void save_lzw(Stream *out, const Bitmap *bmpp, const RGB(*pal)[256]) {
 	// because they also included bmp width and height into compressed data!
 	std::vector<uint8_t> membuf;
 	{
-		MemoryStream memws(membuf, kStream_Write);
+		VectorStream memws(membuf, kStream_Write);
 		int w = bmpp->GetWidth(), h = bmpp->GetHeight(), bpp = bmpp->GetBPP();
 		memws.WriteInt32(w * bpp); // stride
 		memws.WriteInt32(h);
@@ -338,7 +338,7 @@ void save_lzw(Stream *out, const Bitmap *bmpp, const RGB(*pal)[256]) {
 	}
 
 	// Open same buffer for reading, and begin writing compressed data into the output
-	MemoryStream mem_in(membuf);
+	VectorStream mem_in(membuf);
 	// NOTE: old format saves full RGB struct here (4 bytes, including the filler)
 	if (pal)
 		out->WriteArray(*pal, sizeof(RGB), 256);
@@ -372,12 +372,12 @@ void load_lzw(Stream *in, Bitmap **dst_bmp, int dst_bpp, RGB(*pal)[256]) {
 	// First decompress data into the memory buffer
 	std::vector<uint8_t> membuf;
 	{
-		MemoryStream memws(membuf, kStream_Write);
+		VectorStream memws(membuf, kStream_Write);
 		lzwexpand(in, &memws, uncomp_sz);
 	}
 
 	// Open same buffer for reading and get params and pixels
-	MemoryStream mem_in(membuf);
+	VectorStream mem_in(membuf);
 	int stride = mem_in.ReadInt32(); // width * bpp
 	int height = mem_in.ReadInt32();
 	Bitmap *bmm = BitmapHelper::CreateBitmap((stride / dst_bpp), height, dst_bpp * 8);
diff --git a/engines/ags/shared/util/memory_stream.cpp b/engines/ags/shared/util/memory_stream.cpp
index d9b2c55446c..30704814f9d 100644
--- a/engines/ags/shared/util/memory_stream.cpp
+++ b/engines/ags/shared/util/memory_stream.cpp
@@ -26,45 +26,26 @@ namespace AGS3 {
 namespace AGS {
 namespace Shared {
 
-MemoryStream::MemoryStream(const std::vector<uint8_t> &cbuf, DataEndianess stream_endianess)
-	: DataStream(stream_endianess)
-	, _cbuf(&cbuf.front())
-	, _len(cbuf.size())
-	, _buf(nullptr)
-	, _mode(kStream_Read)
-	, _pos(0) {
-}
-
 MemoryStream::MemoryStream(const uint8_t *cbuf, size_t buf_sz, DataEndianess stream_endianess)
 	: DataStream(stream_endianess)
 	, _cbuf(cbuf)
+	, _buf_sz(buf_sz)
 	, _len(buf_sz)
 	, _buf(nullptr)
 	, _mode(kStream_Read)
 	, _pos(0) {
 }
 
-MemoryStream::MemoryStream(const String &cbuf, DataEndianess stream_endianess)
+MemoryStream::MemoryStream(uint8_t *buf, size_t buf_sz, StreamWorkMode mode, DataEndianess stream_endianess)
 	: DataStream(stream_endianess)
-	, _cbuf(reinterpret_cast<const uint8_t *>(cbuf.GetCStr()))
-	, _len(cbuf.GetLength())
-	, _buf(nullptr)
+	, _buf(buf)
+	, _buf_sz(buf_sz)
+	, _len(0)
+	, _cbuf(nullptr)
 	, _mode(kStream_Read)
 	, _pos(0) {
 }
 
-MemoryStream::MemoryStream(std::vector<uint8_t> &buf, StreamWorkMode mode, DataEndianess stream_endianess)
-	: DataStream(stream_endianess)
-	, _len(buf.size())
-	, _buf(&buf)
-	, _mode(mode)
-	, _pos(buf.size()) {
-	_cbuf = (mode == kStream_Read) ? &buf.front() : nullptr;
-}
-
-MemoryStream::~MemoryStream() {
-}
-
 void MemoryStream::Close() {
 	_cbuf = nullptr;
 	_buf = nullptr;
@@ -122,40 +103,76 @@ int32_t MemoryStream::ReadByte() {
 	return _cbuf[(size_t)(_pos++)];
 }
 
+bool MemoryStream::Seek(soff_t offset, StreamSeek origin) {
+	if (!CanSeek()) {
+		return false;
+	}
+	switch (origin) {
+	case kSeekBegin:    _pos = 0 + offset; break;
+	case kSeekCurrent:  _pos = _pos + offset; break;
+	case kSeekEnd:      _pos = _len + offset; break;
+	default:
+		return false;
+	}
+	_pos = std::max<soff_t>(0, _pos);
+	_pos = std::min<soff_t>(_len, _pos); // clamp to EOS
+	return true;
+}
+
 size_t MemoryStream::Write(const void *buffer, size_t size) {
-	if (!_buf) {
+	if (!_buf || _pos >= _buf_sz) {
 		return 0;
 	}
-	_buf->resize(_buf->size() + size);
-	memcpy(_buf->data() + _pos, buffer, size);
+	size = std::min(size, _buf_sz - (size_t)_pos);
+	memcpy(_buf + _pos, buffer, size);
 	_pos += size;
 	_len += size;
 	return size;
 }
 
 int32_t MemoryStream::WriteByte(uint8_t val) {
-	if (!_buf) {
+	if (!_buf || _pos >= _buf_sz) {
 		return -1;
 	}
-	_buf->push_back(val);
+	*(_buf + _pos) = val;
 	_pos++; _len++;
 	return val;
 }
 
-bool MemoryStream::Seek(soff_t offset, StreamSeek origin) {
-	if (!CanSeek()) {
-		return false;
+
+VectorStream::VectorStream(const std::vector<uint8_t> &cbuf, DataEndianess stream_endianess)
+	: MemoryStream(&cbuf.front(), cbuf.size(), stream_endianess)
+	, _vec(nullptr) {
+}
+
+VectorStream::VectorStream(std::vector<uint8_t> &buf, StreamWorkMode mode, DataEndianess stream_endianess)
+	: MemoryStream((mode == kStream_Read) ? &buf.front() : nullptr, buf.size(), mode, stream_endianess)
+	, _vec(&buf) {
+}
+
+void VectorStream::Close() {
+	_vec = nullptr;
+	MemoryStream::Close();
+}
+
+size_t VectorStream::Write(const void *buffer, size_t size) {
+	if (!_vec) {
+		return 0;
 	}
-	switch (origin) {
-	case kSeekBegin:    _pos = 0 + offset; break;
-	case kSeekCurrent:  _pos = _pos + offset; break;
-	case kSeekEnd:      _pos = _len + offset; break;
-	default:
-		return false;
+	_vec->resize(_vec->size() + size);
+	memcpy(_vec->data() + _pos, buffer, size);
+	_pos += size;
+	_len += size;
+	return size;
+}
+
+int32_t VectorStream::WriteByte(uint8_t val) {
+	if (!_vec) {
+		return -1;
 	}
-	_pos = std::max<soff_t>(0, _pos);
-	_pos = std::min<soff_t>(_len, _pos); // clamp to EOS
-	return true;
+	_vec->push_back(val);
+	_pos++; _len++;
+	return val;
 }
 
 } // namespace Shared
diff --git a/engines/ags/shared/util/memory_stream.h b/engines/ags/shared/util/memory_stream.h
index 245bbda2b74..d50526a38fb 100644
--- a/engines/ags/shared/util/memory_stream.h
+++ b/engines/ags/shared/util/memory_stream.h
@@ -19,17 +19,18 @@
  *
  */
 
-//=============================================================================
-//
-// MemoryStream does reading and writing over the buffer of bytes stored in
-// memory. Currently has rather trivial implementation. Does not own a buffer
-// itself, but works with the provided std::vector reference, which means that
-// the buffer *must* persist until stream is closed.
-// TODO: perhaps accept const char* for reading mode, for compatibility with
-// the older code, and maybe to let also read String objects?
-// TODO: separate StringStream for reading & writing String object?
-//
-//=============================================================================
+ //=============================================================================
+ //
+ // MemoryStream does reading and writing over the buffer of bytes stored in
+ // memory. Currently has rather trivial implementation. Does not own a buffer
+ // itself, but works with the provided C-buffer pointer, which means that the
+ // buffer object *must* persist until stream is closed.
+ //
+ // VectorStream is a specialized implementation that works with std::vector.
+ // Unlike base MemoryStream provides continiously resizing buffer for writing.
+ // TODO: separate StringStream for reading & writing String object?
+ //
+ //=============================================================================
 
 #ifndef AGS_SHARED_UTIL_MEMORY_STREAM_H
 #define AGS_SHARED_UTIL_MEMORY_STREAM_H
@@ -44,19 +45,15 @@ namespace Shared {
 
 class MemoryStream : public DataStream {
 public:
-	// Construct memory stream in the read-only mode over a const std::vector;
-	// vector must persist in memory until the stream is closed.
-	MemoryStream(const std::vector<uint8_t> &cbuf, DataEndianess stream_endianess = kLittleEndian);
 	// Construct memory stream in the read-only mode over a const C-buffer;
+	// reading will never exceed buf_sz bytes;
 	// buffer must persist in memory until the stream is closed.
 	MemoryStream(const uint8_t *cbuf, size_t buf_sz, DataEndianess stream_endianess = kLittleEndian);
-	// Construct memory stream in the read-only mode over a const String;
-	// String object must persist in memory until the stream is closed.
-	MemoryStream(const String &cbuf, DataEndianess stream_endianess = kLittleEndian);
-	// Construct memory stream in the chosen mode over a given std::vector;
-	// vector must persist in memory until the stream is closed.
-	MemoryStream(std::vector<uint8_t> &buf, StreamWorkMode mode, DataEndianess stream_endianess = kLittleEndian);
-	~MemoryStream() override;
+	// Construct memory stream in the chosen mode over a given C-buffer;
+	// neither reading nor writing will ever exceed buf_sz bytes;
+	// buffer must persist in memory until the stream is closed.
+	MemoryStream(uint8_t *buf, size_t buf_sz, StreamWorkMode mode, DataEndianess stream_endianess = kLittleEndian);
+	~MemoryStream() override {}
 
 	void    Close() override;
 	bool    Flush() override;
@@ -80,12 +77,35 @@ public:
 
 	bool    Seek(soff_t offset, StreamSeek origin) override;
 
-private:
+protected:
 	const uint8_t *_cbuf;
-	size_t _len;
-	std::vector<uint8_t> *_buf;
-	const StreamWorkMode _mode;
-	soff_t _pos;
+	size_t                   _buf_sz; // hard buffer limit
+	size_t                   _len; // calculated length of stream
+	const StreamWorkMode     _mode;
+	soff_t                   _pos; // current stream pos
+
+private:
+	uint8_t *_buf;
+};
+
+
+class VectorStream : public MemoryStream {
+public:
+	// Construct memory stream in the read-only mode over a const std::vector;
+	// vector must persist in memory until the stream is closed.
+	VectorStream(const std::vector<uint8_t> &cbuf, DataEndianess stream_endianess = kLittleEndian);
+	// Construct memory stream in the chosen mode over a given std::vector;
+	// vector must persist in memory until the stream is closed.
+	VectorStream(std::vector<uint8_t> &buf, StreamWorkMode mode, DataEndianess stream_endianess = kLittleEndian);
+	~VectorStream() override {}
+
+	void    Close() override;
+
+	size_t  Write(const void *buffer, size_t size) override;
+	int32_t WriteByte(uint8_t b) override;
+
+private:
+	std::vector<uint8_t> *_vec; // writeable vector (may be null)
 };
 
 } // namespace Shared


Commit: 6d4a816fb4b42b81e70745a608763a8936429575
    https://github.com/scummvm/scummvm/commit/6d4a816fb4b42b81e70745a608763a8936429575
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-03-30T22:13:18-07:00

Commit Message:
AGS: SpriteFile supports compression type selection (RLE/LZW)

>From upstream 1526a939223974a88abf4c9601f1d4e421296a65

Changed paths:
    engines/ags/shared/ac/sprite_cache.cpp
    engines/ags/shared/ac/sprite_cache.h
    engines/ags/shared/ac/sprite_file.cpp
    engines/ags/shared/ac/sprite_file.h
    engines/ags/shared/util/compress.cpp
    engines/ags/shared/util/compress.h


diff --git a/engines/ags/shared/ac/sprite_cache.cpp b/engines/ags/shared/ac/sprite_cache.cpp
index b03fcb26773..9236fb7f8c0 100644
--- a/engines/ags/shared/ac/sprite_cache.cpp
+++ b/engines/ags/shared/ac/sprite_cache.cpp
@@ -423,7 +423,7 @@ void SpriteCache::RemapSpriteToSprite0(sprkey_t index) {
 #endif
 }
 
-int SpriteCache::SaveToFile(const String &filename, int store_flags, bool compress, SpriteFileIndex &index) {
+int SpriteCache::SaveToFile(const String &filename, int store_flags, SpriteCompression compress, SpriteFileIndex &index) {
 	std::vector<Bitmap *> sprites;
 	for (const auto &data : _spriteData) {
 		// NOTE: this is a horrible hack:
diff --git a/engines/ags/shared/ac/sprite_cache.h b/engines/ags/shared/ac/sprite_cache.h
index d759639c5d8..411948b2aab 100644
--- a/engines/ags/shared/ac/sprite_cache.h
+++ b/engines/ags/shared/ac/sprite_cache.h
@@ -92,17 +92,17 @@ public:
 	~SpriteCache();
 
 	// Loads sprite reference information and inits sprite stream
-	HError   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, int store_flags, bool compress, SpriteFileIndex &index);
+	int         SaveToFile(const Shared::String &filename, int store_flags, SpriteCompression compress, SpriteFileIndex &index);
 	// Closes an active sprite file stream
 	void        DetachFile();
 
 	inline int GetStoreFlags() const {
 		return _file.GetStoreFlags();
 	}
-	inline bool IsFileCompressed() const {
-		return _file.IsFileCompressed();
+	inline SpriteCompression GetSpriteCompression() const {
+		return _file.GetSpriteCompression();
 	}
 
 	// Tells if there is a sprite registered for the given index;
diff --git a/engines/ags/shared/ac/sprite_file.cpp b/engines/ags/shared/ac/sprite_file.cpp
index 2b69ae46b42..dcbee02cab5 100644
--- a/engines/ags/shared/ac/sprite_file.cpp
+++ b/engines/ags/shared/ac/sprite_file.cpp
@@ -141,7 +141,6 @@ static inline uint8_t GetPaletteBPP(SpriteFormat fmt) {
 
 
 SpriteFile::SpriteFile() {
-	_compressed = false;
 	_curPos = -2;
 }
 
@@ -176,13 +175,13 @@ HError SpriteFile::OpenFile(const String &filename, const String &sprindex_filen
 
 	_storeFlags = 0;
 	if (_version < kSprfVersion_Compressed) {
-		_compressed = false;
+		_compress = kSprCompress_None;
 		// skip the palette
 		_stream->Seek(256 * 3); // sizeof(RGB) * 256
 	} else if (_version == kSprfVersion_Compressed) {
-		_compressed = true;
+		_compress = kSprCompress_RLE;
 	} else if (_version >= kSprfVersion_Last32bit) {
-		_compressed = (_stream->ReadInt8() == 1);
+		_compress = (SpriteCompression)_stream->ReadInt8();
 		spriteFileID = _stream->ReadInt32();
 	}
 
@@ -225,8 +224,8 @@ int SpriteFile::GetStoreFlags() const {
 	return _storeFlags;
 }
 
-bool SpriteFile::IsFileCompressed() const {
-	return _compressed;
+SpriteCompression SpriteFile::GetSpriteCompression() const {
+	return _compress;
 }
 
 sprkey_t SpriteFile::GetTopmostSprite() const {
@@ -300,17 +299,18 @@ bool SpriteFile::LoadSpriteIndexFile(const String &filename, int expectedFileID,
 }
 
 static inline void ReadSprHeader(SpriteDatHeader &hdr, Stream *in,
-	const SpriteFileVersion ver, int gl_compress) {
+	const SpriteFileVersion ver, SpriteCompression gl_compress) {
 	int bpp = in->ReadInt8();
 	SpriteFormat sformat = (SpriteFormat)in->ReadInt8();
 	// note we MUST read first 2 * int8 before skipping rest
 	if (bpp == 0) {
 		hdr = SpriteDatHeader(); return;
 	} // empty slot
-	int compress = gl_compress, pal_count = 0;
+	int pal_count = 0;
+	SpriteCompression compress = gl_compress;
 	if (ver >= kSprfVersion_StorageFormats) {
 		pal_count = (uint8_t)in->ReadInt8() + 1; // saved as (count - 1)
-		compress = in->ReadInt8();
+		compress = (SpriteCompression)in->ReadInt8();
 	}
 	int w = in->ReadInt16();
 	int h = in->ReadInt16();
@@ -323,11 +323,12 @@ HError SpriteFile::RebuildSpriteIndex(Stream *in, sprkey_t topmost,
 	for (sprkey_t i = 0; !in->EOS() && (i <= topmost); ++i) {
 		_spriteData[i].Offset = in->GetPosition();
 		SpriteDatHeader hdr;
-		ReadSprHeader(hdr, _stream.get(), _version, _compressed ? 1 : 0);
+		ReadSprHeader(hdr, _stream.get(), _version, _compress);
 		if (hdr.BPP == 0) return HError::None(); // empty slot, this is normal
 		int pal_bpp = GetPaletteBPP(hdr.SFormat);
 		if (pal_bpp > 0) in->Seek(hdr.PalCount * pal_bpp); // skip palette
-		size_t data_sz = ((_version >= kSprfVersion_StorageFormats) || _compressed) ?
+		size_t data_sz =
+			((_version >= kSprfVersion_StorageFormats) || _compress != kSprCompress_None) ?
 			(uint32_t)in->ReadInt32() : hdr.Width * hdr.Height * hdr.BPP;
 		in->Seek(data_sz); // skip image data
 	}
@@ -347,7 +348,7 @@ HError SpriteFile::LoadSprite(sprkey_t index, Shared::Bitmap *&sprite) {
 	_curPos = -2; // mark undefined pos
 
 	SpriteDatHeader hdr;
-	ReadSprHeader(hdr, _stream.get(), _version, _compressed ? 1 : 0);
+	ReadSprHeader(hdr, _stream.get(), _version, _compress);
 	if (hdr.BPP == 0) return HError::None(); // empty slot, this is normal
 	int bpp = hdr.BPP, w = hdr.Width, h = hdr.Height;
 	Bitmap *image = BitmapHelper::CreateBitmap(w, h, bpp * 8);
@@ -370,14 +371,19 @@ HError SpriteFile::LoadSprite(sprkey_t index, Shared::Bitmap *&sprite) {
 		im_data = ImBufferPtr(&indexed_buf[0], indexed_buf.size(), 1);
 	}
 	// (Optional) Decompress the image data into the temp buffer
-	size_t in_data_size = ((_version >= kSprfVersion_StorageFormats) || _compressed) ?
+	size_t in_data_size =
+		((_version >= kSprfVersion_StorageFormats) || _compress != kSprCompress_None) ?
 		(uint32_t)_stream->ReadInt32() : (w * h * bpp);
-	if (_compressed) {
+	if (hdr.Compress != kSprCompress_None) {
 		if (in_data_size == 0) {
 			delete image;
 			return new Error(String::FromFormat("LoadSprite: bad compressed data for sprite %d.", index));
 		}
-		rle_decompress(im_data.Buf, im_data.Size, im_data.BPP, _stream.get());
+		switch (hdr.Compress) {
+		case kSprCompress_RLE: rle_decompress(im_data.Buf, im_data.Size, im_data.BPP, _stream.get()); break;
+		case kSprCompress_LZW: lzw_decompress(im_data.Buf, im_data.Size, im_data.BPP, _stream.get()); break;
+		default: assert(!"Unsupported compression type!");
+		}
 		// TODO: test that not more than data_size was read!
 	}
 	// Otherwise (no compression) read directly
@@ -414,13 +420,13 @@ HError SpriteFile::LoadRawData(sprkey_t index, SpriteDatHeader &hdr, std::vector
 	SeekToSprite(index);
 	_curPos = -2; // mark undefined pos
 
-	ReadSprHeader(hdr, _stream.get(), _version, _compressed ? 1 : 0);
+	ReadSprHeader(hdr, _stream.get(), _version, _compress);
 	if (hdr.BPP == 0) return HError::None(); // empty slot, this is normal
 	size_t data_size = 0;
 	soff_t data_pos = _stream->GetPosition();
 	// Optional palette
 	data_size += hdr.PalCount * GetPaletteBPP(hdr.SFormat);
-	if ((_version >= kSprfVersion_StorageFormats) || _compressed)
+	if ((_version >= kSprfVersion_StorageFormats) || _compress != kSprCompress_None)
 		data_size += (uint32_t)_stream->ReadInt32() + sizeof(uint32_t);
 	else
 		data_size += hdr.Width * hdr.Height * hdr.BPP;
@@ -451,9 +457,8 @@ static sprkey_t FindTopmostSprite(const std::vector<Bitmap *> &sprites) {
 }
 
 int SaveSpriteFile(const String &save_to_file,
-	const std::vector<Bitmap *> &sprites,
-	SpriteFile *read_from_file,
-	int store_flags, bool compress, SpriteFileIndex &index) {
+		const std::vector<Bitmap *> &sprites, SpriteFile *read_from_file,
+		int store_flags, SpriteCompression compress, SpriteFileIndex &index) {
 	std::unique_ptr<Stream> output(File::CreateFile(save_to_file));
 	if (output == nullptr)
 		return -1;
@@ -470,7 +475,7 @@ int SaveSpriteFile(const String &save_to_file,
 
 	const bool diff_compress =
 		read_from_file &&
-		(read_from_file->IsFileCompressed() != compress ||
+		(read_from_file->GetSpriteCompression() != compress ||
 			read_from_file->GetStoreFlags() != store_flags);
 
 	for (sprkey_t i = 0; i <= lastslot; ++i) {
@@ -535,7 +540,7 @@ int SaveSpriteIndex(const String &filename, const SpriteFileIndex &index) {
 SpriteFileWriter::SpriteFileWriter(std::unique_ptr<Stream> &out) : _out(out) {
 }
 
-void SpriteFileWriter::Begin(int store_flags, bool compress, sprkey_t last_slot) {
+void SpriteFileWriter::Begin(int store_flags, SpriteCompression compress, sprkey_t last_slot) {
 	if (!_out) return;
 	_index.SpriteFileIDCheck = g_system->getMillis();
 	_storeFlags = store_flags;
@@ -577,7 +582,7 @@ void SpriteFileWriter::WriteBitmap(Bitmap *image) {
 	uint32_t palette[256];
 	uint32_t pal_count = 0;
 	SpriteFormat sformat = kSprFmt_Undefined;
-	int compress = 0;
+	SpriteCompression compress = kSprCompress_None;
 	if ((_storeFlags & kSprStore_OptimizeForSize) != 0 && (image->GetBPP() > 1)) { // Try to store this sprite as an indexed bitmap
 		if (CreateIndexedBitmap(image, indexed_buf, palette, pal_count) && pal_count > 0) {
 			sformat = PaletteFormatForBPP(image->GetBPP());
@@ -585,10 +590,14 @@ void SpriteFileWriter::WriteBitmap(Bitmap *image) {
 		}
 	}
 	// (Optional) Compress the image data into the temp buffer
-	if (_compress) {
-		compress = 1;
+	if (_compress != kSprCompress_None) {
+		compress = _compress;
 		VectorStream mems(_membuf, kStream_Write);
-		rle_compress(im_data.Buf, im_data.Size, im_data.BPP, &mems);
+		switch (compress) {
+		case kSprCompress_RLE: rle_compress(im_data.Buf, im_data.Size, im_data.BPP, &mems); break;
+		case kSprCompress_LZW: lzw_compress(im_data.Buf, im_data.Size, im_data.BPP, &mems); break;
+		default: assert(!"Unsupported compression type!");
+		}
 		// mark to write as a plain byte array
 		im_data = ImBufferCPtr(&_membuf[0], _membuf.size(), 1);
 	}
diff --git a/engines/ags/shared/ac/sprite_file.h b/engines/ags/shared/ac/sprite_file.h
index 26e000aa21d..49910040fd7 100644
--- a/engines/ags/shared/ac/sprite_file.h
+++ b/engines/ags/shared/ac/sprite_file.h
@@ -19,14 +19,14 @@
  *
  */
 
-//=============================================================================
-//
-// 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.
-//
-//=============================================================================
+ //=============================================================================
+ //
+ // 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.
+ //
+ //=============================================================================
 
 #ifndef AGS_SHARED_AC_SPRITE_FILE_H
 #define AGS_SHARED_AC_SPRITE_FILE_H
@@ -80,6 +80,12 @@ enum SpriteFormat {
 	kSprFmt_PaletteRgb565 = 34
 };
 
+enum SpriteCompression {
+	kSprCompress_None = 0,
+	kSprCompress_RLE,
+	kSprCompress_LZW
+};
+
 typedef int32_t sprkey_t;
 
 // SpriteFileIndex contains sprite file's table of contents
@@ -89,8 +95,12 @@ struct SpriteFileIndex {
 	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; }
+	inline size_t GetCount() const {
+		return Offsets.size();
+	}
+	inline sprkey_t GetLastSlot() const {
+		return (sprkey_t)GetCount() - 1;
+	}
 };
 
 // Invidual sprite data header (as read from the file)
@@ -98,14 +108,14 @@ struct SpriteDatHeader {
 	int BPP = 0; // color depth (bytes per pixel); or input format
 	SpriteFormat SFormat = kSprFmt_Undefined; // storage format
 	uint32_t PalCount = 0; // palette length, if applicable to storage format
-	int Compress = 0; // compression type
+	SpriteCompression Compress = kSprCompress_None; // compression type
 	int Width = 0; // sprite's width
 	int Height = 0; // sprite's height
 
 	SpriteDatHeader() = default;
 	SpriteDatHeader(int bpp, SpriteFormat sformat = kSprFmt_Undefined,
-		uint32_t pal_count = 0, int compress = 0, int w = 0, int h = 0)
-		: BPP(bpp), SFormat(sformat), PalCount(pal_count),
+		uint32_t pal_count = 0, SpriteCompression compress = kSprCompress_None,
+		int w = 0, int h = 0) : BPP(bpp), SFormat(sformat), PalCount(pal_count),
 		Compress(compress), Width(w), Height(h) {
 	}
 };
@@ -127,7 +137,7 @@ public:
 
 	int         GetStoreFlags() const;
 	// Tells if bitmaps in the file are compressed
-	bool        IsFileCompressed() const;
+	SpriteCompression GetSpriteCompression() const;
 	// Tells the highest known sprite index
 	sprkey_t    GetTopmostSprite() const;
 
@@ -161,7 +171,7 @@ private:
 	std::unique_ptr<Stream> _stream; // the sprite stream
 	SpriteFileVersion _version = kSprfVersion_Current;
 	int _storeFlags = 0; // storage flags, specify how sprites may be stored
-	bool _compressed; // are sprites compressed
+	SpriteCompression _compress = kSprCompress_None; // sprite compression typ
 	sprkey_t _curPos; // current stream position (sprite slot)
 };
 
@@ -171,17 +181,20 @@ private:
 class SpriteFileWriter {
 public:
 	SpriteFileWriter(std::unique_ptr<Stream> &out);
-	~SpriteFileWriter() {}
+	~SpriteFileWriter() {
+	}
 
-    // Get the sprite index, accumulated after write
-    const SpriteFileIndex &GetIndex() const { return _index; }
+	// Get the sprite index, accumulated after write
+	const SpriteFileIndex &GetIndex() const {
+		return _index;
+	}
 
-    // Initializes new sprite file format
-    void Begin(int store_flags, 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();
+	// Initializes new sprite file format
+	void Begin(int store_flags, SpriteCompression 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 any additional processing
 	void WriteRawData(const SpriteDatHeader &hdr, const uint8_t *data, size_t data_sz);
 	// Finalizes current format; no further writing is possible after this
@@ -195,22 +208,22 @@ private:
 
 	std::unique_ptr<Stream> &_out;
 	int _storeFlags = 0;
-    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
+	SpriteCompression _compress = kSprCompress_None;
+	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<uint8_t> _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
-	int store_flags, bool compress, SpriteFileIndex &index);
+extern 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
+	int store_flags, SpriteCompression compress, SpriteFileIndex &index);
 // Saves sprite index table in a separate file
-int SaveSpriteIndex(const String &filename, const SpriteFileIndex &index);
+extern int SaveSpriteIndex(const String &filename, const SpriteFileIndex &index);
 
 } // namespace Shared
 } // namespace AGS
diff --git a/engines/ags/shared/util/compress.cpp b/engines/ags/shared/util/compress.cpp
index 4c3a981dd3b..2fc6ab3e33d 100644
--- a/engines/ags/shared/util/compress.cpp
+++ b/engines/ags/shared/util/compress.cpp
@@ -319,6 +319,26 @@ Shared::Bitmap *load_rle_bitmap8(Stream *in, RGB(*pal)[256]) {
 // LZW
 //-----------------------------------------------------------------------------
 
+void lzw_compress(const uint8_t *data, size_t data_sz, int image_bpp, Shared::Stream *out) {
+	// LZW algorithm that we use fails on sequence less than 16 bytes.
+	if (data_sz < 16) {
+		out->Write(data, data_sz);
+		return;
+	}
+	MemoryStream mem_in(data, data_sz);
+	lzwcompress(&mem_in, out);
+}
+
+void lzw_decompress(uint8_t *data, size_t data_sz, int image_bpp, Shared::Stream *in) {
+	// LZW algorithm that we use fails on sequence less than 16 bytes.
+	if (data_sz < 16) {
+		in->Read(data, data_sz);
+		return;
+	}
+	MemoryStream ms(data, data_sz, kStream_Write);
+	lzwexpand(in, &ms, data_sz);
+}
+
 void save_lzw(Stream *out, const Bitmap *bmpp, const RGB(*pal)[256]) {
 	// First write original bitmap's info and data into the memory buffer
 	// NOTE: we must do this purely for backward compatibility with old room formats:
diff --git a/engines/ags/shared/util/compress.h b/engines/ags/shared/util/compress.h
index cda9bd8708b..b4824a28104 100644
--- a/engines/ags/shared/util/compress.h
+++ b/engines/ags/shared/util/compress.h
@@ -44,6 +44,8 @@ void save_rle_bitmap8(Shared::Stream *out, const Shared::Bitmap *bmp, const RGB(
 Shared::Bitmap *load_rle_bitmap8(Shared::Stream *in, RGB(*pal)[256] = nullptr);
 
 // LZW compression
+void lzw_compress(const uint8_t *data, size_t data_sz, int image_bpp, Shared::Stream *out);
+void lzw_decompress(uint8_t *data, size_t data_sz, int image_bpp, Shared::Stream *in);
 // Saves bitmap with an optional palette compressed by LZW
 void save_lzw(Shared::Stream *out, const Shared::Bitmap *bmpp, const RGB(*pal)[256] = nullptr);
 // Loads bitmap decompressing


Commit: 0f6ca133d223e80ea78199c831d9238a0cfd38aa
    https://github.com/scummvm/scummvm/commit/0f6ca133d223e80ea78199c831d9238a0cfd38aa
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-03-30T22:13:18-07:00

Commit Message:
AGS: fixed gcc compilation of lzw

>From upstream ecca80cfb03463117fdf822fd973bf0939bbd38c

Changed paths:
    engines/ags/shared/util/lzw.h


diff --git a/engines/ags/shared/util/lzw.h b/engines/ags/shared/util/lzw.h
index 15a5f290c36..5ca4749e72c 100644
--- a/engines/ags/shared/util/lzw.h
+++ b/engines/ags/shared/util/lzw.h
@@ -22,6 +22,8 @@
 #ifndef AGS_SHARED_UTIL_LZW_H
 #define AGS_SHARED_UTIL_LZW_H
 
+#include "ags/shared/core/types.h"
+
 namespace AGS3 {
 
 namespace AGS {


Commit: b615ca27b3d8fd77fb6ed01b630906cd6c306582
    https://github.com/scummvm/scummvm/commit/b615ca27b3d8fd77fb6ed01b630906cd6c306582
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-03-30T22:13:18-07:00

Commit Message:
AGS: Fix String::ClipRight causing access violation for empty string

>From upstream b1602096ec06aae8f045c0037298d61846111aa0

Changed paths:
    engines/ags/shared/util/string.cpp


diff --git a/engines/ags/shared/util/string.cpp b/engines/ags/shared/util/string.cpp
index 1b9ea89b32c..23770530aff 100644
--- a/engines/ags/shared/util/string.cpp
+++ b/engines/ags/shared/util/string.cpp
@@ -497,7 +497,7 @@ void String::ClipMid(size_t from, size_t count) {
 }
 
 void String::ClipRight(size_t count) {
-	if (count > 0) {
+	if (_len > 0 && count > 0) {
 		count = Math::Min(count, _len);
 		BecomeUnique();
 		_len -= count;




More information about the Scummvm-git-logs mailing list