text: /*
    SDL_image:  An example image loading library for use with SDL
    Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
  
    This software is provided 'as-is', without any express or implied
    warranty.  In no event will the authors be held liable for any damages
    arising from the use of this software.
  
    Permission is granted to anyone to use this software for any purpose,
    including commercial applications, and to alter it and redistribute it
    freely, subject to the following restrictions:
  
    1. The origin of this software must not be misrepresented; you must not
       claim that you wrote the original software. If you use this software
       in a product, an acknowledgment in the product documentation would be
       appreciated but is not required.
    2. Altered source versions must be plainly marked as such, and must not be
       misrepresented as being the original software.
    3. This notice may not be removed or altered from any source distribution.
  */
  
  /* This is a JPEG image file loading framework */
  
  #include <stdio.h>
  #include <setjmp.h>
  
  #include "SDL_image.h"
  
  #if !(defined(__APPLE__) || defined(SDL_IMAGE_USE_WIC_BACKEND)) || defined(SDL_IMAGE_USE_COMMON_BACKEND)
  
  #ifdef LOAD_JPG
  
  #define USE_JPEGLIB
  
  #include <jpeglib.h>
  
  #ifdef JPEG_TRUE  /* MinGW version of jpeg-8.x renamed TRUE to JPEG_TRUE etc. */
      typedef JPEG_boolean boolean;
      #define TRUE JPEG_TRUE
      #define FALSE JPEG_FALSE
  #endif
  
  /* Define this for fast loading and not as good image quality */
  /*#define FAST_JPEG*/
  
  /* Define this for quicker (but less perfect) JPEG identification */
  #define FAST_IS_JPEG
  
  static struct {
      int loaded;
      void *handle;
      void (*jpeg_calc_output_dimensions) (j_decompress_ptr cinfo);
      void (*jpeg_CreateDecompress) (j_decompress_ptr cinfo, int version, size_t structsize);
      void (*jpeg_destroy_decompress) (j_decompress_ptr cinfo);
      boolean (*jpeg_finish_decompress) (j_decompress_ptr cinfo);
      int (*jpeg_read_header) (j_decompress_ptr cinfo, boolean require_image);
      JDIMENSION (*jpeg_read_scanlines) (j_decompress_ptr cinfo, JSAMPARRAY scanlines, JDIMENSION max_lines);
      boolean (*jpeg_resync_to_restart) (j_decompress_ptr cinfo, int desired);
      boolean (*jpeg_start_decompress) (j_decompress_ptr cinfo);
      void (*jpeg_CreateCompress) (j_compress_ptr cinfo, int version, size_t structsize);
      void (*jpeg_start_compress) (j_compress_ptr cinfo, boolean write_all_tables);
      void (*jpeg_set_quality) (j_compress_ptr cinfo, int quality, boolean force_baseline);
      void (*jpeg_set_defaults) (j_compress_ptr cinfo);
      JDIMENSION (*jpeg_write_scanlines) (j_compress_ptr cinfo, JSAMPARRAY scanlines, JDIMENSION num_lines);
      void (*jpeg_finish_compress) (j_compress_ptr cinfo);
      void (*jpeg_destroy_compress) (j_compress_ptr cinfo);
      struct jpeg_error_mgr * (*jpeg_std_error) (struct jpeg_error_mgr * err);
  } lib;
  
  #ifdef LOAD_JPG_DYNAMIC
  #define FUNCTION_LOADER(FUNC, SIG) \
      lib.FUNC = (SIG) SDL_LoadFunction(lib.handle, #FUNC); \
      if (lib.FUNC == NULL) { SDL_UnloadObject(lib.handle); return -1; }
  #else
  #define FUNCTION_LOADER(FUNC, SIG) \
      lib.FUNC = FUNC;
  #endif
  
  int IMG_InitJPG()
  {
      if ( lib.loaded == 0 ) {
  #ifdef LOAD_JPG_DYNAMIC
          lib.handle = SDL_LoadObject(LOAD_JPG_DYNAMIC);
          if ( lib.handle == NULL ) {
              return -1;
          }
  #endif
          FUNCTION_LOADER(jpeg_calc_output_dimensions, void (*) (j_decompress_ptr cinfo))
          FUNCTION_LOADER(jpeg_CreateDecompress, void (*) (j_decompress_ptr cinfo, int version, size_t structsize))
          FUNCTION_LOADER(jpeg_destroy_decompress, void (*) (j_decompress_ptr cinfo))
          FUNCTION_LOADER(jpeg_finish_decompress, boolean (*) (j_decompress_ptr cinfo))
          FUNCTION_LOADER(jpeg_read_header, int (*) (j_decompress_ptr cinfo, boolean require_image))
          FUNCTION_LOADER(jpeg_read_scanlines, JDIMENSION (*) (j_decompress_ptr cinfo, JSAMPARRAY scanlines, JDIMENSION max_lines))
          FUNCTION_LOADER(jpeg_resync_to_restart, boolean (*) (j_decompress_ptr cinfo, int desired))
          FUNCTION_LOADER(jpeg_start_decompress, boolean (*) (j_decompress_ptr cinfo))
          FUNCTION_LOADER(jpeg_CreateCompress, void (*) (j_compress_ptr cinfo, int version, size_t structsize))
          FUNCTION_LOADER(jpeg_start_compress, void (*) (j_compress_ptr cinfo, boolean write_all_tables))
          FUNCTION_LOADER(jpeg_set_quality, void (*) (j_compress_ptr cinfo, int quality, boolean force_baseline))
          FUNCTION_LOADER(jpeg_set_defaults, void (*) (j_compress_ptr cinfo))
          FUNCTION_LOADER(jpeg_write_scanlines, JDIMENSION (*) (j_compress_ptr cinfo, JSAMPARRAY scanlines, JDIMENSION num_lines))
          FUNCTION_LOADER(jpeg_finish_compress, void (*) (j_compress_ptr cinfo))
          FUNCTION_LOADER(jpeg_destroy_compress, void (*) (j_compress_ptr cinfo))
          FUNCTION_LOADER(jpeg_std_error, struct jpeg_error_mgr * (*) (struct jpeg_error_mgr * err))
      }
      ++lib.loaded;
  
      return 0;
  }
  void IMG_QuitJPG()
  {
      if ( lib.loaded == 0 ) {
          return;
      }
      if ( lib.loaded == 1 ) {
  #ifdef LOAD_JPG_DYNAMIC
          SDL_UnloadObject(lib.handle);
  #endif
      }
      --lib.loaded;
  }
  
  /* See if an image is contained in a data source */
  int IMG_isJPG(SDL_RWops *src)
  {
      Sint64 start;
      int is_JPG;
      int in_scan;
      Uint8 magic[4];
  
      /* This detection code is by Steaphan Greene <stea@cs.binghamton.edu> */
      /* Blame me, not Sam, if this doesn't work right. */
      /* And don't forget to report the problem to the the sdl list too! */
  
      if ( !src )
          return 0;
      start = SDL_RWtell(src);
      is_JPG = 0;
      in_scan = 0;
      if ( SDL_RWread(src, magic, 2, 1) ) {
          if ( (magic[0] == 0xFF) && (magic[1] == 0xD8) ) {
              is_JPG = 1;
              while (is_JPG == 1) {
                  if(SDL_RWread(src, magic, 1, 2) != 2) {
                      is_JPG = 0;
                  } else if( (magic[0] != 0xFF) && (in_scan == 0) ) {
                      is_JPG = 0;
                  } else if( (magic[0] != 0xFF) || (magic[1] == 0xFF) ) {
                      /* Extra padding in JPEG (legal) */
                      /* or this is data and we are scanning */
                      SDL_RWseek(src, -1, RW_SEEK_CUR);
                  } else if(magic[1] == 0xD9) {
                      /* Got to end of good JPEG */
                      break;
                  } else if( (in_scan == 1) && (magic[1] == 0x00) ) {
                      /* This is an encoded 0xFF within the data */
                  } else if( (magic[1] >= 0xD0) && (magic[1] < 0xD9) ) {
                      /* These have nothing else */
                  } else if(SDL_RWread(src, magic+2, 1, 2) != 2) {
                      is_JPG = 0;
                  } else {
                      /* Yes, it's big-endian */
                      Sint64 innerStart;
                      Uint32 size;
                      Sint64 end;
                      innerStart = SDL_RWtell(src);
                      size = (magic[2] << 8) + magic[3];
                      end = SDL_RWseek(src, size-2, RW_SEEK_CUR);
                      if ( end != innerStart + size - 2 ) is_JPG = 0;
                      if ( magic[1] == 0xDA ) {
                          /* Now comes the actual JPEG meat */
  #ifdef  FAST_IS_JPEG
                          /* Ok, I'm convinced.  It is a JPEG. */
                          break;
  #else
                          /* I'm not convinced.  Prove it! */
                          in_scan = 1;
  #endif
                      }
                  }
              }
          }
      }
      SDL_RWseek(src, start, RW_SEEK_SET);
      return(is_JPG);
  }
  
  #define INPUT_BUFFER_SIZE   4096
  typedef struct {
      struct jpeg_source_mgr pub;
  
      SDL_RWops *ctx;
      Uint8 buffer[INPUT_BUFFER_SIZE];
  } my_source_mgr;
  
  /*
   * Initialize source --- called by jpeg_read_header
   * before any data is actually read.
   */
  static void init_source (j_decompress_ptr cinfo)
  {
      /* We don't actually need to do anything */
      return;
  }
  
  /*
   * Fill the input buffer --- called whenever buffer is emptied.
   */
  static boolean fill_input_buffer (j_decompress_ptr cinfo)
  {
      my_source_mgr * src = (my_source_mgr *) cinfo->src;
      int nbytes;
  
      nbytes = (int)SDL_RWread(src->ctx, src->buffer, 1, INPUT_BUFFER_SIZE);
      if (nbytes <= 0) {
          /* Insert a fake EOI marker */
          src->buffer[0] = (Uint8) 0xFF;
          src->buffer[1] = (Uint8) JPEG_EOI;
          nbytes = 2;
      }
      src->pub.next_input_byte = src->buffer;
      src->pub.bytes_in_buffer = nbytes;
  
      return TRUE;
  }
  
  
  /*
   * Skip data --- used to skip over a potentially large amount of
   * uninteresting data (such as an APPn marker).
   *
   * Writers of suspendable-input applications must note that skip_input_data
   * is not granted the right to give a suspension return.  If the skip extends
   * beyond the data currently in the buffer, the buffer can be marked empty so
   * that the next read will cause a fill_input_buffer call that can suspend.
   * Arranging for additional bytes to be discarded before reloading the input
   * buffer is the application writer's problem.
   */
  static void skip_input_data (j_decompress_ptr cinfo, long num_bytes)
  {
      my_source_mgr * src = (my_source_mgr *) cinfo->src;
  
      /* Just a dumb implementation for now.  Could use fseek() except
       * it doesn't work on pipes.  Not clear that being smart is worth
       * any trouble anyway --- large skips are infrequent.
       */
      if (num_bytes > 0) {
          while (num_bytes > (long) src->pub.bytes_in_buffer) {
              num_bytes -= (long) src->pub.bytes_in_buffer;
              (void) src->pub.fill_input_buffer(cinfo);
              /* note we assume that fill_input_buffer will never
               * return FALSE, so suspension need not be handled.
               */
          }
          src->pub.next_input_byte += (size_t) num_bytes;
          src->pub.bytes_in_buffer -= (size_t) num_bytes;
      }
  }
  
  /*
   * Terminate source --- called by jpeg_finish_decompress
   * after all data has been read.
   */
  static void term_source (j_decompress_ptr cinfo)
  {
      /* We don't actually need to do anything */
      return;
  }
  
  /*
   * Prepare for input from a stdio stream.
   * The caller must have already opened the stream, and is responsible
   * for closing it after finishing decompression.
   */
  static void jpeg_SDL_RW_src (j_decompress_ptr cinfo, SDL_RWops *ctx)
  {
    my_source_mgr *src;
  
    /* The source object and input buffer are made permanent so that a series
     * of JPEG images can be read from the same file by calling jpeg_stdio_src
     * only before the first one.  (If we discarded the buffer at the end of
     * one image, we'd likely lose the start of the next one.)
     * This makes it unsafe to use this manager and a different source
     * manager serially with the same JPEG object.  Caveat programmer.
     */
    if (cinfo->src == NULL) { /* first time for this JPEG object? */
      cinfo->src = (struct jpeg_source_mgr *)
        (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
                    sizeof(my_source_mgr));
      src = (my_source_mgr *) cinfo->src;
    }
  
    src = (my_source_mgr *) cinfo->src;
    src->pub.init_source = init_source;
    src->pub.fill_input_buffer = fill_input_buffer;
    src->pub.skip_input_data = skip_input_data;
    src->pub.resync_to_restart = lib.jpeg_resync_to_restart; /* use default method */
    src->pub.term_source = term_source;
    src->ctx = ctx;
    src->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */
    src->pub.next_input_byte = NULL; /* until buffer loaded */
  }
  
  struct my_error_mgr {
      struct jpeg_error_mgr errmgr;
      jmp_buf escape;
  };
  
  static void my_error_exit(j_common_ptr cinfo)
  {
      struct my_error_mgr *err = (struct my_error_mgr *)cinfo->err;
      longjmp(err->escape, 1);
  }
  
  static void output_no_message(j_common_ptr cinfo)
  {
      /* do nothing */
  }
  
  /* Load a JPEG type image from an SDL datasource */
  SDL_Surface *IMG_LoadJPG_RW(SDL_RWops *src)
  {
      Sint64 start;
      struct jpeg_decompress_struct cinfo;
      JSAMPROW rowptr[1];
      SDL_Surface *volatile surface = NULL;
      struct my_error_mgr jerr;
  
      if ( !src ) {
          /* The error message has been set in SDL_RWFromFile */
          return NULL;
      }
      start = SDL_RWtell(src);
  
      if ( (IMG_Init(IMG_INIT_JPG) & IMG_INIT_JPG) == 0 ) {
          return NULL;
      }
  
      /* Create a decompression structure and load the JPEG header */
      cinfo.err = lib.jpeg_std_error(&jerr.errmgr);
      jerr.errmgr.error_exit = my_error_exit;
      jerr.errmgr.output_message = output_no_message;
      if(setjmp(jerr.escape)) {
          /* If we get here, libjpeg found an error */
          lib.jpeg_destroy_decompress(&cinfo);
          if ( surface != NULL ) {
              SDL_FreeSurface(surface);
          }
          SDL_RWseek(src, start, RW_SEEK_SET);
          IMG_SetError("JPEG loading error");
          return NULL;
      }
  
      lib.jpeg_create_decompress(&cinfo);
      jpeg_SDL_RW_src(&cinfo, src);
      lib.jpeg_read_header(&cinfo, TRUE);
  
      if(cinfo.num_components == 4) {
          /* Set 32-bit Raw output */
          cinfo.out_color_space = JCS_CMYK;
          cinfo.quantize_colors = FALSE;
          lib.jpeg_calc_output_dimensions(&cinfo);
  
          /* Allocate an output surface to hold the image */
          surface = SDL_CreateRGBSurface(SDL_SWSURFACE,
                  cinfo.output_width, cinfo.output_height, 32,
  #if SDL_BYTEORDER == SDL_LIL_ENDIAN
                             0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000);
  #else
                             0x0000FF00, 0x00FF0000, 0xFF000000, 0x000000FF);
  #endif
      } else {
          /* Set 24-bit RGB output */
          cinfo.out_color_space = JCS_RGB;
          cinfo.quantize_colors = FALSE;
  #ifdef FAST_JPEG
          cinfo.scale_num   = 1;
          cinfo.scale_denom = 1;
          cinfo.dct_method = JDCT_FASTEST;
          cinfo.do_fancy_upsampling = FALSE;
  #endif
          lib.jpeg_calc_output_dimensions(&cinfo);
  
          /* Allocate an output surface to hold the image */
          surface = SDL_CreateRGBSurface(SDL_SWSURFACE,
                  cinfo.output_width, cinfo.output_height, 24,
  #if SDL_BYTEORDER == SDL_LIL_ENDIAN
                             0x0000FF, 0x00FF00, 0xFF0000,
  #else
                             0xFF0000, 0x00FF00, 0x0000FF,
  #endif
                             0);
      }
  
      if ( surface == NULL ) {
          lib.jpeg_destroy_decompress(&cinfo);
          SDL_RWseek(src, start, RW_SEEK_SET);
          IMG_SetError("Out of memory");
          return NULL;
      }
  
      /* Decompress the image */
      lib.jpeg_start_decompress(&cinfo);
      while ( cinfo.output_scanline < cinfo.output_height ) {
          rowptr[0] = (JSAMPROW)(Uint8 *)surface->pixels +
                              cinfo.output_scanline * surface->pitch;
          lib.jpeg_read_scanlines(&cinfo, rowptr, (JDIMENSION) 1);
      }
      lib.jpeg_finish_decompress(&cinfo);
      lib.jpeg_destroy_decompress(&cinfo);
  
      return(surface);
  }
  
  #define OUTPUT_BUFFER_SIZE   4096
  typedef struct {
      struct jpeg_destination_mgr pub;
  
      SDL_RWops *ctx;
      Uint8 buffer[OUTPUT_BUFFER_SIZE];
  } my_destination_mgr;
  
  static void init_destination(j_compress_ptr cinfo)
  {
      /* We don't actually need to do anything */
      return;
  }
  
  static boolean empty_output_buffer(j_compress_ptr cinfo)
  {
      my_destination_mgr * dest = (my_destination_mgr *)cinfo->dest;
  
      /* In typical applications, it should write out the *entire* buffer */
      SDL_RWwrite(dest->ctx, dest->buffer, 1, OUTPUT_BUFFER_SIZE);
      dest->pub.next_output_byte = dest->buffer;
      dest->pub.free_in_buffer = OUTPUT_BUFFER_SIZE;
  
      return TRUE;
  }
  
  static void term_destination(j_compress_ptr cinfo)
  {
      my_destination_mgr * dest = (my_destination_mgr *)cinfo->dest;
  
      /*  In most applications, this must flush any data remaining in the buffer */
      SDL_RWwrite(dest->ctx, dest->buffer, 1, OUTPUT_BUFFER_SIZE - dest->pub.free_in_buffer);
  }
  
  static void jpeg_SDL_RW_dest(j_compress_ptr cinfo, SDL_RWops *ctx)
  {
      my_destination_mgr *dest;
  
      if (cinfo->dest == NULL) {
          cinfo->dest = (struct jpeg_destination_mgr *)
              (*cinfo->mem->alloc_small) ((j_common_ptr)cinfo, JPOOL_PERMANENT,
              sizeof(my_destination_mgr));
          dest = (my_destination_mgr *)cinfo->dest;
      }
  
      dest = (my_destination_mgr *)cinfo->dest;
      dest->pub.init_destination = init_destination;
      dest->pub.empty_output_buffer = empty_output_buffer;
      dest->pub.term_destination = term_destination;
      dest->ctx = ctx;
      dest->pub.next_output_byte = dest->buffer;
      dest->pub.free_in_buffer = OUTPUT_BUFFER_SIZE;
  }
  
  static int IMG_SaveJPG_RW_jpeglib(SDL_Surface *surface, SDL_RWops *dst, int freedst, int quality)
  {
  #if SDL_BYTEORDER == SDL_LIL_ENDIAN
      static const Uint32 jpg_format = SDL_PIXELFORMAT_RGB24;
  #else
      static const Uint32 jpg_format = SDL_PIXELFORMAT_BGR24;
  #endif
      struct jpeg_compress_struct cinfo;
      struct my_error_mgr jerr;
      JSAMPROW row_pointer[1];
      SDL_Surface* jpeg_surface = surface;
      int result = -1;
  
      if (!dst) {
          SDL_SetError("Passed NULL dst");
          goto done;
      }
  
      if (!IMG_Init(IMG_INIT_JPG)) {
          goto done;
      }
  
      /* Convert surface to format we can save */
      if (surface->format->format != jpg_format) {
          jpeg_surface = SDL_ConvertSurfaceFormat(surface, jpg_format, 0);
          if (!jpeg_surface) {
              goto done;
          }
      }
  
      /* Create a decompression structure and load the JPEG header */
      cinfo.err = lib.jpeg_std_error(&jerr.errmgr);
      jerr.errmgr.error_exit = my_error_exit;
      jerr.errmgr.output_message = output_no_message;
  
      lib.jpeg_create_compress(&cinfo);
      jpeg_SDL_RW_dest(&cinfo, dst);
  
      cinfo.image_width = surface->w;
      cinfo.image_height = surface->h;
      cinfo.in_color_space = JCS_RGB;
      cinfo.input_components = 3;
  
      lib.jpeg_set_defaults(&cinfo);
      lib.jpeg_set_quality(&cinfo, quality, TRUE);
      lib.jpeg_start_compress(&cinfo, TRUE);
  
      while (cinfo.next_scanline < cinfo.image_height) {
          int offset = cinfo.next_scanline * surface->pitch;
          row_pointer[0] = ((Uint8*)jpeg_surface->pixels) + offset;
          lib.jpeg_write_scanlines(&cinfo, row_pointer, 1);
      }
  
      lib.jpeg_finish_compress(&cinfo);
      lib.jpeg_destroy_compress(&cinfo);
  
      if (jpeg_surface != surface) {
          SDL_FreeSurface(jpeg_surface);
      }
  
      result = 0;
  
  done:
      if (freedst) {
          SDL_RWclose(dst);
      }
      return result;
  }
  
  #else
  
  int IMG_InitJPG()
  {
      IMG_SetError("JPEG images are not supported");
      return(-1);
  }
  
  void IMG_QuitJPG()
  {
  }
  
  /* See if an image is contained in a data source */
  int IMG_isJPG(SDL_RWops *src)
  {
      return(0);
  }
  
  /* Load a JPEG type image from an SDL datasource */
  SDL_Surface *IMG_LoadJPG_RW(SDL_RWops *src)
  {
      return(NULL);
  }
  
  #endif /* LOAD_JPG */
  
  #endif /* !defined(__APPLE__) || defined(SDL_IMAGE_USE_COMMON_BACKEND) */
  
  /* We'll always have JPG save support */
  #define SAVE_JPG
  
  #ifdef SAVE_JPG
  
  int IMG_SaveJPG(SDL_Surface *surface, const char *file, int quality)
  {
      SDL_RWops *dst = SDL_RWFromFile(file, "wb");
      if (dst) {
          return IMG_SaveJPG_RW(surface, dst, 1, quality);
      } else {
          return -1;
      }
  }
  
  int IMG_SaveJPG_RW(SDL_Surface *surface, SDL_RWops *dst, int freedst, int quality)
  {
  #ifdef USE_JPEGLIB
      return IMG_SaveJPG_RW_jpeglib(surface, dst, freedst, quality);
  #else
      return IMG_SetError("SDL_image not built with jpeglib, saving not supported");
  #endif
  }
  
  #endif /* SAVE_JPG */
  