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