diff --git a/src/IMG_stb.c b/src/IMG_stb.c index acfc3fa3..be3479a7 100644 --- a/src/IMG_stb.c +++ b/src/IMG_stb.c @@ -77,10 +77,13 @@ static int IMG_LoadSTB_RW_eof(void *user) SDL_Surface *IMG_LoadSTB_RW(SDL_RWops *src) { Sint64 start; + Uint8 magic[26]; int w, h, format; stbi_uc *pixels; stbi_io_callbacks rw_callbacks; SDL_Surface *surface = NULL; + SDL_bool use_palette = SDL_FALSE; + unsigned int palette_colors[256]; if (!src) { /* The error message has been set in SDL_RWFromFile */ @@ -88,25 +91,82 @@ SDL_Surface *IMG_LoadSTB_RW(SDL_RWops *src) } start = SDL_RWtell(src); + if (SDL_RWread(src, magic, sizeof(magic)) == sizeof(magic)) { + const Uint8 PNG_COLOR_INDEXED = 3; + if (magic[0] == 0x89 && + magic[1] == 'P' && + magic[2] == 'N' && + magic[3] == 'G' && + magic[12] == 'I' && + magic[13] == 'H' && + magic[14] == 'D' && + magic[15] == 'R' && + magic[25] == PNG_COLOR_INDEXED) { + use_palette = SDL_TRUE; + } + } + SDL_RWseek(src, start, SDL_RW_SEEK_SET); + /* Load the image data */ rw_callbacks.read = IMG_LoadSTB_RW_read; rw_callbacks.skip = IMG_LoadSTB_RW_skip; rw_callbacks.eof = IMG_LoadSTB_RW_eof; w = h = format = 0; /* silence warning */ - pixels = stbi_load_from_callbacks( - &rw_callbacks, - src, - &w, - &h, - &format, - STBI_default - ); + if (use_palette) { + pixels = stbi_load_from_callbacks_with_palette( + &rw_callbacks, + src, + &w, + &h, + palette_colors, + SDL_arraysize(palette_colors) + ); + } else { + pixels = stbi_load_from_callbacks( + &rw_callbacks, + src, + &w, + &h, + &format, + STBI_default + ); + } if (!pixels) { SDL_RWseek(src, start, SDL_RW_SEEK_SET); return NULL; } - if (format == STBI_grey || format == STBI_rgb || format == STBI_rgb_alpha) { + if (use_palette) { + surface = SDL_CreateSurfaceFrom( + pixels, + w, + h, + w, + SDL_PIXELFORMAT_INDEX8 + ); + if (surface) { + SDL_Palette *palette = surface->format->palette; + if (palette) { + int i; + Uint8 *palette_bytes = (Uint8 *)palette_colors; + + for (i = 0; i < palette->ncolors; i++) { + palette->colors[i].r = *palette_bytes++; + palette->colors[i].g = *palette_bytes++; + palette->colors[i].b = *palette_bytes++; + palette->colors[i].a = *palette_bytes++; + } + } + + /* FIXME: This sucks. It'd be better to allocate the surface first, then + * write directly to the pixel buffer: + * https://github.com/nothings/stb/issues/58 + * -flibit + */ + surface->flags &= ~SDL_PREALLOC; + } + + } else if (format == STBI_grey || format == STBI_rgb || format == STBI_rgb_alpha) { surface = SDL_CreateSurfaceFrom( pixels, w, diff --git a/src/stb_image.h b/src/stb_image.h index 6c559fb7..eaeff8a3 100644 --- a/src/stb_image.h +++ b/src/stb_image.h @@ -92,7 +92,7 @@ RECENT REVISION HISTORY: Optimizations & bugfixes Mikhail Morozov (1-bit BMP) Fabian "ryg" Giesen Anael Seghezzi (is-16-bit query) Arseny Kapoulkine Simon Breuss (16-bit PNM) - John-Mark Allen + John-Mark Allen Katelyn Gadd (indexed color loading) Carmelo J Fdez-Aguera Bug & warning fixes @@ -442,6 +442,18 @@ STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int * STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input); #endif +//////////////////////////////////// +// +// 8-bits-per-channel indexed color +// Will fail if image is not an 8-bit PNG or TGA with a palette. +// Palette buffer needs to be at least 256 entries for PNG. +// + +#if 0 /* not used in SDL_image */ +STBIDEF stbi_uc *stbi_load_from_memory_with_palette (stbi_uc const *buffer, int len , int *x, int *y, unsigned int *palette_buffer, int palette_buffer_len); +#endif +STBIDEF stbi_uc *stbi_load_from_callbacks_with_palette(stbi_io_callbacks const *clbk, void *user, int *x, int *y, unsigned int *palette_buffer, int palette_buffer_len); + //////////////////////////////////// // // 16-bits-per-channel interface @@ -694,6 +706,10 @@ typedef Uint32 stbi__uint32; typedef Sint32 stbi__int32; #endif +#ifndef STBI_BUFFER_SIZE +#define STBI_BUFFER_SIZE 128 +#endif + // should produce compiler error if size is wrong typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1]; @@ -960,7 +976,7 @@ static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp); #ifndef STBI_NO_PNG static int stbi__png_test(stbi__context *s); -static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, unsigned int *palette_buffer, int palette_buffer_len, stbi__result_info *ri); #if 0 /* not used in SDL_image */ static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp); static int stbi__png_is16(stbi__context *s); @@ -975,7 +991,7 @@ static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp); #ifndef STBI_NO_TGA static int stbi__tga_test(stbi__context *s); -static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, unsigned int *palette_buffer, int palette_buffer_len, stbi__result_info *ri); static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp); #endif @@ -1193,7 +1209,7 @@ STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_fli : stbi__vertically_flip_on_load_global) #endif // STBI_THREAD_LOCAL -static void *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) +static void *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc, unsigned int *palette_buffer, int palette_buffer_len) { memset(ri, 0, sizeof(*ri)); // make sure it's initialized if we add new fields ri->bits_per_channel = 8; // default is 8 so most paths don't have to be changed @@ -1203,7 +1219,7 @@ static void *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int re // test the formats with a very explicit header first (at least a FOURCC // or distinctive magic number first) #ifndef STBI_NO_PNG - if (stbi__png_test(s)) return stbi__png_load(s,x,y,comp,req_comp, ri); + if (stbi__png_test(s)) return stbi__png_load(s,x,y,comp,req_comp, palette_buffer, palette_buffer_len, ri); #endif #ifndef STBI_NO_BMP if (stbi__bmp_test(s)) return stbi__bmp_load(s,x,y,comp,req_comp, ri); @@ -1240,7 +1256,7 @@ static void *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int re #ifndef STBI_NO_TGA // test tga last because it's a crappy test! if (stbi__tga_test(s)) - return stbi__tga_load(s,x,y,comp,req_comp, ri); + return stbi__tga_load(s,x,y,comp,req_comp, palette_buffer, palette_buffer_len, ri); #endif return stbi__errpuc("unknown image type", "Image not of any known type, or corrupt"); @@ -1318,10 +1334,42 @@ static void stbi__vertical_flip_slices(void *image, int w, int h, int z, int byt } #endif +static unsigned char *stbi__load_indexed(stbi__context *s, int *x, int *y, unsigned int *palette_buffer, int palette_buffer_len) +{ + if (!palette_buffer) + return NULL; + + stbi__result_info ri; + int comp; + void *result = stbi__load_main(s, x, y, &comp, 1, &ri, 8, palette_buffer, palette_buffer_len); + + if (result == NULL) + return NULL; + + if (comp != 1) { + stbi_image_free(result); + return NULL; + } + + if (ri.bits_per_channel != 8) { + stbi_image_free(result); + return NULL; + } + + // @TODO: move stbi__convert_format to here + + if (stbi__vertically_flip_on_load) { + int channels = 1; + stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi_uc)); + } + + return (unsigned char *) result; +} + static unsigned char *stbi__load_and_postprocess_8bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) { stbi__result_info ri; - void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 8); + void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 8, NULL, 0); if (result == NULL) return NULL; @@ -1348,7 +1396,7 @@ static unsigned char *stbi__load_and_postprocess_8bit(stbi__context *s, int *x, static stbi__uint16 *stbi__load_and_postprocess_16bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) { stbi__result_info ri; - void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 16); + void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 16, NULL, 0); if (result == NULL) return NULL; @@ -1505,6 +1553,22 @@ STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *u return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); } +#if 0 /* not used in SDL_image */ +STBIDEF stbi_uc *stbi_load_from_memory_with_palette(stbi_uc const *buffer, int len, int *x, int *y, unsigned int *palette_buffer, int palette_buffer_len) +{ + stbi__context s; + stbi__start_mem(&s, buffer, len); + return stbi__load_indexed(&s, x, y, palette_buffer, palette_buffer_len); +} +#endif + +STBIDEF stbi_uc *stbi_load_from_callbacks_with_palette(stbi_io_callbacks const *clbk, void *user, int *x, int *y, unsigned int *palette_buffer, int palette_buffer_len) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *)clbk, user); + return stbi__load_indexed(&s, x, y, palette_buffer, palette_buffer_len); +} + #ifndef STBI_NO_GIF STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp) { @@ -5180,15 +5244,25 @@ static void stbi__de_iphone(stbi__png *z) #define STBI__PNG_TYPE(a,b,c,d) (((unsigned) (a) << 24) + ((unsigned) (b) << 16) + ((unsigned) (c) << 8) + (unsigned) (d)) -static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) +static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp, unsigned int *palette_buffer, int palette_buffer_len) { - stbi_uc palette[1024], pal_img_n=0; + stbi_uc _palette[1024], pal_img_n=0; + stbi_uc *palette = _palette; stbi_uc has_trans=0, tc[3]={0}; stbi__uint16 tc16[3]; stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0; int first=1,k,interlace=0, color=0, is_iphone=0; stbi__context *s = z->s; + if (palette_buffer) { + if (palette_buffer_len < 256) + return stbi__err("palette buffer too small", "palette buffer len must be 256"); + else if (req_comp != 1) + return stbi__err("invalid req_comp", "req_comp must be 1 when loading paletted"); + else + palette = (stbi_uc *)(void *)palette_buffer; + } + z->expanded = NULL; z->idata = NULL; z->out = NULL; @@ -5329,8 +5403,9 @@ static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) s->img_n = pal_img_n; // record the actual colors we had s->img_out_n = pal_img_n; if (req_comp >= 3) s->img_out_n = req_comp; - if (!stbi__expand_png_palette(z, palette, pal_len, s->img_out_n)) - return 0; + if (!palette_buffer) + if (!stbi__expand_png_palette(z, palette, pal_len, s->img_out_n)) + return 0; } else if (has_trans) { // non-paletted image with tRNS -> source image has (constant) alpha ++s->img_n; @@ -5364,11 +5439,18 @@ static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) } } -static void *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp, stbi__result_info *ri) +static void *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp, unsigned int *palette_buffer, int palette_buffer_len, stbi__result_info *ri) { void *result=NULL; - if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); - if (stbi__parse_png_file(p, STBI__SCAN_load, req_comp)) { + if (palette_buffer && req_comp != 1) { + stbi__err("bad req_comp", "req_comp must be 1 if loading paletted image without expansion"); + return NULL; + } + if (req_comp < 0 || req_comp > 4) { + stbi__err("bad req_comp", "Internal error"); + return NULL; + } + if (stbi__parse_png_file(p, STBI__SCAN_load, req_comp, palette_buffer, palette_buffer_len)) { if (p->depth <= 8) ri->bits_per_channel = 8; else if (p->depth == 16) @@ -5378,7 +5460,9 @@ static void *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp, st result = p->out; p->out = NULL; if (req_comp && req_comp != p->s->img_out_n) { - if (ri->bits_per_channel == 8) + if (palette_buffer) + ; + else if (ri->bits_per_channel == 8) result = stbi__convert_format((unsigned char *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); else result = stbi__convert_format16((stbi__uint16 *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); @@ -5387,7 +5471,12 @@ static void *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp, st } *x = p->s->img_x; *y = p->s->img_y; - if (n) *n = p->s->img_n; + if (n) { + if (palette_buffer) + *n = 1; + else + *n = p->s->img_n; + } } STBI_FREE(p->out); p->out = NULL; STBI_FREE(p->expanded); p->expanded = NULL; @@ -5396,11 +5485,11 @@ static void *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp, st return result; } -static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, unsigned int *palette_buffer, int palette_buffer_len, stbi__result_info *ri) { stbi__png p; p.s = s; - return stbi__do_png(&p, x,y,comp,req_comp, ri); + return stbi__do_png(&p, x,y,comp,req_comp, palette_buffer, palette_buffer_len, ri); } static int stbi__png_test(stbi__context *s) @@ -5414,7 +5503,7 @@ static int stbi__png_test(stbi__context *s) #if 0 /* not used in SDL_image */ static int stbi__png_info_raw(stbi__png *p, int *x, int *y, int *comp) { - if (!stbi__parse_png_file(p, STBI__SCAN_header, 0)) { + if (!stbi__parse_png_file(p, STBI__SCAN_header, NULL, 0, NULL)) { stbi__rewind( p->s ); return 0; } @@ -5974,7 +6063,7 @@ static void stbi__tga_read_rgb16(stbi__context *s, stbi_uc* out) // so let's treat all 15 and 16bit TGAs as RGB with no alpha. } -static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, unsigned int *palette_buffer, int palette_buffer_len, stbi__result_info *ri) { // read in the TGA header stuff int tga_offset = stbi__get8(s); @@ -6054,10 +6143,18 @@ static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req // any data to skip? (offset usually = 0) stbi__skip(s, tga_palette_start ); // load the palette - tga_palette = (unsigned char*)stbi__malloc_mad2(tga_palette_len, tga_comp, 0); - if (!tga_palette) { - STBI_FREE(tga_data); - return stbi__errpuc("outofmem", "Out of memory"); + if (palette_buffer) { + if (palette_buffer_len < tga_palette_len * tga_comp) { + STBI_FREE(tga_data); + return stbi__errpuc("buffer too small", "Palette buffer too small"); + } + tga_palette = (unsigned char*)(void*)palette_buffer; + } else { + tga_palette = (unsigned char*)stbi__malloc_mad2(tga_palette_len, tga_comp, 0); + if (!tga_palette) { + STBI_FREE(tga_data); + return stbi__errpuc("outofmem", "Out of memory"); + } } if (tga_rgb16) { stbi_uc *pal_entry = tga_palette; @@ -6068,7 +6165,8 @@ static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req } } else if (!stbi__getn(s, tga_palette, tga_palette_len * tga_comp)) { STBI_FREE(tga_data); - STBI_FREE(tga_palette); + if (!palette_buffer) + STBI_FREE(tga_palette); return stbi__errpuc("bad palette", "Corrupt TGA"); } } @@ -6097,7 +6195,7 @@ static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req if ( read_next_pixel ) { // load however much data we did have - if ( tga_indexed ) + if ( tga_indexed && !palette_buffer ) { // read in index, then perform the lookup int pal_idx = (tga_bits_per_pixel == 8) ? stbi__get8(s) : stbi__get16le(s); @@ -6147,7 +6245,7 @@ static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req } } // clear my palette, if I had one - if ( tga_palette != NULL ) + if ( tga_palette != NULL && !palette_buffer ) { STBI_FREE( tga_palette ); }