1 /*
2 SDL_image: An example image loading library for use with SDL
3 Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20 */
21
22 /* This is a JPEG image file loading framework */
23
24 #include <stdio.h>
25 #include <setjmp.h>
26
27 #include "SDL_image.h"
28
29 #if !(defined(__APPLE__) || defined(SDL_IMAGE_USE_WIC_BACKEND)) || defined(SDL_IMAGE_USE_COMMON_BACKEND)
30
31 #ifdef LOAD_JPG
32
33 #define USE_JPEGLIB
34
35 #include <jpeglib.h>
36
37 #ifdef JPEG_TRUE /* MinGW version of jpeg-8.x renamed TRUE to JPEG_TRUE etc. */
38 typedef JPEG_boolean boolean;
39 #define TRUE JPEG_TRUE
40 #define FALSE JPEG_FALSE
41 #endif
42
43 /* Define this for fast loading and not as good image quality */
44 #define FAST_JPEG
45
46 /* Define this for quicker (but less perfect) JPEG identification */
47 #define FAST_IS_JPEG
48
49 static struct {
50 int loaded;
51 void *handle;
52 void (*jpeg_calc_output_dimensions) (j_decompress_ptr cinfo);
53 void (*jpeg_CreateDecompress) (j_decompress_ptr cinfo, int version, size_t structsize);
54 void (*jpeg_destroy_decompress) (j_decompress_ptr cinfo);
55 boolean (*jpeg_finish_decompress) (j_decompress_ptr cinfo);
56 int (*jpeg_read_header) (j_decompress_ptr cinfo, boolean require_image);
57 JDIMENSION (*jpeg_read_scanlines) (j_decompress_ptr cinfo, JSAMPARRAY scanlines, JDIMENSION max_lines);
58 boolean (*jpeg_resync_to_restart) (j_decompress_ptr cinfo, int desired);
59 boolean (*jpeg_start_decompress) (j_decompress_ptr cinfo);
60 void (*jpeg_CreateCompress) (j_compress_ptr cinfo, int version, size_t structsize);
61 void (*jpeg_start_compress) (j_compress_ptr cinfo, boolean write_all_tables);
62 void (*jpeg_set_quality) (j_compress_ptr cinfo, int quality, boolean force_baseline);
63 void (*jpeg_set_defaults) (j_compress_ptr cinfo);
64 JDIMENSION (*jpeg_write_scanlines) (j_compress_ptr cinfo, JSAMPARRAY scanlines, JDIMENSION num_lines);
65 void (*jpeg_finish_compress) (j_compress_ptr cinfo);
66 void (*jpeg_destroy_compress) (j_compress_ptr cinfo);
67 struct jpeg_error_mgr * (*jpeg_std_error) (struct jpeg_error_mgr * err);
68 } lib;
69
70 #ifdef LOAD_JPG_DYNAMIC
71 #define FUNCTION_LOADER(FUNC, SIG) \
72 lib.FUNC = (SIG) SDL_LoadFunction(lib.handle, #FUNC); \
73 if (lib.FUNC == NULL) { SDL_UnloadObject(lib.handle); return -1; }
74 #else
75 #define FUNCTION_LOADER(FUNC, SIG) \
76 lib.FUNC = FUNC;
77 #endif
78
IMG_InitJPG()79 int IMG_InitJPG()
80 {
81 if ( lib.loaded == 0 ) {
82 #ifdef LOAD_JPG_DYNAMIC
83 lib.handle = SDL_LoadObject(LOAD_JPG_DYNAMIC);
84 if ( lib.handle == NULL ) {
85 return -1;
86 }
87 #endif
88 FUNCTION_LOADER(jpeg_calc_output_dimensions, void (*) (j_decompress_ptr cinfo))
89 FUNCTION_LOADER(jpeg_CreateDecompress, void (*) (j_decompress_ptr cinfo, int version, size_t structsize))
90 FUNCTION_LOADER(jpeg_destroy_decompress, void (*) (j_decompress_ptr cinfo))
91 FUNCTION_LOADER(jpeg_finish_decompress, boolean (*) (j_decompress_ptr cinfo))
92 FUNCTION_LOADER(jpeg_read_header, int (*) (j_decompress_ptr cinfo, boolean require_image))
93 FUNCTION_LOADER(jpeg_read_scanlines, JDIMENSION (*) (j_decompress_ptr cinfo, JSAMPARRAY scanlines, JDIMENSION max_lines))
94 FUNCTION_LOADER(jpeg_resync_to_restart, boolean (*) (j_decompress_ptr cinfo, int desired))
95 FUNCTION_LOADER(jpeg_start_decompress, boolean (*) (j_decompress_ptr cinfo))
96 FUNCTION_LOADER(jpeg_CreateCompress, void (*) (j_compress_ptr cinfo, int version, size_t structsize))
97 FUNCTION_LOADER(jpeg_start_compress, void (*) (j_compress_ptr cinfo, boolean write_all_tables))
98 FUNCTION_LOADER(jpeg_set_quality, void (*) (j_compress_ptr cinfo, int quality, boolean force_baseline))
99 FUNCTION_LOADER(jpeg_set_defaults, void (*) (j_compress_ptr cinfo))
100 FUNCTION_LOADER(jpeg_write_scanlines, JDIMENSION (*) (j_compress_ptr cinfo, JSAMPARRAY scanlines, JDIMENSION num_lines))
101 FUNCTION_LOADER(jpeg_finish_compress, void (*) (j_compress_ptr cinfo))
102 FUNCTION_LOADER(jpeg_destroy_compress, void (*) (j_compress_ptr cinfo))
103 FUNCTION_LOADER(jpeg_std_error, struct jpeg_error_mgr * (*) (struct jpeg_error_mgr * err))
104 }
105 ++lib.loaded;
106
107 return 0;
108 }
IMG_QuitJPG()109 void IMG_QuitJPG()
110 {
111 if ( lib.loaded == 0 ) {
112 return;
113 }
114 if ( lib.loaded == 1 ) {
115 #ifdef LOAD_JPG_DYNAMIC
116 SDL_UnloadObject(lib.handle);
117 #endif
118 }
119 --lib.loaded;
120 }
121
122 /* See if an image is contained in a data source */
IMG_isJPG(SDL_RWops * src)123 int IMG_isJPG(SDL_RWops *src)
124 {
125 Sint64 start;
126 int is_JPG;
127 int in_scan;
128 Uint8 magic[4];
129
130 /* This detection code is by Steaphan Greene <stea@cs.binghamton.edu> */
131 /* Blame me, not Sam, if this doesn't work right. */
132 /* And don't forget to report the problem to the the sdl list too! */
133
134 if ( !src )
135 return 0;
136 start = SDL_RWtell(src);
137 is_JPG = 0;
138 in_scan = 0;
139 if ( SDL_RWread(src, magic, 2, 1) ) {
140 if ( (magic[0] == 0xFF) && (magic[1] == 0xD8) ) {
141 is_JPG = 1;
142 while (is_JPG == 1) {
143 if(SDL_RWread(src, magic, 1, 2) != 2) {
144 is_JPG = 0;
145 } else if( (magic[0] != 0xFF) && (in_scan == 0) ) {
146 is_JPG = 0;
147 } else if( (magic[0] != 0xFF) || (magic[1] == 0xFF) ) {
148 /* Extra padding in JPEG (legal) */
149 /* or this is data and we are scanning */
150 SDL_RWseek(src, -1, RW_SEEK_CUR);
151 } else if(magic[1] == 0xD9) {
152 /* Got to end of good JPEG */
153 break;
154 } else if( (in_scan == 1) && (magic[1] == 0x00) ) {
155 /* This is an encoded 0xFF within the data */
156 } else if( (magic[1] >= 0xD0) && (magic[1] < 0xD9) ) {
157 /* These have nothing else */
158 } else if(SDL_RWread(src, magic+2, 1, 2) != 2) {
159 is_JPG = 0;
160 } else {
161 /* Yes, it's big-endian */
162 Sint64 innerStart;
163 Uint32 size;
164 Sint64 end;
165 innerStart = SDL_RWtell(src);
166 size = (magic[2] << 8) + magic[3];
167 end = SDL_RWseek(src, size-2, RW_SEEK_CUR);
168 if ( end != innerStart + size - 2 ) is_JPG = 0;
169 if ( magic[1] == 0xDA ) {
170 /* Now comes the actual JPEG meat */
171 #ifdef FAST_IS_JPEG
172 /* Ok, I'm convinced. It is a JPEG. */
173 break;
174 #else
175 /* I'm not convinced. Prove it! */
176 in_scan = 1;
177 #endif
178 }
179 }
180 }
181 }
182 }
183 SDL_RWseek(src, start, RW_SEEK_SET);
184 return(is_JPG);
185 }
186
187 #define INPUT_BUFFER_SIZE 4096
188 typedef struct {
189 struct jpeg_source_mgr pub;
190
191 SDL_RWops *ctx;
192 Uint8 buffer[INPUT_BUFFER_SIZE];
193 } my_source_mgr;
194
195 /*
196 * Initialize source --- called by jpeg_read_header
197 * before any data is actually read.
198 */
init_source(j_decompress_ptr cinfo)199 static void init_source (j_decompress_ptr cinfo)
200 {
201 /* We don't actually need to do anything */
202 return;
203 }
204
205 /*
206 * Fill the input buffer --- called whenever buffer is emptied.
207 */
fill_input_buffer(j_decompress_ptr cinfo)208 static boolean fill_input_buffer (j_decompress_ptr cinfo)
209 {
210 my_source_mgr * src = (my_source_mgr *) cinfo->src;
211 int nbytes;
212
213 nbytes = (int)SDL_RWread(src->ctx, src->buffer, 1, INPUT_BUFFER_SIZE);
214 if (nbytes <= 0) {
215 /* Insert a fake EOI marker */
216 src->buffer[0] = (Uint8) 0xFF;
217 src->buffer[1] = (Uint8) JPEG_EOI;
218 nbytes = 2;
219 }
220 src->pub.next_input_byte = src->buffer;
221 src->pub.bytes_in_buffer = nbytes;
222
223 return TRUE;
224 }
225
226
227 /*
228 * Skip data --- used to skip over a potentially large amount of
229 * uninteresting data (such as an APPn marker).
230 *
231 * Writers of suspendable-input applications must note that skip_input_data
232 * is not granted the right to give a suspension return. If the skip extends
233 * beyond the data currently in the buffer, the buffer can be marked empty so
234 * that the next read will cause a fill_input_buffer call that can suspend.
235 * Arranging for additional bytes to be discarded before reloading the input
236 * buffer is the application writer's problem.
237 */
skip_input_data(j_decompress_ptr cinfo,long num_bytes)238 static void skip_input_data (j_decompress_ptr cinfo, long num_bytes)
239 {
240 my_source_mgr * src = (my_source_mgr *) cinfo->src;
241
242 /* Just a dumb implementation for now. Could use fseek() except
243 * it doesn't work on pipes. Not clear that being smart is worth
244 * any trouble anyway --- large skips are infrequent.
245 */
246 if (num_bytes > 0) {
247 while (num_bytes > (long) src->pub.bytes_in_buffer) {
248 num_bytes -= (long) src->pub.bytes_in_buffer;
249 (void) src->pub.fill_input_buffer(cinfo);
250 /* note we assume that fill_input_buffer will never
251 * return FALSE, so suspension need not be handled.
252 */
253 }
254 src->pub.next_input_byte += (size_t) num_bytes;
255 src->pub.bytes_in_buffer -= (size_t) num_bytes;
256 }
257 }
258
259 /*
260 * Terminate source --- called by jpeg_finish_decompress
261 * after all data has been read.
262 */
term_source(j_decompress_ptr cinfo)263 static void term_source (j_decompress_ptr cinfo)
264 {
265 /* We don't actually need to do anything */
266 return;
267 }
268
269 /*
270 * Prepare for input from a stdio stream.
271 * The caller must have already opened the stream, and is responsible
272 * for closing it after finishing decompression.
273 */
jpeg_SDL_RW_src(j_decompress_ptr cinfo,SDL_RWops * ctx)274 static void jpeg_SDL_RW_src (j_decompress_ptr cinfo, SDL_RWops *ctx)
275 {
276 my_source_mgr *src;
277
278 /* The source object and input buffer are made permanent so that a series
279 * of JPEG images can be read from the same file by calling jpeg_stdio_src
280 * only before the first one. (If we discarded the buffer at the end of
281 * one image, we'd likely lose the start of the next one.)
282 * This makes it unsafe to use this manager and a different source
283 * manager serially with the same JPEG object. Caveat programmer.
284 */
285 if (cinfo->src == NULL) { /* first time for this JPEG object? */
286 cinfo->src = (struct jpeg_source_mgr *)
287 (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
288 sizeof(my_source_mgr));
289 src = (my_source_mgr *) cinfo->src;
290 }
291
292 src = (my_source_mgr *) cinfo->src;
293 src->pub.init_source = init_source;
294 src->pub.fill_input_buffer = fill_input_buffer;
295 src->pub.skip_input_data = skip_input_data;
296 src->pub.resync_to_restart = lib.jpeg_resync_to_restart; /* use default method */
297 src->pub.term_source = term_source;
298 src->ctx = ctx;
299 src->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */
300 src->pub.next_input_byte = NULL; /* until buffer loaded */
301 }
302
303 struct my_error_mgr {
304 struct jpeg_error_mgr errmgr;
305 jmp_buf escape;
306 };
307
my_error_exit(j_common_ptr cinfo)308 static void my_error_exit(j_common_ptr cinfo)
309 {
310 struct my_error_mgr *err = (struct my_error_mgr *)cinfo->err;
311 longjmp(err->escape, 1);
312 }
313
output_no_message(j_common_ptr cinfo)314 static void output_no_message(j_common_ptr cinfo)
315 {
316 /* do nothing */
317 }
318
319 /* Load a JPEG type image from an SDL datasource */
IMG_LoadJPG_RW(SDL_RWops * src)320 SDL_Surface *IMG_LoadJPG_RW(SDL_RWops *src)
321 {
322 Sint64 start;
323 struct jpeg_decompress_struct cinfo;
324 JSAMPROW rowptr[1];
325 SDL_Surface *volatile surface = NULL;
326 struct my_error_mgr jerr;
327
328 if ( !src ) {
329 /* The error message has been set in SDL_RWFromFile */
330 return NULL;
331 }
332 start = SDL_RWtell(src);
333
334 if ( (IMG_Init(IMG_INIT_JPG) & IMG_INIT_JPG) == 0 ) {
335 return NULL;
336 }
337
338 /* Create a decompression structure and load the JPEG header */
339 cinfo.err = lib.jpeg_std_error(&jerr.errmgr);
340 jerr.errmgr.error_exit = my_error_exit;
341 jerr.errmgr.output_message = output_no_message;
342 if(setjmp(jerr.escape)) {
343 /* If we get here, libjpeg found an error */
344 lib.jpeg_destroy_decompress(&cinfo);
345 if ( surface != NULL ) {
346 SDL_FreeSurface(surface);
347 }
348 SDL_RWseek(src, start, RW_SEEK_SET);
349 IMG_SetError("JPEG loading error");
350 return NULL;
351 }
352
353 lib.jpeg_create_decompress(&cinfo);
354 jpeg_SDL_RW_src(&cinfo, src);
355 lib.jpeg_read_header(&cinfo, TRUE);
356
357 if(cinfo.num_components == 4) {
358 /* Set 32-bit Raw output */
359 cinfo.out_color_space = JCS_CMYK;
360 cinfo.quantize_colors = FALSE;
361 lib.jpeg_calc_output_dimensions(&cinfo);
362
363 /* Allocate an output surface to hold the image */
364 surface = SDL_CreateRGBSurface(SDL_SWSURFACE,
365 cinfo.output_width, cinfo.output_height, 32,
366 #if SDL_BYTEORDER == SDL_LIL_ENDIAN
367 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000);
368 #else
369 0x0000FF00, 0x00FF0000, 0xFF000000, 0x000000FF);
370 #endif
371 } else {
372 /* Set 24-bit RGB output */
373 cinfo.out_color_space = JCS_RGB;
374 cinfo.quantize_colors = FALSE;
375 #ifdef FAST_JPEG
376 cinfo.scale_num = 1;
377 cinfo.scale_denom = 1;
378 cinfo.dct_method = JDCT_FASTEST;
379 cinfo.do_fancy_upsampling = FALSE;
380 #endif
381 lib.jpeg_calc_output_dimensions(&cinfo);
382
383 /* Allocate an output surface to hold the image */
384 surface = SDL_CreateRGBSurface(SDL_SWSURFACE,
385 cinfo.output_width, cinfo.output_height, 24,
386 #if SDL_BYTEORDER == SDL_LIL_ENDIAN
387 0x0000FF, 0x00FF00, 0xFF0000,
388 #else
389 0xFF0000, 0x00FF00, 0x0000FF,
390 #endif
391 0);
392 }
393
394 if ( surface == NULL ) {
395 lib.jpeg_destroy_decompress(&cinfo);
396 SDL_RWseek(src, start, RW_SEEK_SET);
397 IMG_SetError("Out of memory");
398 return NULL;
399 }
400
401 /* Decompress the image */
402 lib.jpeg_start_decompress(&cinfo);
403 while ( cinfo.output_scanline < cinfo.output_height ) {
404 rowptr[0] = (JSAMPROW)(Uint8 *)surface->pixels +
405 cinfo.output_scanline * surface->pitch;
406 lib.jpeg_read_scanlines(&cinfo, rowptr, (JDIMENSION) 1);
407 }
408 lib.jpeg_finish_decompress(&cinfo);
409 lib.jpeg_destroy_decompress(&cinfo);
410
411 return(surface);
412 }
413
414 #define OUTPUT_BUFFER_SIZE 4096
415 typedef struct {
416 struct jpeg_destination_mgr pub;
417
418 SDL_RWops *ctx;
419 Uint8 buffer[OUTPUT_BUFFER_SIZE];
420 } my_destination_mgr;
421
init_destination(j_compress_ptr cinfo)422 static void init_destination(j_compress_ptr cinfo)
423 {
424 /* We don't actually need to do anything */
425 return;
426 }
427
empty_output_buffer(j_compress_ptr cinfo)428 static boolean empty_output_buffer(j_compress_ptr cinfo)
429 {
430 my_destination_mgr * dest = (my_destination_mgr *)cinfo->dest;
431
432 /* In typical applications, it should write out the *entire* buffer */
433 SDL_RWwrite(dest->ctx, dest->buffer, 1, OUTPUT_BUFFER_SIZE);
434 dest->pub.next_output_byte = dest->buffer;
435 dest->pub.free_in_buffer = OUTPUT_BUFFER_SIZE;
436
437 return TRUE;
438 }
439
term_destination(j_compress_ptr cinfo)440 static void term_destination(j_compress_ptr cinfo)
441 {
442 my_destination_mgr * dest = (my_destination_mgr *)cinfo->dest;
443
444 /* In most applications, this must flush any data remaining in the buffer */
445 SDL_RWwrite(dest->ctx, dest->buffer, 1, OUTPUT_BUFFER_SIZE - dest->pub.free_in_buffer);
446 }
447
jpeg_SDL_RW_dest(j_compress_ptr cinfo,SDL_RWops * ctx)448 static void jpeg_SDL_RW_dest(j_compress_ptr cinfo, SDL_RWops *ctx)
449 {
450 my_destination_mgr *dest;
451
452 if (cinfo->dest == NULL) {
453 cinfo->dest = (struct jpeg_destination_mgr *)
454 (*cinfo->mem->alloc_small) ((j_common_ptr)cinfo, JPOOL_PERMANENT,
455 sizeof(my_destination_mgr));
456 dest = (my_destination_mgr *)cinfo->dest;
457 }
458
459 dest = (my_destination_mgr *)cinfo->dest;
460 dest->pub.init_destination = init_destination;
461 dest->pub.empty_output_buffer = empty_output_buffer;
462 dest->pub.term_destination = term_destination;
463 dest->ctx = ctx;
464 dest->pub.next_output_byte = dest->buffer;
465 dest->pub.free_in_buffer = OUTPUT_BUFFER_SIZE;
466 }
467
IMG_SaveJPG_RW_jpeglib(SDL_Surface * surface,SDL_RWops * dst,int freedst,int quality)468 static int IMG_SaveJPG_RW_jpeglib(SDL_Surface *surface, SDL_RWops *dst, int freedst, int quality)
469 {
470 #if SDL_BYTEORDER == SDL_LIL_ENDIAN
471 static const Uint32 jpg_format = SDL_PIXELFORMAT_RGB24;
472 #else
473 static const Uint32 jpg_format = SDL_PIXELFORMAT_BGR24;
474 #endif
475 struct jpeg_compress_struct cinfo;
476 struct my_error_mgr jerr;
477 JSAMPROW row_pointer[1];
478 SDL_Surface* jpeg_surface = surface;
479 int result = -1;
480
481 if (!dst) {
482 SDL_SetError("Passed NULL dst");
483 goto done;
484 }
485
486 if (!IMG_Init(IMG_INIT_JPG)) {
487 goto done;
488 }
489
490 /* Convert surface to format we can save */
491 if (surface->format->format != jpg_format) {
492 jpeg_surface = SDL_ConvertSurfaceFormat(surface, jpg_format, 0);
493 if (!jpeg_surface) {
494 goto done;
495 }
496 }
497
498 /* Create a decompression structure and load the JPEG header */
499 cinfo.err = lib.jpeg_std_error(&jerr.errmgr);
500 jerr.errmgr.error_exit = my_error_exit;
501 jerr.errmgr.output_message = output_no_message;
502
503 lib.jpeg_create_compress(&cinfo);
504 jpeg_SDL_RW_dest(&cinfo, dst);
505
506 cinfo.image_width = jpeg_surface->w;
507 cinfo.image_height = jpeg_surface->h;
508 cinfo.in_color_space = JCS_RGB;
509 cinfo.input_components = 3;
510
511 lib.jpeg_set_defaults(&cinfo);
512 lib.jpeg_set_quality(&cinfo, quality, TRUE);
513 lib.jpeg_start_compress(&cinfo, TRUE);
514
515 while (cinfo.next_scanline < cinfo.image_height) {
516 int offset = cinfo.next_scanline * jpeg_surface->pitch;
517 row_pointer[0] = ((Uint8*)jpeg_surface->pixels) + offset;
518 lib.jpeg_write_scanlines(&cinfo, row_pointer, 1);
519 }
520
521 lib.jpeg_finish_compress(&cinfo);
522 lib.jpeg_destroy_compress(&cinfo);
523
524 if (jpeg_surface != surface) {
525 SDL_FreeSurface(jpeg_surface);
526 }
527
528 result = 0;
529
530 done:
531 if (freedst) {
532 SDL_RWclose(dst);
533 }
534 return result;
535 }
536
537 #else
538
IMG_InitJPG()539 int IMG_InitJPG()
540 {
541 IMG_SetError("JPEG images are not supported");
542 return(-1);
543 }
544
IMG_QuitJPG()545 void IMG_QuitJPG()
546 {
547 }
548
549 /* See if an image is contained in a data source */
IMG_isJPG(SDL_RWops * src)550 int IMG_isJPG(SDL_RWops *src)
551 {
552 return(0);
553 }
554
555 /* Load a JPEG type image from an SDL datasource */
IMG_LoadJPG_RW(SDL_RWops * src)556 SDL_Surface *IMG_LoadJPG_RW(SDL_RWops *src)
557 {
558 return(NULL);
559 }
560
561 #endif /* LOAD_JPG */
562
563 #endif /* !defined(__APPLE__) || defined(SDL_IMAGE_USE_COMMON_BACKEND) */
564
565 /* We'll always have JPG save support */
566 #define SAVE_JPG
567
568 #ifdef SAVE_JPG
569
IMG_SaveJPG(SDL_Surface * surface,const char * file,int quality)570 int IMG_SaveJPG(SDL_Surface *surface, const char *file, int quality)
571 {
572 SDL_RWops *dst = SDL_RWFromFile(file, "wb");
573 if (dst) {
574 return IMG_SaveJPG_RW(surface, dst, 1, quality);
575 } else {
576 return -1;
577 }
578 }
579
IMG_SaveJPG_RW(SDL_Surface * surface,SDL_RWops * dst,int freedst,int quality)580 int IMG_SaveJPG_RW(SDL_Surface *surface, SDL_RWops *dst, int freedst, int quality)
581 {
582 #ifdef USE_JPEGLIB
583 return IMG_SaveJPG_RW_jpeglib(surface, dst, freedst, quality);
584 #else
585 return IMG_SetError("SDL_image not built with jpeglib, saving not supported");
586 #endif
587 }
588
589 #endif /* SAVE_JPG */
590