IMG_jpg.c
author Ryan C. Gordon <icculus@icculus.org>
Thu, 08 Sep 2005 07:28:12 +0000
changeset 109 0d1bd5675598
parent 108 a02f321125f1
child 117 e613cf987897
permissions -rw-r--r--
SDL_image jpeg IPTC patch.

--ryan.



Date: Mon, 15 Aug 2005 21:24:39 -0700 (PDT)
From: Jeremy Stanley <stanmuffin@yahoo.com>
To: sdl@libsdl.org
Subject: [SDL] SDL_Image: IMG_isJPG() fails on pictures with IPTC information

I found that SDL_Image fails to load JPEG files to
which I have added a caption using Picasa (a free as
in beer picture organization tool;
http://www.picasa.com). Picasa claims to store its
captions in IPTC format. Photoshop apparently uses
this format too, but I don't have that program so I
can't verify that.

The problem is that the IPTC information block is
stored between the SOI (FF D8) and APP1 (FF E1)
markers in the JPEG file, so that the "JFIF" or "Exif"
constant is not stored at offset 6. Instead, the two
bytes at offset 2 store FF ED and the next two bytes
store the size of the block as a big endian word
(including the size field). The APP1 (FF E1) marker
will immediately follow.

A simple fix could be inserted just after the second
SDL_RWread() in IMG_isJPG(). Instead of throwing
these four bytes away, we can check for the IPTC
header and skip over it. The following code does the
trick for me.
     1 /*
     2     SDL_image:  An example image loading library for use with SDL
     3     Copyright (C) 1999-2004 Sam Lantinga
     4 
     5     This library is free software; you can redistribute it and/or
     6     modify it under the terms of the GNU Library General Public
     7     License as published by the Free Software Foundation; either
     8     version 2 of the License, or (at your option) any later version.
     9 
    10     This library is distributed in the hope that it will be useful,
    11     but WITHOUT ANY WARRANTY; without even the implied warranty of
    12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    13     Library General Public License for more details.
    14 
    15     You should have received a copy of the GNU Library General Public
    16     License along with this library; if not, write to the Free
    17     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
    18 
    19     Sam Lantinga
    20     slouken@libsdl.org
    21 */
    22 
    23 /* $Id$ */
    24 
    25 /* This is a JPEG image file loading framework */
    26 
    27 #include <stdio.h>
    28 #include <string.h>
    29 #include <setjmp.h>
    30 
    31 #include "SDL_image.h"
    32 
    33 #ifdef LOAD_JPG
    34 
    35 #include <jpeglib.h>
    36 
    37 /* Define this for fast loading and not as good image quality */
    38 /*#define FAST_JPEG*/
    39 
    40 /* See if an image is contained in a data source */
    41 int IMG_isJPG(SDL_RWops *src)
    42 {
    43 	int is_JPG;
    44 	Uint8 magic[4];
    45 
    46 	is_JPG = 0;
    47 	if ( SDL_RWread(src, magic, 2, 1) ) {
    48 		if ( (magic[0] == 0xFF) && (magic[1] == 0xD8) ) {
    49 			SDL_RWread(src, magic, 4, 1);
    50 
    51 			/* skip IPTC info block if it exists... */
    52 			if ( (magic[0] == 0xFF) && (magic[1] == 0xED) ) {
    53 				SDL_RWseek(src, (((Uint16)magic[2] << 8) |magic[3]) + 2, SEEK_CUR);
    54 			}
    55 
    56 			SDL_RWread(src, magic, 4, 1);
    57 			if ( memcmp((char *)magic, "JFIF", 4) == 0 ||
    58 			     memcmp((char *)magic, "Exif", 4) == 0 ||
    59 			     memcmp((char *)magic, "VVL", 3) == 0 ) {
    60 				is_JPG = 1;
    61 			}
    62 		}
    63 	}
    64 	return(is_JPG);
    65 }
    66 
    67 #define INPUT_BUFFER_SIZE	4096
    68 typedef struct {
    69 	struct jpeg_source_mgr pub;
    70 
    71 	SDL_RWops *ctx;
    72 	Uint8 buffer[INPUT_BUFFER_SIZE];
    73 } my_source_mgr;
    74 
    75 /*
    76  * Initialize source --- called by jpeg_read_header
    77  * before any data is actually read.
    78  */
    79 static void init_source (j_decompress_ptr cinfo)
    80 {
    81 	/* We don't actually need to do anything */
    82 	return;
    83 }
    84 
    85 /*
    86  * Fill the input buffer --- called whenever buffer is emptied.
    87  */
    88 static int fill_input_buffer (j_decompress_ptr cinfo)
    89 {
    90 	my_source_mgr * src = (my_source_mgr *) cinfo->src;
    91 	int nbytes;
    92 
    93 	nbytes = SDL_RWread(src->ctx, src->buffer, 1, INPUT_BUFFER_SIZE);
    94 	if (nbytes <= 0) {
    95 		/* Insert a fake EOI marker */
    96 		src->buffer[0] = (Uint8) 0xFF;
    97 		src->buffer[1] = (Uint8) JPEG_EOI;
    98 		nbytes = 2;
    99 	}
   100 	src->pub.next_input_byte = src->buffer;
   101 	src->pub.bytes_in_buffer = nbytes;
   102 
   103 	return TRUE;
   104 }
   105 
   106 
   107 /*
   108  * Skip data --- used to skip over a potentially large amount of
   109  * uninteresting data (such as an APPn marker).
   110  *
   111  * Writers of suspendable-input applications must note that skip_input_data
   112  * is not granted the right to give a suspension return.  If the skip extends
   113  * beyond the data currently in the buffer, the buffer can be marked empty so
   114  * that the next read will cause a fill_input_buffer call that can suspend.
   115  * Arranging for additional bytes to be discarded before reloading the input
   116  * buffer is the application writer's problem.
   117  */
   118 static void skip_input_data (j_decompress_ptr cinfo, long num_bytes)
   119 {
   120 	my_source_mgr * src = (my_source_mgr *) cinfo->src;
   121 
   122 	/* Just a dumb implementation for now.	Could use fseek() except
   123 	 * it doesn't work on pipes.  Not clear that being smart is worth
   124 	 * any trouble anyway --- large skips are infrequent.
   125 	 */
   126 	if (num_bytes > 0) {
   127 		while (num_bytes > (long) src->pub.bytes_in_buffer) {
   128 			num_bytes -= (long) src->pub.bytes_in_buffer;
   129 			(void) src->pub.fill_input_buffer(cinfo);
   130 			/* note we assume that fill_input_buffer will never
   131 			 * return FALSE, so suspension need not be handled.
   132 			 */
   133 		}
   134 		src->pub.next_input_byte += (size_t) num_bytes;
   135 		src->pub.bytes_in_buffer -= (size_t) num_bytes;
   136 	}
   137 }
   138 
   139 /*
   140  * Terminate source --- called by jpeg_finish_decompress
   141  * after all data has been read.
   142  */
   143 static void term_source (j_decompress_ptr cinfo)
   144 {
   145 	/* We don't actually need to do anything */
   146 	return;
   147 }
   148 
   149 /*
   150  * Prepare for input from a stdio stream.
   151  * The caller must have already opened the stream, and is responsible
   152  * for closing it after finishing decompression.
   153  */
   154 static void jpeg_SDL_RW_src (j_decompress_ptr cinfo, SDL_RWops *ctx)
   155 {
   156   my_source_mgr *src;
   157 
   158   /* The source object and input buffer are made permanent so that a series
   159    * of JPEG images can be read from the same file by calling jpeg_stdio_src
   160    * only before the first one.  (If we discarded the buffer at the end of
   161    * one image, we'd likely lose the start of the next one.)
   162    * This makes it unsafe to use this manager and a different source
   163    * manager serially with the same JPEG object.  Caveat programmer.
   164    */
   165   if (cinfo->src == NULL) {	/* first time for this JPEG object? */
   166     cinfo->src = (struct jpeg_source_mgr *)
   167       (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
   168 				  sizeof(my_source_mgr));
   169     src = (my_source_mgr *) cinfo->src;
   170   }
   171 
   172   src = (my_source_mgr *) cinfo->src;
   173   src->pub.init_source = init_source;
   174   src->pub.fill_input_buffer = fill_input_buffer;
   175   src->pub.skip_input_data = skip_input_data;
   176   src->pub.resync_to_restart = jpeg_resync_to_restart; /* use default method */
   177   src->pub.term_source = term_source;
   178   src->ctx = ctx;
   179   src->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */
   180   src->pub.next_input_byte = NULL; /* until buffer loaded */
   181 }
   182 
   183 struct my_error_mgr {
   184 	struct jpeg_error_mgr errmgr;
   185 	jmp_buf escape;
   186 };
   187 
   188 static void my_error_exit(j_common_ptr cinfo)
   189 {
   190 	struct my_error_mgr *err = (struct my_error_mgr *)cinfo->err;
   191 	longjmp(err->escape, 1);
   192 }
   193 
   194 static void output_no_message(j_common_ptr cinfo)
   195 {
   196 	/* do nothing */
   197 }
   198 
   199 /* Load a JPEG type image from an SDL datasource */
   200 SDL_Surface *IMG_LoadJPG_RW(SDL_RWops *src)
   201 {
   202 	struct jpeg_decompress_struct cinfo;
   203 	JSAMPROW rowptr[1];
   204 	SDL_Surface *volatile surface = NULL;
   205 	struct my_error_mgr jerr;
   206 
   207 	if ( !src ) {
   208 		/* The error message has been set in SDL_RWFromFile */
   209 		return NULL;
   210 	}
   211 
   212 	/* Create a decompression structure and load the JPEG header */
   213 	cinfo.err = jpeg_std_error(&jerr.errmgr);
   214 	jerr.errmgr.error_exit = my_error_exit;
   215 	jerr.errmgr.output_message = output_no_message;
   216 	if(setjmp(jerr.escape)) {
   217 		/* If we get here, libjpeg found an error */
   218 		jpeg_destroy_decompress(&cinfo);
   219 		IMG_SetError("JPEG loading error");
   220 		SDL_FreeSurface(surface);
   221 		return NULL;
   222 	}
   223 
   224 	jpeg_create_decompress(&cinfo);
   225 	jpeg_SDL_RW_src(&cinfo, src);
   226 	jpeg_read_header(&cinfo, TRUE);
   227 
   228 	/* Set 24-bit RGB output */
   229 	cinfo.out_color_space = JCS_RGB;
   230 	cinfo.quantize_colors = FALSE;
   231 #ifdef FAST_JPEG
   232 	cinfo.scale_num   = 1;
   233 	cinfo.scale_denom = 1;
   234 	cinfo.dct_method = JDCT_FASTEST;
   235 	cinfo.do_fancy_upsampling = FALSE;
   236 #endif
   237 	jpeg_calc_output_dimensions(&cinfo);
   238 
   239 	/* Allocate an output surface to hold the image */
   240 	surface = SDL_AllocSurface(SDL_SWSURFACE,
   241 	               cinfo.output_width, cinfo.output_height, 24,
   242 #if SDL_BYTEORDER == SDL_LIL_ENDIAN
   243 	                           0x0000FF, 0x00FF00, 0xFF0000,
   244 #else
   245 	                           0xFF0000, 0x00FF00, 0x0000FF,
   246 #endif
   247 				   0);
   248 	if ( surface == NULL ) {
   249 		IMG_SetError("Out of memory");
   250 		goto done;
   251 	}
   252 
   253 	/* Decompress the image */
   254 	jpeg_start_decompress(&cinfo);
   255 	while ( cinfo.output_scanline < cinfo.output_height ) {
   256 		rowptr[0] = (JSAMPROW)(Uint8 *)surface->pixels +
   257 		                    cinfo.output_scanline * surface->pitch;
   258 		jpeg_read_scanlines(&cinfo, rowptr, (JDIMENSION) 1);
   259 	}
   260 	jpeg_finish_decompress(&cinfo);
   261 
   262 	/* Clean up and return */
   263 done:
   264 	jpeg_destroy_decompress(&cinfo);
   265 	return(surface);
   266 }
   267 
   268 #else
   269 
   270 /* See if an image is contained in a data source */
   271 int IMG_isJPG(SDL_RWops *src)
   272 {
   273 	return(0);
   274 }
   275 
   276 /* Load a JPEG type image from an SDL datasource */
   277 SDL_Surface *IMG_LoadJPG_RW(SDL_RWops *src)
   278 {
   279 	return(NULL);
   280 }
   281 
   282 #endif /* LOAD_JPG */