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 JPEG format via libjpeg.
28  */
29 
30 #include <agar/core/core.h>
31 
32 #include <agar/gui/gui.h>
33 #include <agar/gui/surface.h>
34 
35 #include <agar/config/have_jpeg.h>
36 #ifdef HAVE_JPEG
37 
38 #include <jpeglib.h>
39 #include <errno.h>
40 #include <setjmp.h>
41 
42 struct ag_jpg_errmgr {
43 	struct jpeg_error_mgr errmgr;
44 	jmp_buf escape;
45 };
46 
47 struct ag_jpg_sourcemgr {
48 	struct jpeg_source_mgr pub;
49 	AG_DataSource *ds;
50 	Uint8 buffer[4096];
51 };
52 
53 /*
54  * Callbacks
55  */
56 static void
AG_JPG_InitSource(j_decompress_ptr cinfo)57 AG_JPG_InitSource(j_decompress_ptr cinfo)
58 {
59 	/* no-op */
60 }
61 static int
AG_JPG_FillInputBuffer(j_decompress_ptr cinfo)62 AG_JPG_FillInputBuffer(j_decompress_ptr cinfo)
63 {
64 	struct ag_jpg_sourcemgr *sm = (struct ag_jpg_sourcemgr *)cinfo->src;
65 	size_t rv;
66 
67 	if (AG_ReadP(sm->ds, sm->buffer, sizeof(sm->buffer), &rv) == -1) {
68 		return (FALSE);
69 	}
70 	if (rv == 0) {					/* Reached EOF */
71 		sm->buffer[0] = 0xff;
72 		sm->buffer[1] = (Uint8)JPEG_EOI;
73 		rv = 2;
74 	}
75 	sm->pub.next_input_byte = sm->buffer;
76 	sm->pub.bytes_in_buffer = rv;
77 	return (TRUE);
78 }
79 static void
AG_JPG_SkipInputData(j_decompress_ptr cinfo,long len)80 AG_JPG_SkipInputData(j_decompress_ptr cinfo, long len)
81 {
82 	struct ag_jpg_sourcemgr *sm = (struct ag_jpg_sourcemgr *)cinfo->src;
83 
84 	if (len > 0) {
85 		while (len > (long)sm->pub.bytes_in_buffer) {
86 			len -= (long)sm->pub.bytes_in_buffer;
87 			sm->pub.fill_input_buffer(cinfo);
88 		}
89 		sm->pub.next_input_byte += (size_t)len;
90 		sm->pub.bytes_in_buffer -= (size_t)len;
91 	}
92 }
93 static void
AG_JPG_TermSource(j_decompress_ptr cinfo)94 AG_JPG_TermSource(j_decompress_ptr cinfo)
95 {
96 	/* no-op */
97 }
98 static void
AG_JPG_ErrorExit(j_common_ptr cinfo)99 AG_JPG_ErrorExit(j_common_ptr cinfo)
100 {
101 	struct ag_jpg_errmgr *err = (struct ag_jpg_errmgr *)cinfo->err;
102 	longjmp(err->escape, 1);
103 }
104 static void
AG_JPG_OutputMessage(j_common_ptr cinfo)105 AG_JPG_OutputMessage(j_common_ptr cinfo)
106 {
107 	/* no-op */
108 }
109 
110 /* Load a surface from a JPEG image file. */
111 AG_Surface *
AG_SurfaceFromJPEG(const char * path)112 AG_SurfaceFromJPEG(const char *path)
113 {
114 	AG_DataSource *ds;
115 	AG_Surface *s;
116 
117 	if ((ds = AG_OpenFile(path, "rb")) == NULL) {
118 		return (NULL);
119 	}
120 	if ((s = AG_ReadSurfaceFromJPEG(ds)) == NULL) {
121 		AG_SetError("%s: %s", path, AG_GetError());
122 		AG_CloseFile(ds);
123 		return (NULL);
124 	}
125 	AG_CloseFile(ds);
126 	return (s);
127 }
128 
129 /* Export a surface to a JPEG image file. */
130 int
AG_SurfaceExportJPEG(const AG_Surface * su,const char * path,Uint quality,Uint flags)131 AG_SurfaceExportJPEG(const AG_Surface *su, const char *path, Uint quality,
132     Uint flags)
133 {
134 	struct jpeg_error_mgr jerrmgr;
135 	struct jpeg_compress_struct jcomp;
136 	Uint8 *jcopybuf;
137 	FILE *f;
138 	JSAMPROW row[1];
139 	int x;
140 
141 	if ((f = fopen(path, "wb")) == NULL) {
142 		AG_SetError("fdopen: %s", strerror(errno));
143 		return (-1);
144 	}
145 
146 	jcomp.err = jpeg_std_error(&jerrmgr);
147 
148 	jpeg_create_compress(&jcomp);
149 
150 	jcomp.image_width = su->w;
151 	jcomp.image_height = su->h;
152 	jcomp.input_components = 3;
153 	jcomp.in_color_space = JCS_RGB;
154 
155 	jpeg_set_defaults(&jcomp);
156 	jpeg_set_quality(&jcomp, quality, TRUE);
157 
158 	if (flags & AG_EXPORT_JPEG_JDCT_ISLOW) { jcomp.dct_method = JDCT_ISLOW; }
159 	if (flags & AG_EXPORT_JPEG_JDCT_IFAST) { jcomp.dct_method = JDCT_IFAST; }
160 	if (flags & AG_EXPORT_JPEG_JDCT_FLOAT) { jcomp.dct_method = JDCT_FLOAT; }
161 
162 	jpeg_stdio_dest(&jcomp, f);
163 
164 	if ((jcopybuf = TryMalloc(su->w*3)) == NULL) {
165 		jpeg_destroy_compress(&jcomp);
166 		fclose(f);
167 		return (-1);
168 	}
169 
170 	jpeg_start_compress(&jcomp, TRUE);
171 	while (jcomp.next_scanline < jcomp.image_height) {
172 		Uint8 *pSrc = (Uint8 *)su->pixels +
173 		    jcomp.next_scanline*su->pitch;
174 		Uint8 *pDst = jcopybuf;
175 		AG_Color C;
176 
177 		for (x = 0; x < su->w; x++) {
178 			C = AG_GetColorRGB(AG_GET_PIXEL(su,pSrc), su->format);
179 			*pDst++ = C.r;
180 			*pDst++ = C.g;
181 			*pDst++ = C.b;
182 			pSrc += su->format->BytesPerPixel;
183 		}
184 		row[0] = jcopybuf;
185 		jpeg_write_scanlines(&jcomp, row, 1);
186 	}
187 	jpeg_finish_compress(&jcomp);
188 	jpeg_destroy_compress(&jcomp);
189 
190 	fclose(f);
191 	Free(jcopybuf);
192 	return (0);
193 }
194 
195 /* Load surface contents from a JPEG image file. */
196 AG_Surface *
AG_ReadSurfaceFromJPEG(AG_DataSource * ds)197 AG_ReadSurfaceFromJPEG(AG_DataSource *ds)
198 {
199 	struct jpeg_decompress_struct cinfo;
200 	JSAMPROW rowptr[1];
201 	AG_Surface *volatile su = NULL;
202 	off_t start = AG_Tell(ds);
203 	struct ag_jpg_errmgr jerrmgr;
204 	struct ag_jpg_sourcemgr *sm;
205 
206 	cinfo.err = jpeg_std_error(&jerrmgr.errmgr);
207 	jerrmgr.errmgr.error_exit = AG_JPG_ErrorExit;
208 	jerrmgr.errmgr.output_message = AG_JPG_OutputMessage;
209 	if (setjmp(jerrmgr.escape)) {
210 		jpeg_destroy_decompress(&cinfo);
211 		if (su != NULL) {
212 			AG_SurfaceFree(su);
213 		}
214 		AG_SetError("Error loading JPEG file");
215 		goto fail;
216 	}
217 
218 	jpeg_create_decompress(&cinfo);
219 
220 	if (cinfo.src == NULL) {
221 		cinfo.src = (struct jpeg_source_mgr *)
222 		    (*cinfo.mem->alloc_small)((j_common_ptr)&cinfo,
223 		    JPOOL_PERMANENT,
224 		    sizeof(struct ag_jpg_sourcemgr));
225 		sm = (struct ag_jpg_sourcemgr *)cinfo.src;
226 	}
227 	sm = (struct ag_jpg_sourcemgr *)cinfo.src;
228 	sm->ds = ds;
229 	sm->pub.init_source = AG_JPG_InitSource;
230 	sm->pub.fill_input_buffer = AG_JPG_FillInputBuffer;
231 	sm->pub.skip_input_data = AG_JPG_SkipInputData;
232 	sm->pub.resync_to_restart = jpeg_resync_to_restart;
233 	sm->pub.term_source = AG_JPG_TermSource;
234 	sm->pub.bytes_in_buffer = 0;
235 	sm->pub.next_input_byte = NULL;
236 
237 	jpeg_read_header(&cinfo, TRUE);
238 
239 	if (cinfo.num_components == 4) {
240 		cinfo.out_color_space = JCS_CMYK;
241 		cinfo.quantize_colors = FALSE;
242 		jpeg_calc_output_dimensions(&cinfo);
243 
244 		su = AG_SurfaceRGBA(cinfo.output_width, cinfo.output_height,
245 		    32, 0,
246 #if AG_BYTEORDER == AG_BIG_ENDIAN
247 		    0x0000ff00, 0x00ff0000, 0xff000000, 0x000000ff
248 #else
249 		    0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000
250 #endif
251 		);
252 	} else {
253 		cinfo.out_color_space = JCS_RGB;
254 		cinfo.quantize_colors = FALSE;
255 		jpeg_calc_output_dimensions(&cinfo);
256 
257 		su = AG_SurfaceRGB(cinfo.output_width, cinfo.output_height,
258 		    24, 0,
259 #if AG_BYTEORDER == AG_BIG_ENDIAN
260 		    0xff0000, 0x00ff00, 0x0000ff
261 #else
262 		    0x0000ff, 0x00ff00, 0xff0000
263 #endif
264 		    );
265 	}
266 	if (su == NULL) {
267 		jpeg_destroy_decompress(&cinfo);
268 		AG_SetError("Out of memory");
269 		goto fail;
270 	}
271 
272 	jpeg_start_decompress(&cinfo);
273 	while (cinfo.output_scanline < cinfo.output_height) {
274 		rowptr[0] = (JSAMPROW)(Uint8 *)su->pixels +
275 		                               cinfo.output_scanline*su->pitch;
276 		jpeg_read_scanlines(&cinfo, rowptr, (JDIMENSION) 1);
277 	}
278 	jpeg_finish_decompress(&cinfo);
279 	jpeg_destroy_decompress(&cinfo);
280 	return (su);
281 fail:
282 	AG_Seek(ds, start, AG_SEEK_SET);
283 	return (NULL);
284 }
285 
286 #else /* !HAVE_JPEG */
287 
288 AG_Surface *
AG_SurfaceFromJPEG(const char * path)289 AG_SurfaceFromJPEG(const char *path)
290 {
291 	AG_SetError(_("Agar not compiled with JPEG support"));
292 	return (NULL);
293 }
294 int
AG_SurfaceExportJPEG(const AG_Surface * su,const char * path,Uint quality,Uint flags)295 AG_SurfaceExportJPEG(const AG_Surface *su, const char *path, Uint quality,
296     Uint flags)
297 {
298 	AG_SetError(_("Agar not compiled with JPEG support"));
299 	return (-1);
300 }
301 AG_Surface *
AG_ReadSurfaceFromJPEG(AG_DataSource * ds)302 AG_ReadSurfaceFromJPEG(AG_DataSource *ds)
303 {
304 	AG_SetError(_("Agar not compiled with JPEG support"));
305 	return (NULL);
306 }
307 
308 #endif /* HAVE_JPEG */
309