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