diff options
Diffstat (limited to 'src/libs/graphics/sdl/png2sdl.c')
-rw-r--r-- | src/libs/graphics/sdl/png2sdl.c | 300 |
1 files changed, 300 insertions, 0 deletions
diff --git a/src/libs/graphics/sdl/png2sdl.c b/src/libs/graphics/sdl/png2sdl.c new file mode 100644 index 0000000..1717886 --- /dev/null +++ b/src/libs/graphics/sdl/png2sdl.c @@ -0,0 +1,300 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * This code is adapted from IMG_png.c in the SDL2_image library, which is + * available under the zlib license and is (c) 1997-2019 Sam Lantinga. The + * code itself is credited to Philippe Lavoie as the original author. It + * also shares some heritage with libpng's own example.c file, and + * ultimately inherits the GPL2+ license from the rest of UQM. + * + * Differences from SDL2_image are: + * - It is PNG-only + * - It directly links the relevant version of libpng at compile time + * - It always uses libpng and will never forward to alternative + * libraries such as ImageIO.framework. This means that palette + * information will always be preserved, as UQM requires. + * - It locks the surface as the API demands rather than using + * volatility markers + * - Palette assignment is done through the API rather than by + * directly editing the format contents + */ + +#include "png2sdl.h" +#include <png.h> + +/* Link function between SDL_RWops and PNG's data source */ +static void +png_read_data(png_structp ctx, png_bytep area, png_size_t size) +{ + SDL_RWops *src = (SDL_RWops *)png_get_io_ptr (ctx); + SDL_RWread (src, area, size, 1); +} + +SDL_Surface * +TFB_png_to_sdl (SDL_RWops *src) +{ + Sint64 start; + const char *error; + SDL_Surface *surface; + png_structp png_ptr; + png_infop info_ptr; + png_uint_32 width, height; + int bit_depth, color_type, interlace_type, num_channels; + Uint32 Rmask, Gmask, Bmask, Amask; + png_bytep *row_pointers; + int row, i; + int ckey = -1; + png_color_16 *transv; + + if (!src) + { + /* The error message has been set in SDL_RWFromFile */ + return NULL; + } + start = SDL_RWtell (src); + + /* Initialize the data we will clean up when we're done */ + error = NULL; + png_ptr = NULL; + info_ptr = NULL; + row_pointers = NULL; + surface = NULL; + + /* Create the PNG loading context structure */ + png_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING, + NULL, NULL, NULL); + if (png_ptr == NULL) + { + error = "Couldn't allocate memory for PNG file"; + goto done; + } + + /* Allocate/initialize the memory for image information */ + info_ptr = png_create_info_struct (png_ptr); + if (info_ptr == NULL) + { + error = "Couldn't create image information for PNG file"; + goto done; + } + + /* Set error handling */ + if (setjmp (png_jmpbuf (png_ptr))) + { + error = "Error reading the PNG file."; + goto done; + } + + /* Set up the input control */ + png_set_read_fn (png_ptr, src, png_read_data); + + /* Read PNG header info */ + png_read_info (png_ptr, info_ptr); + png_get_IHDR (png_ptr, info_ptr, &width, &height, &bit_depth, + &color_type, &interlace_type, NULL, NULL); + + + /* Configure the decode based on what we know of the image + * already: strip 16 bit color down to 8 bit, automatically + * deinterlace, expand grayscale images or those with more + * than one transparent color or any translucent colors into + * full RGB or RGBA, and expand 1, 2, or 4-bpp paletted + * images to 8bpp. */ + png_set_strip_16 (png_ptr); + png_set_interlace_handling (png_ptr); + png_set_packing (png_ptr); + if (color_type == PNG_COLOR_TYPE_GRAY) + { + png_set_expand (png_ptr); + } + if (png_get_valid (png_ptr, info_ptr, PNG_INFO_tRNS)) + { + int num_trans; + Uint8 *trans; + png_get_tRNS (png_ptr, info_ptr, &trans, &num_trans, &transv); + if (color_type == PNG_COLOR_TYPE_PALETTE) + { + /* Check if all tRNS entries are opaque except one */ + int j, t = -1; + for (j = 0; j < num_trans; j++) + { + if (trans[j] == 0) + { + if (t >= 0) + { + break; + } + t = j; + } + else if (trans[j] != 255) + { + break; + } + } + if (j == num_trans) + { + /* exactly one transparent index */ + ckey = t; + } + else + { + /* more than one transparent index, or translucency */ + png_set_expand (png_ptr); + } + } + else + { + ckey = 0; /* actual value will be set later */ + } + } + + if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + { + png_set_gray_to_rgb (png_ptr); + } + + /* Register our changes with the reading machinery and refresh + * our ancillary data about the image */ + png_read_update_info (png_ptr, info_ptr); + png_get_IHDR (png_ptr, info_ptr, &width, &height, &bit_depth, + &color_type, &interlace_type, NULL, NULL); + + /* Allocate the SDL surface to hold the image */ + Rmask = Gmask = Bmask = Amask = 0; + num_channels = png_get_channels (png_ptr, info_ptr); + if (num_channels >= 3) + { +#if SDL_BYTEORDER == SDL_LIL_ENDIAN + Rmask = 0x000000FF; + Gmask = 0x0000FF00; + Bmask = 0x00FF0000; + Amask = (num_channels == 4) ? 0xFF000000 : 0; +#else + int s = (num_channels == 4) ? 0 : 8; + Rmask = 0xFF000000 >> s; + Gmask = 0x00FF0000 >> s; + Bmask = 0x0000FF00 >> s; + Amask = 0x000000FF >> s; +#endif + } + surface = SDL_CreateRGBSurface (SDL_SWSURFACE, width, height, + bit_depth * num_channels, + Rmask, Gmask, Bmask, Amask); + if (surface == NULL) + { + error = SDL_GetError (); + goto done; + } + + if (ckey != -1) + { + if (color_type != PNG_COLOR_TYPE_PALETTE) + { + /* FIXME: Should these be truncated or shifted down? */ + ckey = SDL_MapRGB(surface->format, + (Uint8)transv->red, + (Uint8)transv->green, + (Uint8)transv->blue); + } +#if SDL_MAJOR_VERSION >= 2 + SDL_SetColorKey (surface, SDL_TRUE, ckey); +#else + SDL_SetColorKey (surface, SDL_SRCCOLORKEY, ckey); +#endif + } + + SDL_LockSurface (surface); + /* Create the array of pointers to image data */ + row_pointers = (png_bytep *)SDL_malloc (sizeof (png_bytep) * height); + if (!row_pointers) + { + error = "Out of memory"; + goto done; + } + for (row = 0; row < (int)height; row++) + { + row_pointers[row] = (png_bytep) + (Uint8 *)surface->pixels + row*surface->pitch; + } + + /* Read the entire image in one go */ + png_read_image (png_ptr, row_pointers); + SDL_UnlockSurface (surface); + + /* and we're done! (png_read_end() can be omitted if no + * processing of post-IDAT text/time/etc. is desired) + * In some cases it can't read PNGs created by some popular + * programs (ACDSEE), we do not want to process comments, so + * we omit png_read_end */ + + /* Load the palette, if any */ + if (surface->format->palette) + { + SDL_Color palette[256]; + int png_num_palette; + png_colorp png_palette; + png_get_PLTE (png_ptr, info_ptr, &png_palette, &png_num_palette); + if (color_type == PNG_COLOR_TYPE_GRAY) + { + png_num_palette = 256; + for (i = 0; i < 256; i++) + { + palette[i].r = (Uint8)i; + palette[i].g = (Uint8)i; + palette[i].b = (Uint8)i; + } + } + else if (png_num_palette > 0) + { + for (i = 0; i < png_num_palette; ++i) + { + palette[i].b = png_palette[i].blue; + palette[i].g = png_palette[i].green; + palette[i].r = png_palette[i].red; + } + } +#if SDL_MAJOR_VERSION >= 2 + SDL_SetPaletteColors (surface->format->palette, palette, + 0, png_num_palette); +#else + SDL_SetPalette (surface, SDL_LOGPAL, palette, + 0, png_num_palette); +#endif + } + +done: /* Clean up and return */ + if (png_ptr) + { + png_destroy_read_struct (&png_ptr, + info_ptr ? &info_ptr : (png_infopp)0, + (png_infopp)0); + } + if (row_pointers) + { + SDL_free (row_pointers); + } + if (error) + { + SDL_RWseek(src, start, RW_SEEK_SET); + if (surface) + { + SDL_FreeSurface (surface); + surface = NULL; + } + fprintf (stderr, "%s", error); + } + return surface; +} |