1 /*
2 * Copyright (c) 2010-2015 Hypertriton, Inc. <http://hypertriton.com/>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
18 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
23 * USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26 /*
27 * Support for reading image files in PNG format via libpng.
28 */
29
30 #include <agar/core/core.h>
31 #include <agar/gui/gui.h>
32 #include <agar/gui/surface.h>
33
34 #include <agar/config/have_png.h>
35 #if defined(HAVE_PNG)
36 #include <agar/config/have_libpng14.h>
37
38 #ifdef macintosh
39 # define MACOS
40 #endif
41 #include <png.h>
42
43 /* Load a surface from a PNG image file. */
44 AG_Surface *
AG_SurfaceFromPNG(const char * path)45 AG_SurfaceFromPNG(const char *path)
46 {
47 AG_DataSource *ds;
48 AG_Surface *s;
49
50 if ((ds = AG_OpenFile(path, "rb")) == NULL) {
51 return (NULL);
52 }
53 if ((s = AG_ReadSurfaceFromPNG(ds)) == NULL) {
54 AG_SetError("%s: %s", path, AG_GetError());
55 AG_CloseFile(ds);
56 return (NULL);
57 }
58 AG_CloseFile(ds);
59 return (s);
60 }
61
62 static void
AG_PNG_ReadData(png_structp png,png_bytep buf,png_size_t size)63 AG_PNG_ReadData(png_structp png, png_bytep buf, png_size_t size)
64 {
65 AG_DataSource *ds = (AG_DataSource*)png_get_io_ptr(png);
66 AG_Read(ds, buf, size);
67 }
68
69 /* Load a surface from PNG image data. */
70 AG_Surface *
AG_ReadSurfaceFromPNG(AG_DataSource * ds)71 AG_ReadSurfaceFromPNG(AG_DataSource *ds)
72 {
73 AG_Surface *volatile su = NULL;
74 png_structp png;
75 png_infop info;
76 png_uint_32 width, height;
77 int depth, colorType, intlaceType, channels, start, row;
78 Uint32 Rmask = 0, Gmask = 0, Bmask = 0, Amask = 0;
79 png_bytep *volatile pData = NULL;
80 volatile int colorKey = -1;
81 png_color_16 *transColor;
82
83 start = AG_Tell(ds);
84
85 if ((png = png_create_read_struct(PNG_LIBPNG_VER_STRING,
86 NULL, NULL, NULL)) == NULL) {
87 AG_SetError("Out of memory (libpng)");
88 goto fail;
89 }
90 if ((info = png_create_info_struct(png)) == NULL) {
91 AG_SetError("png_create_info_struct() failed");
92 goto fail;
93 }
94
95 png_set_read_fn(png, ds, AG_PNG_ReadData);
96
97 png_read_info(png, info);
98 png_get_IHDR(png, info,
99 &width, &height, &depth,
100 &colorType, &intlaceType,
101 NULL, NULL);
102
103 png_set_strip_16(png);
104 png_set_packing(png);
105 png_set_expand(png);
106 png_set_tRNS_to_alpha(png);
107
108 /* Read transparency information. */
109 if (png_get_valid(png, info, PNG_INFO_tRNS)) {
110 int num_trans;
111 Uint8 *trans;
112 png_get_tRNS(png, info, &trans, &num_trans, &transColor);
113 }
114
115 /* Expand grayscale to 24-bit RGB */
116 if (colorType == PNG_COLOR_TYPE_GRAY_ALPHA)
117 png_set_gray_to_rgb(png);
118
119 /* Update png_info structure per our requirements. */
120 png_read_update_info(png, info);
121
122 png_get_IHDR(png, info, &width, &height, &depth,
123 &colorType, &intlaceType, NULL, NULL);
124
125 #ifdef HAVE_LIBPNG14
126 channels = (int)png_get_channels(png, info);
127 #else
128 channels = info->channels;
129 #endif
130
131 #if AG_BYTEORDER == AG_BIG_ENDIAN
132 {
133 int s = (channels == 4) ? 0 : 8;
134 Rmask = 0xff000000 >> s;
135 Gmask = 0x00ff0000 >> s;
136 Bmask = 0x0000ff00 >> s;
137 Amask = 0x000000ff >> s;
138 }
139 #else
140 Rmask = 0x000000ff;
141 Gmask = 0x0000ff00;
142 Bmask = 0x00ff0000;
143 Amask = (channels == 4) ? 0xff000000 : 0;
144 #endif
145 if ((su = AG_SurfaceRGBA(width, height, depth*channels, 0,
146 Rmask, Gmask, Bmask, Amask)) == NULL)
147 goto fail;
148
149 if (colorKey != -1) {
150 colorKey = AG_MapPixelRGB(su->format,
151 (Uint8)transColor->red,
152 (Uint8)transColor->green,
153 (Uint8)transColor->blue);
154 AG_SurfaceSetColorKey(su, AG_SRCCOLORKEY, colorKey);
155 }
156
157 /* Read image data */
158 if ((pData = TryMalloc(sizeof(png_bytep)*height)) == NULL) {
159 goto fail;
160 }
161 for (row = 0; row < (int)height; row++) {
162 pData[row] = (png_bytep)(Uint8 *)su->pixels + row*su->pitch;
163 }
164 png_read_image(png, pData);
165
166 if (png != NULL) {
167 png_destroy_read_struct(&png,
168 info ? &info : (png_infopp)0, (png_infopp)0);
169 }
170 Free(pData);
171 return (su);
172 fail:
173 if (png != NULL) {
174 png_destroy_read_struct(&png,
175 info ? &info : (png_infopp)0, (png_infopp)0);
176 }
177 Free(pData);
178 if (su) {
179 AG_SurfaceFree(su);
180 }
181 AG_Seek(ds, start, AG_SEEK_SET);
182 return (NULL);
183 }
184
185 /* Save a surface to a PNG image file. */
186 int
AG_SurfaceExportPNG(const AG_Surface * su,const char * path,Uint flags)187 AG_SurfaceExportPNG(const AG_Surface *su, const char *path, Uint flags)
188 {
189 FILE *f;
190 png_structp png;
191 png_infop info;
192 int pngDepth, pngType;
193 png_colorp pngPal = NULL;
194 png_color_8 sig_bit;
195 png_byte ** rows = NULL;
196 int i, x, y;
197 Uint8 *pSrc;
198
199 if ((f = fopen(path, "wb")) == NULL) {
200 AG_SetError("%s: %s", path, AG_GetError());
201 return (-1);
202 }
203 if ((png = png_create_write_struct(PNG_LIBPNG_VER_STRING,
204 NULL, NULL, NULL)) == NULL) {
205 AG_SetError("png_create_write_struct() failed");
206 fclose(f);
207 return (-1);
208 }
209 if ((info = png_create_info_struct(png)) == NULL) {
210 AG_SetError("png_create_info_struct() failed");
211 goto fail;
212 }
213
214 if (setjmp(png_jmpbuf(png))) {
215 AG_SetError("png_init_io() failed");
216 goto fail;
217 }
218 png_init_io(png, f);
219
220 if (su->format->palette != NULL) {
221 pngType = PNG_COLOR_TYPE_PALETTE;
222
223 if (su->format->palette->nColors > 16) { pngDepth = 8; }
224 else if (su->format->palette->nColors > 4) { pngDepth = 4; }
225 else if (su->format->palette->nColors > 2) { pngDepth = 2; }
226 else { pngDepth = 1; }
227 } else {
228 if (su->format->Amask != 0) {
229 pngType = PNG_COLOR_TYPE_RGB_ALPHA;
230 } else {
231 pngType = PNG_COLOR_TYPE_RGB;
232 }
233 pngDepth = 8;
234 }
235 if (setjmp(png_jmpbuf(png))) {
236 AG_SetError("png_write_info() failed");
237 goto fail;
238 }
239
240 png_set_IHDR(png, info,
241 su->w, su->h, pngDepth, pngType,
242 (flags & AG_EXPORT_PNG_ADAM7) ? PNG_INTERLACE_ADAM7 : PNG_INTERLACE_NONE,
243 PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
244
245 /* Write PLTE chunk if color-index mode. */
246 if (pngType == PNG_COLOR_TYPE_PALETTE) {
247 AG_Palette *pal = su->format->palette;
248
249 pngPal = (png_colorp)TryMalloc(pal->nColors*sizeof(png_color));
250 if (pngPal == NULL) {
251 goto fail;
252 }
253 for (i = 0; i < pal->nColors; i++) {
254 pngPal[i].red = pal->colors[i].r;
255 pngPal[i].green = pal->colors[i].g;
256 pngPal[i].blue = pal->colors[i].b;
257 }
258 png_set_PLTE(png, info, pngPal, pal->nColors);
259 }
260
261 if ((rows = png_malloc(png, su->h*sizeof(png_byte *))) == NULL) {
262 AG_SetError("png_malloc rows");
263 goto fail;
264 }
265 pSrc = (Uint8 *)su->pixels;
266 if (pngType == PNG_COLOR_TYPE_RGB_ALPHA) {
267 for (y = 0; y < su->h; y++) {
268 png_byte *row;
269
270 if ((row = png_malloc(png, su->w*4)) == NULL) {
271 for (y--; y >= 0; y--) { png_free(png, rows[y]); }
272 png_free(png, rows);
273 AG_SetError("png_malloc row");
274 goto fail;
275 }
276 rows[y] = row;
277 for (x = 0; x < su->w; x++) {
278 AG_Color C;
279
280 C = AG_GetColorRGBA(AG_GET_PIXEL(su,pSrc),
281 su->format);
282 *row++ = C.r;
283 *row++ = C.g;
284 *row++ = C.b;
285 *row++ = C.a;
286 pSrc += su->format->BytesPerPixel;
287 }
288 pSrc += su->padding;
289 }
290 } else {
291 for (y = 0; y < su->h; y++) {
292 png_byte *row;
293
294 if ((row = png_malloc(png, su->w*4)) == NULL) {
295 for (y--; y >= 0; y--) { png_free(png, rows[y]); }
296 png_free(png, rows);
297 AG_SetError("png_malloc row");
298 goto fail;
299 }
300 rows[y] = row;
301 for (x = 0; x < su->w; x++) {
302 AG_Color C;
303
304 C = AG_GetColorRGB(AG_GET_PIXEL(su,pSrc),
305 su->format);
306 *row++ = C.r;
307 *row++ = C.g;
308 *row++ = C.b;
309 pSrc += su->format->BytesPerPixel;
310 }
311 for (x = 0; x < su->w; x++) {
312 *row++ = 0;
313 }
314 pSrc += su->padding;
315 }
316 }
317
318 png_write_info(png, info);
319
320 if (pngType & PNG_COLOR_MASK_COLOR) {
321 sig_bit.red = pngDepth;
322 sig_bit.green = pngDepth;
323 sig_bit.blue = pngDepth;
324 } else {
325 sig_bit.gray = pngDepth;
326 }
327 if (pngType & PNG_COLOR_MASK_ALPHA) {
328 sig_bit.alpha = pngDepth;
329 }
330 png_set_sBIT(png, info, &sig_bit);
331
332 if (setjmp(png_jmpbuf(png))) {
333 AG_SetError("png_write_image() failed");
334 for (y = 0; y < su->h; y++) { png_free(png, rows[y]); }
335 png_free(png, rows);
336 goto fail;
337 }
338 png_write_image(png, rows);
339 png_write_end(png, info);
340
341 for (y = 0; y < su->h; y++) { png_free(png, rows[y]); }
342 png_free(png, rows);
343 png_destroy_write_struct(&png, &info);
344 Free(pngPal);
345 fclose(f);
346 return (0);
347 fail:
348 png_destroy_write_struct(&png, NULL);
349 Free(pngPal);
350 fclose(f);
351 return (-1);
352 }
353
354 #else /* !HAVE_PNG */
355
356 AG_Surface *
AG_SurfaceFromPNG(const char * path)357 AG_SurfaceFromPNG(const char *path)
358 {
359 AG_SetError(_("Agar not compiled with PNG support"));
360 return (NULL);
361 }
362 int
AG_SurfaceExportPNG(const AG_Surface * su,const char * path,Uint flags)363 AG_SurfaceExportPNG(const AG_Surface *su, const char *path, Uint flags)
364 {
365 AG_SetError(_("Agar not compiled with PNG support"));
366 return (-1);
367 }
368 AG_Surface *
AG_ReadSurfaceFromPNG(AG_DataSource * ds)369 AG_ReadSurfaceFromPNG(AG_DataSource *ds)
370 {
371 AG_SetError(_("Agar not compiled with PNG support"));
372 return (NULL);
373 }
374
375 #endif /* HAVE_PNG */
376