Browse Source

Added gzip support to love.math.compress and love.math.decompress.

Alex Szpakowski 10 years ago
parent
commit
2e9154283c

+ 126 - 24
src/modules/math/Compressor.cpp

@@ -39,8 +39,11 @@ class LZ4Compressor : public Compressor
 {
 public:
 
-	char *compress(const char *data, size_t dataSize, int level, size_t &compressedSize) override
+	char *compress(Format format, const char *data, size_t dataSize, int level, size_t &compressedSize) override
 	{
+		if (format != FORMAT_LZ4)
+			throw love::Exception("Invalid format (expecting LZ4)");
+
 		if (dataSize > LZ4_MAX_INPUT_SIZE)
 			throw love::Exception("Data is too large for LZ4 compressor.");
 
@@ -99,8 +102,11 @@ public:
 		return compressedbytes;
 	}
 
-	char *decompress(const char *data, size_t dataSize, size_t &decompressedSize) override
+	char *decompress(Format format, const char *data, size_t dataSize, size_t &decompressedSize) override
 	{
+		if (format != FORMAT_LZ4)
+			throw love::Exception("Invalid format (expecting LZ4)");
+
 		const size_t headersize = sizeof(uint32);
 		char *rawbytes = nullptr;
 
@@ -154,23 +160,111 @@ public:
 		return rawbytes;
 	}
 
-	Compressor::Format getFormat() const override { return FORMAT_LZ4; }
+	bool isSupported(Format format) const override
+	{
+		return format == FORMAT_LZ4;
+	}
 
 }; // LZ4Compressor
 
 
 class zlibCompressor : public Compressor
 {
+private:
+
+	// The following three functions are mostly copied from the zlib source
+	// (compressBound, compress2, and uncompress), but modified to support both
+	// zlib and gzip.
+
+	uLong zlibCompressBound(Format format, uLong sourceLen)
+	{
+		uLong size = sourceLen + (sourceLen >> 12) + (sourceLen >> 14) + (sourceLen >> 25) + 13;
+
+		// The gzip header is slightly larger than the zlib header.
+		if (format == FORMAT_GZIP)
+			size += 18 - 6;
+
+		return size;
+	}
+
+	int zlibCompress(Format format, Bytef *dest, uLongf *destLen, const Bytef *source, uLong sourceLen, int level)
+	{
+		z_stream stream = {};
+
+		stream.next_in = (Bytef *) source;
+		stream.avail_in = (uInt) sourceLen;
+
+		stream.next_out = dest;
+		stream.avail_out = (uInt) (*destLen);
+
+		int windowbits = 15;
+		if (format == FORMAT_GZIP)
+			windowbits += 16; // This tells zlib to use a gzip header.
+
+		int err = deflateInit2(&stream, level, Z_DEFLATED, windowbits, 8, Z_DEFAULT_STRATEGY);
+
+		if (err != Z_OK)
+			return err;
+
+		err = deflate(&stream, Z_FINISH);
+
+		if (err != Z_STREAM_END)
+		{
+			deflateEnd(&stream);
+			return err == Z_OK ? Z_BUF_ERROR : err;
+		}
+
+		*destLen = stream.total_out;
+
+		return deflateEnd(&stream);
+	}
+
+	int zlibDecompress(Bytef *dest, uLongf *destLen, const Bytef *source, uLong sourceLen)
+	{
+		z_stream stream = {};
+
+		stream.next_in = (Bytef *) source;
+		stream.avail_in = (uInt) sourceLen;
+
+		stream.next_out = dest;
+		stream.avail_out = (uInt) (*destLen);
+
+		// 15 is the default. Adding 32 makes zlib auto-detect the header type.
+		int windowbits = 15 + 32;
+
+		int err = inflateInit2(&stream, windowbits);
+
+		if (err != Z_OK)
+			return err;
+
+		err = inflate(&stream, Z_FINISH);
+
+		if (err != Z_STREAM_END)
+		{
+			inflateEnd(&stream);
+			if (err == Z_NEED_DICT || (err == Z_BUF_ERROR && stream.avail_in == 0))
+				return Z_DATA_ERROR;
+			return err;
+		}
+
+		*destLen = stream.total_out;
+
+		return inflateEnd(&stream);
+	}
+
 public:
 
-	char *compress(const char *data, size_t dataSize, int level, size_t &compressedSize) override
+	char *compress(Format format, const char *data, size_t dataSize, int level, size_t &compressedSize) override
 	{
+		if (!isSupported(format))
+			throw love::Exception("Invalid format (expecting zlib or gzip)");
+
 		if (level < 0)
 			level = Z_DEFAULT_COMPRESSION;
 		else if (level > 9)
 			level = 9;
 
-		uLong maxsize = compressBound((uLong) dataSize);
+		uLong maxsize = zlibCompressBound(format, (uLong) dataSize);
 		char *compressedbytes = nullptr;
 
 		try
@@ -183,18 +277,18 @@ public:
 		}
 
 		uLongf destlen = maxsize;
-		int status = compress2((Bytef *) compressedbytes, &destlen, (const Bytef *) data, (uLong) dataSize, level);
+		int status = zlibCompress(format, (Bytef *) compressedbytes, &destlen, (const Bytef *) data, (uLong) dataSize, level);
 
 		if (status != Z_OK)
 		{
 			delete[] compressedbytes;
-			throw love::Exception("Could not zlib-compress data.");
+			throw love::Exception("Could not zlib/gzip-compress data.");
 		}
 
 		// We allocated space for the maximum possible amount of data, but the
 		// actual compressed size might be much smaller, so we should shrink the
 		// data buffer if so.
-		if ((double) maxsize / (double) destlen >= 1.2)
+		if ((double) maxsize / (double) destlen >= 1.3)
 		{
 			char *cbytes = new (std::nothrow) char[destlen];
 			if (cbytes)
@@ -209,8 +303,11 @@ public:
 		return compressedbytes;
 	}
 
-	char *decompress(const char *data, size_t dataSize, size_t &decompressedSize) override
+	char *decompress(Format format, const char *data, size_t dataSize, size_t &decompressedSize) override
 	{
+		if (!isSupported(format))
+			throw love::Exception("Invalid format (expecting zlib or gzip)");
+
 		char *rawbytes = nullptr;
 
 		// We might know the output size before decompression. If not, we guess.
@@ -229,7 +326,7 @@ public:
 			}
 
 			uLongf destLen = (uLongf) rawsize;
-			int status = uncompress((Bytef *) rawbytes, &destLen, (const Bytef *) data, (uLong) dataSize);
+			int status = zlibDecompress((Bytef *) rawbytes, &destLen, (const Bytef *) data, (uLong) dataSize);
 
 			if (status == Z_OK)
 			{
@@ -240,7 +337,7 @@ public:
 			{
 				// For any error other than "not enough room", throw an exception.
 				delete[] rawbytes;
-				throw love::Exception("Could not decompress zlib-compressed data.");
+				throw love::Exception("Could not decompress zlib/gzip-compressed data.");
 			}
 
 			// Not enough room in the output buffer: try again with a larger size.
@@ -251,23 +348,27 @@ public:
 		return rawbytes;
 	}
 
-	Compressor::Format getFormat() const override { return FORMAT_ZLIB; }
+	bool isSupported(Format format) const override
+	{
+		return format == FORMAT_ZLIB || format == FORMAT_GZIP;
+	}
 
 }; // zlibCompressor
 
-
-Compressor *Compressor::Create(Format format)
+Compressor *Compressor::getCompressor(Format format)
 {
-	switch (format)
+	static LZ4Compressor lz4compressor;
+	static zlibCompressor zlibcompressor;
+
+	Compressor *compressors[] = {&lz4compressor, &zlibcompressor};
+
+	for (Compressor *c : compressors)
 	{
-	case FORMAT_LZ4:
-		return new LZ4Compressor;
-	case FORMAT_ZLIB:
-		return new zlibCompressor;
-	default:
-		throw love::Exception("Invalid compressor format.");
-		return nullptr;
+		if (c->isSupported(format))
+			return c;
 	}
+
+	return nullptr;
 }
 
 bool Compressor::getConstant(const char *in, Format &out)
@@ -282,8 +383,9 @@ bool Compressor::getConstant(Format in, const char *&out)
 
 StringMap<Compressor::Format, Compressor::FORMAT_MAX_ENUM>::Entry Compressor::formatEntries[] =
 {
-	{"lz4", Compressor::FORMAT_LZ4},
-	{"zlib", Compressor::FORMAT_ZLIB},
+	{"lz4",  FORMAT_LZ4},
+	{"zlib", FORMAT_ZLIB},
+	{"gzip", FORMAT_GZIP},
 };
 
 StringMap<Compressor::Format, Compressor::FORMAT_MAX_ENUM> Compressor::formatNames(Compressor::formatEntries, sizeof(Compressor::formatEntries));

+ 17 - 9
src/modules/math/Compressor.h

@@ -24,6 +24,9 @@
 // LOVE
 #include "common/StringMap.h"
 
+// C++
+#include <vector>
+
 namespace love
 {
 namespace math
@@ -40,20 +43,22 @@ public:
 	{
 		FORMAT_LZ4,
 		FORMAT_ZLIB,
+		FORMAT_GZIP,
 		FORMAT_MAX_ENUM
 	};
 
 	/**
-	 * Creates a new Compressor that can compress and decompress a specific
-	 * format.
+	 * Gets a Compressor that can compress and decompress a specific format.
+	 * Returns null if there are no supported compressors for the given format.
 	 **/
-	static Compressor *Create(Format format);
+	static Compressor *getCompressor(Format format);
 
 	virtual ~Compressor() {}
 
 	/**
 	 * Compresses input data, and returns the compressed result.
 	 *
+	 * @param[in] format The format to compress to.
 	 * @param[in] data The input (uncompressed) data.
 	 * @param[in] dataSize The size in bytes of the input data.
 	 * @param[in] level The amount of compression to apply (between 0 and 9.)
@@ -63,11 +68,12 @@ public:
 	 *
 	 * @return The newly compressed data (allocated with new[]).
 	 **/
-	virtual char *compress(const char *data, size_t dataSize, int level, size_t &compressedSize) = 0;
+	virtual char *compress(Format format, const char *data, size_t dataSize, int level, size_t &compressedSize) = 0;
 
 	/**
 	 * Decompresses compressed data, and returns the decompressed result.
 	 *
+	 * @param[in] format The format the compressed data is in.
 	 * @param[in] data The input (compressed) data.
 	 * @param[in] dataSize The size in bytes of the compressed data.
 	 * @param[in,out] decompressedSize On input, the size in bytes of the
@@ -76,18 +82,20 @@ public:
 	 *
 	 * @return The decompressed data (allocated with new[]).
 	 **/
-	virtual char *decompress(const char *data, size_t dataSize, size_t &decompressedSize) = 0;
+	virtual char *decompress(Format format, const char *data, size_t dataSize, size_t &decompressedSize) = 0;
 
 	/**
-	 * Gets the compression format implemented by this backend.
-	 *
-	 * @return The supported format.
+	 * Gets whether a specific format is supported by this backend.
 	 **/
-	virtual Format getFormat() const = 0;
+	virtual bool isSupported(Format format) const = 0;
 
 	static bool getConstant(const char *in, Format &out);
 	static bool getConstant(Format in, const char *&out);
 
+protected:
+
+	Compressor() {}
+
 private:
 
 	static StringMap<Format, FORMAT_MAX_ENUM>::Entry formatEntries[];

+ 8 - 12
src/modules/math/MathModule.cpp

@@ -86,19 +86,13 @@ Math Math::instance;
 
 Math::Math()
 	: rng()
-	, compressors()
 {
 	// prevent the runtime from free()-ing this
 	retain();
-
-	for (int i = 0; i < (int) Compressor::FORMAT_MAX_ENUM; i++)
-		compressors[i] = Compressor::Create((Compressor::Format) i);
 }
 
 Math::~Math()
 {
-	for (Compressor *c : compressors)
-		delete c;
 }
 
 RandomGenerator *Math::newRandomGenerator()
@@ -232,13 +226,13 @@ CompressedData *Math::compress(Compressor::Format format, love::Data *rawdata, i
 
 CompressedData *Math::compress(Compressor::Format format, const char *rawbytes, size_t rawsize, int level)
 {
-	if (format == Compressor::FORMAT_MAX_ENUM || !compressors[format])
+	Compressor *compressor = Compressor::getCompressor(format);
+
+	if (compressor == nullptr)
 		throw love::Exception("Invalid compression format.");
 
 	size_t compressedsize = 0;
-	Compressor *compressor = compressors[format];
-
-	char *cbytes = compressor->compress(rawbytes, rawsize, level, compressedsize);
+	char *cbytes = compressor->compress(format, rawbytes, rawsize, level, compressedsize);
 
 	CompressedData *data = nullptr;
 
@@ -268,10 +262,12 @@ char *Math::decompress(CompressedData *data, size_t &decompressedsize)
 
 char *Math::decompress(Compressor::Format format, const char *cbytes, size_t compressedsize, size_t &rawsize)
 {
-	if (format == Compressor::FORMAT_MAX_ENUM || !compressors[format])
+	Compressor *compressor = Compressor::getCompressor(format);
+
+	if (compressor == nullptr)
 		throw love::Exception("Invalid compression format.");
 
-	return compressors[format]->decompress(cbytes, compressedsize, rawsize);
+	return compressor->decompress(format, cbytes, compressedsize, rawsize);
 }
 
 } // math

+ 0 - 2
src/modules/math/MathModule.h

@@ -205,8 +205,6 @@ private:
 
 	Math();
 
-	Compressor *compressors[Compressor::FORMAT_MAX_ENUM];
-
 }; // Math
 
 inline float Math::noise(float x) const