载入中...
搜索中...
未找到
Compressor.cpp
浏览该文件的文档.
1#include "Compressor.h"
2#include "common/config.h"
3#include "common/Exception.h"
4
5#include "lz4/lz4.h"
6#include "lz4/lz4hc.h"
7
8#include <zlib.h>
9#include <vector>
10#include <cstring>
11
12namespace eve
13{
14namespace data
15{
16
18{
19public:
20
21 char *compress(std::string format, const char *data, size_t dataSize, int level, size_t &compressedSize) override
22 {
23 if (format != "lz4")
24 throw eve::Exception("Invalid format (expecting LZ4)");
25
26 if (dataSize > LZ4_MAX_INPUT_SIZE)
27 throw eve::Exception("Data is too large for LZ4 compressor.");
28
29 // We use a custom header to store some info with the compressed data.
30 const size_t headersize = sizeof(uint32_t);
31
32 int maxdestsize = LZ4_compressBound((int) dataSize);
33 size_t maxsize = headersize + (size_t) maxdestsize;
34 char *compressedbytes = nullptr;
35
36 try
37 {
38 compressedbytes = new char[maxsize];
39 }
40 catch (std::bad_alloc &)
41 {
42 throw eve::Exception("Out of memory.");
43 }
44
45 // Store the size of the uncompressed data as a header.
46#ifdef LOVE_BIG_ENDIAN
47 // Make sure it's little-endian for storage.
48 *(uint32_t *) compressedbytes = swapuint32((uint32_t) dataSize);
49#else
50 *(uint32_t *) compressedbytes = (uint32_t) dataSize;
51#endif
52
53 // Use LZ4-HC for compression level 9 and higher.
54 int csize = 0;
55 if (level > 8)
56 csize = LZ4_compress_HC(data, compressedbytes + headersize, (int) dataSize, maxdestsize, LZ4HC_CLEVEL_DEFAULT);
57 else
58 csize = LZ4_compress_default(data, compressedbytes + headersize, (int) dataSize, maxdestsize);
59
60 if (csize <= 0)
61 {
62 delete[] compressedbytes;
63 throw eve::Exception("Could not LZ4-compress data.");
64 }
65
66 // We allocated space for the maximum possible amount of data, but the
67 // actual compressed size might be much smaller, so we should shrink the
68 // data buffer if so.
69 if ((double) maxsize / (double) (csize + headersize) >= 1.2)
70 {
71 char *cbytes = new (std::nothrow) char[csize + headersize];
72 if (cbytes)
73 {
74 memcpy(cbytes, compressedbytes, csize + headersize);
75 delete[] compressedbytes;
76 compressedbytes = cbytes;
77 }
78 }
79
80 compressedSize = (size_t) csize + headersize;
81 return compressedbytes;
82 }
83
84 char *decompress(std::string format, const char *data, size_t dataSize, size_t &decompressedSize) override
85 {
86 if (format != "lz4")
87 throw eve::Exception("Invalid format (expecting LZ4)");
88
89 const size_t headersize = sizeof(uint32_t);
90 char *rawbytes = nullptr;
91
92 if (dataSize < headersize)
93 throw eve::Exception("Invalid LZ4-compressed data size.");
94
95 // Extract the original uncompressed size (stored in our custom header.)
96#ifdef LOVE_BIG_ENDIAN
97 // Convert from stored little-endian to big-endian.
98 uint32_t rawsize = swapuint32(*(uint32_t *) data);
99#else
100 uint32_t rawsize = *(uint32_t *) data;
101#endif
102
103 try
104 {
105 rawbytes = new char[rawsize];
106 }
107 catch (std::bad_alloc &)
108 {
109 throw eve::Exception("Out of memory.");
110 }
111
112 // If the uncompressed size is passed in as an argument (non-zero) and
113 // it matches the header's stored size, then we assume it's 100% accurate
114 // and we use a more efficient decompression function.
115 if (decompressedSize > 0 && decompressedSize == (size_t) rawsize)
116 {
117 // We don't use the header here, but we need to account for its size.
118 if (LZ4_decompress_fast(data + headersize, rawbytes, (int) decompressedSize) < 0)
119 {
120 delete[] rawbytes;
121 throw eve::Exception("Could not decompress LZ4-compressed data.");
122 }
123 }
124 else
125 {
126 // Account for our custom header's size in the decompress arguments.
127 int result = LZ4_decompress_safe(data + headersize, rawbytes,
128 (int) (dataSize - headersize), rawsize);
129
130 if (result < 0)
131 {
132 delete[] rawbytes;
133 throw eve::Exception("Could not decompress LZ4-compressed data.");
134 }
135
136 decompressedSize = (size_t) result;
137 }
138
139 return rawbytes;
140 }
141
142 bool isSupported(std::string format) const override
143 {
144 return format == "lz4";
145 }
146
147}; // LZ4Compressor
148
149
151{
152private:
153
154 // The following three functions are mostly copied from the zlib source
155 // (compressBound, compress2, and uncompress), but modified to support both
156 // zlib and gzip.
157
158 uLong zlibCompressBound(std::string format, uLong sourceLen)
159 {
160 uLong size = sourceLen + (sourceLen >> 12) + (sourceLen >> 14) + (sourceLen >> 25) + 13;
161
162 // The gzip header is slightly larger than the zlib header.
163 if (format == "gzip")
164 size += 18 - 6;
165
166 return size;
167 }
168
169 int zlibCompress(std::string format, Bytef *dest, uLongf *destLen, const Bytef *source, uLong sourceLen, int level)
170 {
171 z_stream stream = {};
172
173 stream.next_in = (Bytef *) source;
174 stream.avail_in = (uInt) sourceLen;
175
176 stream.next_out = dest;
177 stream.avail_out = (uInt) (*destLen);
178
179 int windowbits = 15;
180 if (format == "gzip")
181 windowbits += 16; // This tells zlib to use a gzip header.
182 else if (format == "deflate")
183 windowbits = -windowbits;
184
185 int err = deflateInit2(&stream, level, Z_DEFLATED, windowbits, 8, Z_DEFAULT_STRATEGY);
186
187 if (err != Z_OK)
188 return err;
189
190 err = deflate(&stream, Z_FINISH);
191
192 if (err != Z_STREAM_END)
193 {
194 deflateEnd(&stream);
195 return err == Z_OK ? Z_BUF_ERROR : err;
196 }
197
198 *destLen = stream.total_out;
199
200 return deflateEnd(&stream);
201 }
202
203 int zlibDecompress(std::string format, Bytef *dest, uLongf *destLen, const Bytef *source, uLong sourceLen)
204 {
205 z_stream stream = {};
206
207 stream.next_in = (Bytef *) source;
208 stream.avail_in = (uInt) sourceLen;
209
210 stream.next_out = dest;
211 stream.avail_out = (uInt) (*destLen);
212
213 // 15 is the default. Adding 32 makes zlib auto-detect the header type.
214 int windowbits = 15 + 32;
215
216 if (format == "deflate")
217 windowbits = -15;
218
219 int err = inflateInit2(&stream, windowbits);
220
221 if (err != Z_OK)
222 return err;
223
224 err = inflate(&stream, Z_FINISH);
225
226 if (err != Z_STREAM_END)
227 {
228 inflateEnd(&stream);
229 if (err == Z_NEED_DICT || (err == Z_BUF_ERROR && stream.avail_in == 0))
230 return Z_DATA_ERROR;
231 return err;
232 }
233
234 *destLen = stream.total_out;
235
236 return inflateEnd(&stream);
237 }
238
239public:
240
241 char *compress(std::string format, const char *data, size_t dataSize, int level, size_t &compressedSize) override
242 {
243 if (!isSupported(format))
244 throw eve::Exception("Invalid format (expecting zlib or gzip)");
245
246 if (level < 0)
247 level = Z_DEFAULT_COMPRESSION;
248 else if (level > 9)
249 level = 9;
250
251 uLong maxsize = zlibCompressBound(format, (uLong) dataSize);
252 char *compressedbytes = nullptr;
253
254 try
255 {
256 compressedbytes = new char[maxsize];
257 }
258 catch (std::bad_alloc &)
259 {
260 throw eve::Exception("Out of memory.");
261 }
262
263 uLongf destlen = maxsize;
264 int status = zlibCompress(format, (Bytef *) compressedbytes, &destlen, (const Bytef *) data, (uLong) dataSize, level);
265
266 if (status != Z_OK)
267 {
268 delete[] compressedbytes;
269 throw eve::Exception("Could not zlib/gzip-compress data.");
270 }
271
272 // We allocated space for the maximum possible amount of data, but the
273 // actual compressed size might be much smaller, so we should shrink the
274 // data buffer if so.
275 if ((double) maxsize / (double) destlen >= 1.3)
276 {
277 char *cbytes = new (std::nothrow) char[destlen];
278 if (cbytes)
279 {
280 memcpy(cbytes, compressedbytes, destlen);
281 delete[] compressedbytes;
282 compressedbytes = cbytes;
283 }
284 }
285
286 compressedSize = (size_t) destlen;
287 return compressedbytes;
288 }
289
290 char *decompress(std::string format, const char *data, size_t dataSize, size_t &decompressedSize) override
291 {
292 if (!isSupported(format))
293 throw eve::Exception("Invalid format (expecting zlib or gzip)");
294
295 char *rawbytes = nullptr;
296
297 // We might know the output size before decompression. If not, we guess.
298 size_t rawsize = decompressedSize > 0 ? decompressedSize : dataSize * 2;
299
300 // Repeatedly try to decompress with an increasingly large output buffer.
301 while (true)
302 {
303 try
304 {
305 rawbytes = new char[rawsize];
306 }
307 catch (std::bad_alloc &)
308 {
309 throw eve::Exception("Out of memory.");
310 }
311
312 uLongf destLen = (uLongf) rawsize;
313 int status = zlibDecompress(format, (Bytef *) rawbytes, &destLen, (const Bytef *) data, (uLong) dataSize);
314
315 if (status == Z_OK)
316 {
317 decompressedSize = (size_t) destLen;
318 break;
319 }
320 else if (status != Z_BUF_ERROR)
321 {
322 // For any error other than "not enough room", throw an exception.
323 delete[] rawbytes;
324 throw eve::Exception("Could not decompress zlib/gzip-compressed data.");
325 }
326
327 // Not enough room in the output buffer: try again with a larger size.
328 delete[] rawbytes;
329 rawsize *= 2;
330 }
331
332 return rawbytes;
333 }
334
335 bool isSupported(std::string format) const override
336 {
337 return format == "zlib" || format == "gzip" || format == "deflate";
338 }
339
340}; // zlibCompressor
341
343{
344 static LZ4Compressor lz4compressor;
345 static zlibCompressor zlibcompressor;
346
347 Compressor *compressors[] = {&lz4compressor, &zlibcompressor};
348
349 for (Compressor *c : compressors)
350 {
351 if (c->isSupported(format))
352 return c;
353 }
354
355 return nullptr;
356}
357
358} // data
359} // eve
JobStatus status
uint32_t c
Light2D::Data * data
Base class for backends for different compression formats.
Definition Compressor.h:14
static Compressor * getCompressor(std::string format)
Gets a Compressor that can compress and decompress a specific format. Returns null if there are no su...
char * decompress(std::string format, const char *data, size_t dataSize, size_t &decompressedSize) override
Decompresses compressed data, and returns the decompressed result.
bool isSupported(std::string format) const override
Gets whether a specific format is supported by this backend.
char * compress(std::string format, const char *data, size_t dataSize, int level, size_t &compressedSize) override
Compresses input data, and returns the compressed result.
bool isSupported(std::string format) const override
Gets whether a specific format is supported by this backend.
char * decompress(std::string format, const char *data, size_t dataSize, size_t &decompressedSize) override
Decompresses compressed data, and returns the decompressed result.
char * compress(std::string format, const char *data, size_t dataSize, int level, size_t &compressedSize) override
Compresses input data, and returns the compressed result.
Definition Build.cpp:11