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