1/**
2 * Copyright (c) 2006-2023 LOVE Development Team
3 *
4 * This software is provided 'as-is', without any express or implied
5 * warranty. In no event will the authors be held liable for any damages
6 * arising from the use of this software.
7 *
8 * Permission is granted to anyone to use this software for any purpose,
9 * including commercial applications, and to alter it and redistribute it
10 * freely, subject to the following restrictions:
11 *
12 * 1. The origin of this software must not be misrepresented; you must not
13 * claim that you wrote the original software. If you use this software
14 * in a product, an acknowledgment in the product documentation would be
15 * appreciated but is not required.
16 * 2. Altered source versions must be plainly marked as such, and must not be
17 * misrepresented as being the original software.
18 * 3. This notice may not be removed or altered from any source distribution.
19 **/
20
21#include "PNGHandler.h"
22
23// LOVE
24#include "common/Exception.h"
25#include "common/math.h"
26
27// LodePNG
28#include "lodepng/lodepng.h"
29
30// zlib
31#include <zlib.h>
32
33// C++
34#include <algorithm>
35
36// C
37#include <cstdlib>
38
39namespace love
40{
41namespace image
42{
43namespace magpie
44{
45
46// Custom PNG decompression function for LodePNG, using zlib.
47static unsigned zlibDecompress(unsigned char **out, size_t *outsize, const unsigned char *in,
48 size_t insize, const LodePNGDecompressSettings* /*settings*/)
49{
50 int status = Z_OK;
51
52 uLongf outdatasize = insize;
53 size_t sizemultiplier = 0;
54 unsigned char *outdata = out != nullptr ? *out : nullptr;
55
56 while (true)
57 {
58 // Enough size to hold the decompressed data, hopefully.
59 outdatasize = insize << (++sizemultiplier);
60
61 // LodePNG uses malloc, realloc, and free.
62 // Since version 2014-08-23, LodePNG passes in an existing pointer in
63 // the 'out' argument that it expects to be realloc'd. Not doing so can
64 // result in a memory leak.
65 if (outdata != nullptr)
66 outdata = (unsigned char *) realloc(outdata, outdatasize);
67 else
68 outdata = (unsigned char *) malloc(outdatasize);
69
70 if (!outdata)
71 return 83; // "Memory allocation failed" error code for LodePNG.
72
73 // Use zlib to decompress the PNG data.
74 status = uncompress(outdata, &outdatasize, in, insize);
75
76 // If the out buffer was big enough, break out of the loop.
77 if (status != Z_BUF_ERROR)
78 break;
79
80 // Otherwise delete the out buffer and try again with a larger size...
81 free(outdata);
82 outdata = nullptr;
83 }
84
85 if (status != Z_OK)
86 {
87 free(outdata);
88 return 10000; // "Unknown error code" for LodePNG.
89 }
90
91 if (out != nullptr)
92 *out = outdata;
93
94 if (outsize != nullptr)
95 *outsize = outdatasize;
96
97 return 0; // Success.
98}
99
100// Custom PNG compression function for LodePNG, using zlib.
101static unsigned zlibCompress(unsigned char **out, size_t *outsize, const unsigned char *in,
102 size_t insize, const LodePNGCompressSettings* /*settings*/)
103{
104 // Get the maximum compressed size of the data.
105 uLongf outdatasize = compressBound(insize);
106
107 // LodePNG uses malloc, realloc, and free.
108 unsigned char *outdata = (unsigned char *) malloc(outdatasize);
109
110 if (!outdata)
111 return 83; // "Memory allocation failed" error code for LodePNG.
112
113 // Use zlib to compress the PNG data.
114 int status = compress(outdata, &outdatasize, in, insize);
115
116 if (status != Z_OK)
117 {
118 free(outdata);
119 return 10000; // "Unknown error code" for LodePNG.
120 }
121
122 if (out != nullptr)
123 *out = outdata;
124
125 if (outsize != nullptr)
126 *outsize = (size_t) outdatasize;
127
128 return 0; // Success.
129}
130
131bool PNGHandler::canDecode(Data *data)
132{
133 unsigned int width = 0, height = 0;
134 unsigned char *indata = (unsigned char *) data->getData();
135 size_t insize = data->getSize();
136
137 lodepng::State state;
138 unsigned status = lodepng_inspect(&width, &height, &state, indata, insize);
139
140 return status == 0 && width > 0 && height > 0;
141}
142
143bool PNGHandler::canEncode(PixelFormat rawFormat, EncodedFormat encodedFormat)
144{
145 return encodedFormat == ENCODED_PNG
146 && (rawFormat == PIXELFORMAT_RGBA8 || rawFormat == PIXELFORMAT_RGBA16);
147}
148
149PNGHandler::DecodedImage PNGHandler::decode(Data *fdata)
150{
151 unsigned int width = 0, height = 0;
152 unsigned char *indata = (unsigned char *) fdata->getData();
153 size_t insize = fdata->getSize();
154
155 DecodedImage img;
156
157 lodepng::State state;
158 unsigned status = lodepng_inspect(&width, &height, &state, indata, insize);
159
160 if (status != 0)
161 {
162 const char *err = lodepng_error_text(status);
163 throw love::Exception("Could not decode PNG image (%s)", err);
164 }
165
166 state.decoder.zlibsettings.custom_zlib = zlibDecompress;
167 state.info_raw.colortype = LCT_RGBA;
168
169 if (state.info_png.color.bitdepth == 16)
170 state.info_raw.bitdepth = 16;
171 else
172 state.info_raw.bitdepth = 8;
173
174 status = lodepng_decode(&img.data, &width, &height, &state, indata, insize);
175
176 if (status != 0)
177 {
178 const char *err = lodepng_error_text(status);
179 throw love::Exception("Could not decode PNG image (%s)", err);
180 }
181
182 img.width = (int) width;
183 img.height = (int) height;
184 img.size = width * height * (state.info_raw.bitdepth * 4 / 8);
185 img.format = state.info_raw.bitdepth == 16 ? PIXELFORMAT_RGBA16 : PIXELFORMAT_RGBA8;
186
187 // LodePNG keeps raw 16 bit images stored as big-endian.
188#ifndef LOVE_BIG_ENDIAN
189 if (state.info_raw.bitdepth == 16)
190 {
191 uint16 *pixeldata = (uint16 *) img.data;
192 size_t numpixelcomponents = img.size / sizeof(uint16);
193
194 for (size_t i = 0; i < numpixelcomponents; i++)
195 pixeldata[i] = swapuint16(pixeldata[i]);
196 }
197#endif
198
199 return img;
200}
201
202FormatHandler::EncodedImage PNGHandler::encode(const DecodedImage &img, EncodedFormat encodedFormat)
203{
204 if (!canEncode(img.format, encodedFormat))
205 throw love::Exception("PNG encoder cannot encode to non-PNG format.");
206
207 EncodedImage encimg;
208
209 lodepng::State state;
210
211 state.info_raw.colortype = LCT_RGBA;
212 state.info_raw.bitdepth = img.format == PIXELFORMAT_RGBA16 ? 16 : 8;
213
214 state.info_png.color.colortype = LCT_RGBA;
215 state.info_png.color.bitdepth = state.info_raw.bitdepth;
216
217 state.encoder.zlibsettings.custom_zlib = zlibCompress;
218
219 const uint8 *data = img.data;
220 uint16 *swappeddata = nullptr;
221
222 // LodePNG expects big-endian raw pixel data when encoding a 16 bit image.
223#ifndef LOVE_BIG_ENDIAN
224 if (state.info_raw.bitdepth == 16)
225 {
226 try
227 {
228 swappeddata = new uint16[img.size / sizeof(uint16)];
229 }
230 catch (std::exception &)
231 {
232 throw love::Exception("Out of memory.");
233 }
234
235 const uint16 *rawdata = (const uint16 *) img.data;
236 size_t numpixelcomponents = img.size / sizeof(uint16);
237
238 for (size_t i = 0; i < numpixelcomponents; i++)
239 swappeddata[i] = swapuint16(rawdata[i]);
240
241 data = (const uint8 *) swappeddata;
242 }
243#endif
244
245 unsigned status = lodepng_encode(&encimg.data, &encimg.size,
246 data, img.width, img.height, &state);
247
248 if (swappeddata != nullptr)
249 delete[] swappeddata;
250
251 if (status != 0)
252 {
253 const char *err = lodepng_error_text(status);
254 throw love::Exception("Could not encode PNG image (%s)", err);
255 }
256
257 return encimg;
258}
259
260void PNGHandler::freeRawPixels(unsigned char *mem)
261{
262 // LodePNG uses malloc, realloc, and free.
263 if (mem)
264 ::free(mem);
265}
266
267} // magpie
268} // image
269} // love
270