IMG_jpg.c
author Ryan C. Gordon <icculus@icculus.org>
Wed, 26 Sep 2018 15:19:34 -0400
changeset 587 32a18ca05935
parent 575 36e9e2255178
child 638 e3e9d7430674
permissions -rw-r--r--
pnm: Don't get into infinite loops on truncated files.
     1 /*
     2   SDL_image:  An example image loading library for use with SDL
     3   Copyright (C) 1997-2018 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 
    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 }
   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 */
   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  */
   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  */
   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  */
   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  */
   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  */
   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 
   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 
   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 */
   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 
   422 static void init_destination(j_compress_ptr cinfo)
   423 {
   424     /* We don't actually need to do anything */
   425     return;
   426 }
   427 
   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 
   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 
   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 
   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 
   539 int IMG_InitJPG()
   540 {
   541     IMG_SetError("JPEG images are not supported");
   542     return(-1);
   543 }
   544 
   545 void IMG_QuitJPG()
   546 {
   547 }
   548 
   549 /* See if an image is contained in a data source */
   550 int IMG_isJPG(SDL_RWops *src)
   551 {
   552     return(0);
   553 }
   554 
   555 /* Load a JPEG type image from an SDL datasource */
   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 
   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 
   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 */