IMG_jpg.c
author Sam Lantinga <slouken@libsdl.org>
Thu, 20 Apr 2006 16:52:50 +0000
changeset 123 1f7908a9cabb
parent 121 1bf9c0c87374
child 143 e01fee7bdf3b
permissions -rw-r--r--
Date: Mon, 10 Apr 2006 08:29:17 -0400
From: Stea Greene
Subject: [SDL] Patch for JPEG Loading in SDL_image

This fixes the JPEG detection routine in SDL_image such that it should
be able to detect any JPEG file, JFIF or not. It does this by parsing
the headers and tags, rather than expecting specific ones in order with
no gaps.

This also enables it to load raw 32-bit JPEG files, with an alpha
channel. While not defined specifically in the JPEG spec (as far as I
know), this is the format used internally by the .blp files in some of
Blizzard's recent titles. It detects this from the JPEG itself, and
should not cause any problems with any normal JPEGs that were loadable
previously (in fact, it should make more of them loadable).

I've tested it on both little and big endian and on just about every
kind of file I can find. So far, it has worked perfectly in every case.
I've also tested it with a number of .blp files and it works great.
Note that .blp is NOT really an image format, so this won't load them
straight up. You have to process them a bit before you can load them as
JPEGs. Once you do that (and it's trivial), this patch makes SDL_image
load them fine.
slouken@0
     1
/*
slouken@53
     2
    SDL_image:  An example image loading library for use with SDL
slouken@121
     3
    Copyright (C) 1997-2006 Sam Lantinga
slouken@0
     4
slouken@0
     5
    This library is free software; you can redistribute it and/or
slouken@121
     6
    modify it under the terms of the GNU Lesser General Public
slouken@0
     7
    License as published by the Free Software Foundation; either
slouken@121
     8
    version 2.1 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@121
    13
    Lesser General Public License for more details.
slouken@0
    14
slouken@121
    15
    You should have received a copy of the GNU Lesser General Public
slouken@121
    16
    License along with this library; if not, write to the Free Software
slouken@121
    17
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
slouken@0
    18
slouken@0
    19
    Sam Lantinga
slouken@53
    20
    slouken@libsdl.org
slouken@0
    21
*/
slouken@0
    22
slouken@0
    23
/* This is a JPEG image file loading framework */
slouken@0
    24
slouken@0
    25
#include <stdio.h>
slouken@7
    26
#include <string.h>
slouken@61
    27
#include <setjmp.h>
slouken@0
    28
slouken@0
    29
#include "SDL_image.h"
slouken@0
    30
slouken@0
    31
#ifdef LOAD_JPG
slouken@0
    32
slouken@0
    33
#include <jpeglib.h>
slouken@0
    34
slouken@0
    35
/* Define this for fast loading and not as good image quality */
slouken@0
    36
/*#define FAST_JPEG*/
slouken@0
    37
slouken@123
    38
/* Define this for quicker (but less perfect) JPEG identification */
slouken@123
    39
#define FAST_IS_JPEG
slouken@123
    40
slouken@0
    41
/* See if an image is contained in a data source */
slouken@0
    42
int IMG_isJPG(SDL_RWops *src)
slouken@0
    43
{
slouken@117
    44
	int start;
slouken@0
    45
	int is_JPG;
slouken@123
    46
	int in_scan;
slouken@0
    47
	Uint8 magic[4];
slouken@0
    48
slouken@123
    49
	/* This detection code is by Steaphan Greene <stea@cs.binghamton.edu> */
slouken@123
    50
	/* Blame me, not Sam, if this doesn't work right. */
slouken@123
    51
	/* And don't forget to report the problem to the the sdl list too! */
slouken@123
    52
slouken@117
    53
	start = SDL_RWtell(src);
slouken@0
    54
	is_JPG = 0;
slouken@123
    55
	in_scan = 0;
slouken@0
    56
	if ( SDL_RWread(src, magic, 2, 1) ) {
slouken@0
    57
		if ( (magic[0] == 0xFF) && (magic[1] == 0xD8) ) {
slouken@123
    58
			is_JPG = 1;
slouken@123
    59
			while (is_JPG == 1) {
slouken@123
    60
				if(SDL_RWread(src, magic, 1, 2) != 2) {
slouken@123
    61
					is_JPG = 0;
slouken@123
    62
				} else if( (magic[0] != 0xFF) && (in_scan == 0) ) {
slouken@123
    63
					is_JPG = 0;
slouken@123
    64
				} else if( (magic[0] != 0xFF) || (magic[1] == 0xFF) ) {
slouken@123
    65
					/* Extra padding in JPEG (legal) */
slouken@123
    66
					/* or this is data and we are scanning */
slouken@123
    67
					SDL_RWseek(src, -1, SEEK_CUR);
slouken@123
    68
				} else if(magic[1] == 0xD9) {
slouken@123
    69
					/* Got to end of good JPEG */
slouken@123
    70
					break;
slouken@123
    71
				} else if( (in_scan == 1) && (magic[1] == 0x00) ) {
slouken@123
    72
					/* This is an encoded 0xFF within the data */
slouken@123
    73
				} else if( (magic[1] >= 0xD0) && (magic[1] < 0xD9) ) {
slouken@123
    74
					/* These have nothing else */
slouken@123
    75
				} else if(SDL_RWread(src, magic+2, 1, 2) != 2) {
slouken@123
    76
					is_JPG = 0;
slouken@123
    77
				} else {
slouken@123
    78
					/* Yes, it's big-endian */
slouken@123
    79
					Uint32 start;
slouken@123
    80
					Uint32 size;
slouken@123
    81
					Uint32 end;
slouken@123
    82
					start = SDL_RWtell(src);
slouken@123
    83
					size = (magic[2] << 8) + magic[3];
slouken@123
    84
					end = SDL_RWseek(src, size-2, SEEK_CUR);
slouken@123
    85
					if ( end != start + size - 2 ) is_JPG = 0;
slouken@123
    86
					if ( magic[1] == 0xDA ) {
slouken@123
    87
						/* Now comes the actual JPEG meat */
slouken@123
    88
#ifdef	FAST_IS_JPEG
slouken@123
    89
						/* Ok, I'm convinced.  It is a JPEG. */
slouken@123
    90
						break;
slouken@123
    91
#else
slouken@123
    92
						/* I'm not convinced.  Prove it! */
slouken@123
    93
						in_scan = 1;
slouken@123
    94
#endif
slouken@123
    95
					}
slouken@123
    96
				}
slouken@0
    97
			}
slouken@0
    98
		}
slouken@0
    99
	}
slouken@117
   100
	SDL_RWseek(src, start, SEEK_SET);
slouken@0
   101
	return(is_JPG);
slouken@0
   102
}
slouken@0
   103
slouken@0
   104
#define INPUT_BUFFER_SIZE	4096
slouken@0
   105
typedef struct {
slouken@0
   106
	struct jpeg_source_mgr pub;
slouken@0
   107
slouken@0
   108
	SDL_RWops *ctx;
slouken@0
   109
	Uint8 buffer[INPUT_BUFFER_SIZE];
slouken@0
   110
} my_source_mgr;
slouken@0
   111
slouken@0
   112
/*
slouken@0
   113
 * Initialize source --- called by jpeg_read_header
slouken@0
   114
 * before any data is actually read.
slouken@0
   115
 */
slouken@27
   116
static void init_source (j_decompress_ptr cinfo)
slouken@0
   117
{
slouken@0
   118
	/* We don't actually need to do anything */
slouken@0
   119
	return;
slouken@0
   120
}
slouken@0
   121
slouken@0
   122
/*
slouken@0
   123
 * Fill the input buffer --- called whenever buffer is emptied.
slouken@0
   124
 */
slouken@27
   125
static int fill_input_buffer (j_decompress_ptr cinfo)
slouken@0
   126
{
slouken@0
   127
	my_source_mgr * src = (my_source_mgr *) cinfo->src;
slouken@0
   128
	int nbytes;
slouken@0
   129
slouken@0
   130
	nbytes = SDL_RWread(src->ctx, src->buffer, 1, INPUT_BUFFER_SIZE);
slouken@0
   131
	if (nbytes <= 0) {
slouken@0
   132
		/* Insert a fake EOI marker */
slouken@0
   133
		src->buffer[0] = (Uint8) 0xFF;
slouken@0
   134
		src->buffer[1] = (Uint8) JPEG_EOI;
slouken@0
   135
		nbytes = 2;
slouken@0
   136
	}
slouken@0
   137
	src->pub.next_input_byte = src->buffer;
slouken@0
   138
	src->pub.bytes_in_buffer = nbytes;
slouken@0
   139
slouken@0
   140
	return TRUE;
slouken@0
   141
}
slouken@0
   142
slouken@0
   143
slouken@0
   144
/*
slouken@0
   145
 * Skip data --- used to skip over a potentially large amount of
slouken@0
   146
 * uninteresting data (such as an APPn marker).
slouken@0
   147
 *
slouken@0
   148
 * Writers of suspendable-input applications must note that skip_input_data
slouken@0
   149
 * is not granted the right to give a suspension return.  If the skip extends
slouken@0
   150
 * beyond the data currently in the buffer, the buffer can be marked empty so
slouken@0
   151
 * that the next read will cause a fill_input_buffer call that can suspend.
slouken@0
   152
 * Arranging for additional bytes to be discarded before reloading the input
slouken@0
   153
 * buffer is the application writer's problem.
slouken@0
   154
 */
slouken@27
   155
static void skip_input_data (j_decompress_ptr cinfo, long num_bytes)
slouken@0
   156
{
slouken@0
   157
	my_source_mgr * src = (my_source_mgr *) cinfo->src;
slouken@0
   158
slouken@0
   159
	/* Just a dumb implementation for now.	Could use fseek() except
slouken@0
   160
	 * it doesn't work on pipes.  Not clear that being smart is worth
slouken@0
   161
	 * any trouble anyway --- large skips are infrequent.
slouken@0
   162
	 */
slouken@0
   163
	if (num_bytes > 0) {
slouken@0
   164
		while (num_bytes > (long) src->pub.bytes_in_buffer) {
slouken@0
   165
			num_bytes -= (long) src->pub.bytes_in_buffer;
slouken@0
   166
			(void) src->pub.fill_input_buffer(cinfo);
slouken@0
   167
			/* note we assume that fill_input_buffer will never
slouken@0
   168
			 * return FALSE, so suspension need not be handled.
slouken@0
   169
			 */
slouken@0
   170
		}
slouken@0
   171
		src->pub.next_input_byte += (size_t) num_bytes;
slouken@0
   172
		src->pub.bytes_in_buffer -= (size_t) num_bytes;
slouken@0
   173
	}
slouken@0
   174
}
slouken@0
   175
slouken@0
   176
/*
slouken@0
   177
 * Terminate source --- called by jpeg_finish_decompress
slouken@0
   178
 * after all data has been read.
slouken@0
   179
 */
slouken@27
   180
static void term_source (j_decompress_ptr cinfo)
slouken@0
   181
{
slouken@0
   182
	/* We don't actually need to do anything */
slouken@0
   183
	return;
slouken@0
   184
}
slouken@0
   185
slouken@0
   186
/*
slouken@0
   187
 * Prepare for input from a stdio stream.
slouken@0
   188
 * The caller must have already opened the stream, and is responsible
slouken@0
   189
 * for closing it after finishing decompression.
slouken@0
   190
 */
slouken@27
   191
static void jpeg_SDL_RW_src (j_decompress_ptr cinfo, SDL_RWops *ctx)
slouken@0
   192
{
slouken@0
   193
  my_source_mgr *src;
slouken@0
   194
slouken@0
   195
  /* The source object and input buffer are made permanent so that a series
slouken@0
   196
   * of JPEG images can be read from the same file by calling jpeg_stdio_src
slouken@0
   197
   * only before the first one.  (If we discarded the buffer at the end of
slouken@0
   198
   * one image, we'd likely lose the start of the next one.)
slouken@0
   199
   * This makes it unsafe to use this manager and a different source
slouken@0
   200
   * manager serially with the same JPEG object.  Caveat programmer.
slouken@0
   201
   */
slouken@0
   202
  if (cinfo->src == NULL) {	/* first time for this JPEG object? */
slouken@0
   203
    cinfo->src = (struct jpeg_source_mgr *)
slouken@0
   204
      (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
slouken@0
   205
				  sizeof(my_source_mgr));
slouken@0
   206
    src = (my_source_mgr *) cinfo->src;
slouken@0
   207
  }
slouken@0
   208
slouken@0
   209
  src = (my_source_mgr *) cinfo->src;
slouken@27
   210
  src->pub.init_source = init_source;
slouken@27
   211
  src->pub.fill_input_buffer = fill_input_buffer;
slouken@27
   212
  src->pub.skip_input_data = skip_input_data;
slouken@0
   213
  src->pub.resync_to_restart = jpeg_resync_to_restart; /* use default method */
slouken@27
   214
  src->pub.term_source = term_source;
slouken@0
   215
  src->ctx = ctx;
slouken@0
   216
  src->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */
slouken@0
   217
  src->pub.next_input_byte = NULL; /* until buffer loaded */
slouken@0
   218
}
slouken@0
   219
slouken@61
   220
struct my_error_mgr {
slouken@61
   221
	struct jpeg_error_mgr errmgr;
slouken@61
   222
	jmp_buf escape;
slouken@61
   223
};
slouken@61
   224
slouken@61
   225
static void my_error_exit(j_common_ptr cinfo)
slouken@61
   226
{
slouken@61
   227
	struct my_error_mgr *err = (struct my_error_mgr *)cinfo->err;
slouken@61
   228
	longjmp(err->escape, 1);
slouken@61
   229
}
slouken@61
   230
slouken@61
   231
static void output_no_message(j_common_ptr cinfo)
slouken@61
   232
{
slouken@61
   233
	/* do nothing */
slouken@61
   234
}
slouken@61
   235
slouken@0
   236
/* Load a JPEG type image from an SDL datasource */
slouken@0
   237
SDL_Surface *IMG_LoadJPG_RW(SDL_RWops *src)
slouken@0
   238
{
slouken@118
   239
	int start;
slouken@0
   240
	struct jpeg_decompress_struct cinfo;
slouken@0
   241
	JSAMPROW rowptr[1];
slouken@61
   242
	SDL_Surface *volatile surface = NULL;
slouken@61
   243
	struct my_error_mgr jerr;
slouken@0
   244
slouken@98
   245
	if ( !src ) {
slouken@98
   246
		/* The error message has been set in SDL_RWFromFile */
slouken@98
   247
		return NULL;
slouken@98
   248
	}
slouken@118
   249
	start = SDL_RWtell(src);
slouken@98
   250
slouken@0
   251
	/* Create a decompression structure and load the JPEG header */
slouken@61
   252
	cinfo.err = jpeg_std_error(&jerr.errmgr);
slouken@61
   253
	jerr.errmgr.error_exit = my_error_exit;
slouken@61
   254
	jerr.errmgr.output_message = output_no_message;
slouken@61
   255
	if(setjmp(jerr.escape)) {
slouken@61
   256
		/* If we get here, libjpeg found an error */
slouken@61
   257
		jpeg_destroy_decompress(&cinfo);
slouken@118
   258
		if ( surface != NULL ) {
slouken@118
   259
			SDL_FreeSurface(surface);
slouken@118
   260
		}
slouken@118
   261
		SDL_RWseek(src, start, SEEK_SET);
slouken@61
   262
		IMG_SetError("JPEG loading error");
slouken@61
   263
		return NULL;
slouken@61
   264
	}
slouken@61
   265
slouken@0
   266
	jpeg_create_decompress(&cinfo);
slouken@0
   267
	jpeg_SDL_RW_src(&cinfo, src);
slouken@0
   268
	jpeg_read_header(&cinfo, TRUE);
slouken@0
   269
slouken@123
   270
	if(cinfo.num_components == 4) {
slouken@123
   271
		/* Set 32-bit Raw output */
slouken@123
   272
		cinfo.out_color_space = JCS_CMYK;
slouken@123
   273
		cinfo.quantize_colors = FALSE;
slouken@123
   274
		jpeg_calc_output_dimensions(&cinfo);
slouken@123
   275
slouken@123
   276
		/* Allocate an output surface to hold the image */
slouken@123
   277
		surface = SDL_AllocSurface(SDL_SWSURFACE,
slouken@123
   278
		        cinfo.output_width, cinfo.output_height, 32,
slouken@123
   279
#if SDL_BYTEORDER == SDL_LIL_ENDIAN
slouken@123
   280
		                   0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000);
slouken@123
   281
#else
slouken@123
   282
		                   0x0000FF00, 0x00FF0000, 0xFF000000, 0x000000FF);
slouken@123
   283
#endif
slouken@123
   284
	} else {
slouken@123
   285
		/* Set 24-bit RGB output */
slouken@123
   286
		cinfo.out_color_space = JCS_RGB;
slouken@123
   287
		cinfo.quantize_colors = FALSE;
slouken@0
   288
#ifdef FAST_JPEG
slouken@123
   289
		cinfo.scale_num   = 1;
slouken@123
   290
		cinfo.scale_denom = 1;
slouken@123
   291
		cinfo.dct_method = JDCT_FASTEST;
slouken@123
   292
		cinfo.do_fancy_upsampling = FALSE;
slouken@0
   293
#endif
slouken@123
   294
		jpeg_calc_output_dimensions(&cinfo);
slouken@0
   295
slouken@123
   296
		/* Allocate an output surface to hold the image */
slouken@123
   297
		surface = SDL_AllocSurface(SDL_SWSURFACE,
slouken@123
   298
		        cinfo.output_width, cinfo.output_height, 24,
slouken@0
   299
#if SDL_BYTEORDER == SDL_LIL_ENDIAN
slouken@123
   300
		                   0x0000FF, 0x00FF00, 0xFF0000,
slouken@0
   301
#else
slouken@123
   302
		                   0xFF0000, 0x00FF00, 0x0000FF,
slouken@0
   303
#endif
slouken@123
   304
		                   0);
slouken@123
   305
	}
slouken@123
   306
slouken@0
   307
	if ( surface == NULL ) {
slouken@118
   308
		jpeg_destroy_decompress(&cinfo);
slouken@118
   309
		SDL_RWseek(src, start, SEEK_SET);
slouken@0
   310
		IMG_SetError("Out of memory");
slouken@118
   311
		return NULL;
slouken@0
   312
	}
slouken@0
   313
slouken@0
   314
	/* Decompress the image */
slouken@0
   315
	jpeg_start_decompress(&cinfo);
slouken@0
   316
	while ( cinfo.output_scanline < cinfo.output_height ) {
slouken@0
   317
		rowptr[0] = (JSAMPROW)(Uint8 *)surface->pixels +
slouken@0
   318
		                    cinfo.output_scanline * surface->pitch;
slouken@0
   319
		jpeg_read_scanlines(&cinfo, rowptr, (JDIMENSION) 1);
slouken@0
   320
	}
slouken@0
   321
	jpeg_finish_decompress(&cinfo);
slouken@118
   322
	jpeg_destroy_decompress(&cinfo);
slouken@0
   323
slouken@0
   324
	return(surface);
slouken@0
   325
}
slouken@0
   326
slouken@0
   327
#else
slouken@0
   328
slouken@0
   329
/* See if an image is contained in a data source */
slouken@0
   330
int IMG_isJPG(SDL_RWops *src)
slouken@0
   331
{
slouken@0
   332
	return(0);
slouken@0
   333
}
slouken@0
   334
slouken@0
   335
/* Load a JPEG type image from an SDL datasource */
slouken@0
   336
SDL_Surface *IMG_LoadJPG_RW(SDL_RWops *src)
slouken@0
   337
{
slouken@0
   338
	return(NULL);
slouken@0
   339
}
slouken@0
   340
slouken@0
   341
#endif /* LOAD_JPG */