From 7aeb7a4f0a693a1e67209e8ec3439d71326529cb Mon Sep 17 00:00:00 2001 From: Maarten ter Huurne Date: Sun, 5 Jun 2011 05:58:47 +0200 Subject: [PATCH] PNG: Use libpng directly instead of via SDL_image. This eliminates the build-time dependency on SDL_image and the run-time dependencies on all libraries used by SDL_image except SDL and libpng. In addition we now let libpng convert to ARGB format while decoding the image, rather than letting SDL convert the surface afterwards. --- configure.in | 8 +-- src/imageio.cpp | 141 ++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 115 insertions(+), 34 deletions(-) diff --git a/configure.in b/configure.in index 9849628..83ea713 100644 --- a/configure.in +++ b/configure.in @@ -15,17 +15,13 @@ SDL_VERSION=1.2.8 AM_PATH_SDL($SDL_VERSION, :, AC_MSG_ERROR([*** SDL version $SDL_VERSION not found!])) -AC_ARG_WITH(sdl-image-prefix, - [ --with-sdl-image-prefix=DIR specify where SDL_image library is installed], - [SDL_IMAGE_PREFIX="$withval"]) - -AC_CHECK_LIB(SDL_image, IMG_LoadPNG_RW,,check_sdl_image="no") - AC_ARG_WITH(sdl-gfx-prefix, [ --with-sdl-gfx-prefix=DIR specify where SDL_gfx library is installed], [SDL_GFX_PREFIX="$withval"]) AC_CHECK_LIB(SDL_gfx, rotozoomSurfaceXY,,check_sdl_gfx="no") +# Check for libpng +AC_CHECK_LIB(png, png_read_image,,check_png="no") AC_OUTPUT(Makefile src/Makefile) diff --git a/src/imageio.cpp b/src/imageio.cpp index 177d073..988fc56 100644 --- a/src/imageio.cpp +++ b/src/imageio.cpp @@ -1,37 +1,122 @@ +// Written by Maarten ter Huurne in 2011. +// Based on the libpng example.c source, with some additional inspiration +// from SDL_image and openMSX. +// License: GPL version 2 or later. + + #include "imageio.h" -#include +#include "debug.h" + +#include +#include +#include + SDL_Surface *loadPNG(const std::string &path) { - // Load PNG file into an SDL surface. - SDL_Surface *surface = IMG_Load(path.c_str()); - if (!surface) { - return NULL; + // Declare these with function scope and initialize them to NULL, + // so we can use a single cleanup block at the end of the function. + SDL_Surface *surface = NULL; + FILE *fp = NULL; + png_structp png = NULL; + png_infop info = NULL; + + // Create and initialize the top-level libpng struct. + png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png) goto cleanup; + // Create and initialize the image information struct. + info = png_create_info_struct(png); + if (!info) goto cleanup; + // Setup error handling for errors detected by libpng. + if (setjmp(png_jmpbuf(png))) { + // Note: This gets executed when an error occurs. + if (surface) { + SDL_FreeSurface(surface); + surface = NULL; + } + goto cleanup; } - // Make sure we have a surface that can be blitted using alpha blending. - if (surface->format->BytesPerPixel == 4) { - SDL_SetAlpha(surface, SDL_SRCALPHA, 255); - return surface; + // Open input file. + fp = fopen(path.c_str(), "rb"); + if (!fp) goto cleanup; + // Set up the input control if you are using standard C streams. + png_init_io(png, fp); + + // The call to png_read_info() gives us all of the information from the + // PNG file before the first IDAT (image data chunk). + png_read_info(png, info); + png_uint_32 width, height; + int bitDepth, colorType; + png_get_IHDR( + png, info, &width, &height, &bitDepth, &colorType, NULL, NULL, NULL); + + // Select ARGB pixel format: + // (ARGB is the native pixel format for the JZ47xx frame buffer in 24bpp) + // - strip 16 bit/color files down to 8 bits/color + png_set_strip_16(png); + // - convert 1/2/4 bpp to 8 bpp + png_set_packing(png); + // - expand paletted images to RGB + // - expand grayscale images of less than 8-bit depth to 8-bit depth + // - expand tRNS chunks to alpha channels + png_set_expand(png); + // - convert grayscale to RGB + png_set_gray_to_rgb(png); + // - add alpha channel + png_set_add_alpha(png, 0xFF, PNG_FILLER_AFTER); + // - convert RGBA to ARGB + if (SDL_BYTEORDER == SDL_BIG_ENDIAN) { + png_set_swap_alpha(png); } else { - bool hasAlphaChannel = surface->format->Amask != 0; - SDL_PixelFormat format32; - memset(&format32, 0, sizeof(format32)); - format32.BitsPerPixel = 32; - format32.BytesPerPixel = 4; - format32.Rmask = - SDL_BYTEORDER == SDL_BIG_ENDIAN ? 0x00FF0000 : 0x000000FF; - format32.Gmask = 0x0000FF00; - format32.Bmask = - SDL_BYTEORDER == SDL_BIG_ENDIAN ? 0x000000FF : 0x00FF0000; - format32.Amask = hasAlphaChannel ? 0xFF000000 : 0; - format32.Rshift = SDL_BYTEORDER == SDL_BIG_ENDIAN ? 16 : 0; - format32.Gshift = 8; - format32.Bshift = SDL_BYTEORDER == SDL_BIG_ENDIAN ? 0 : 16; - format32.Ashift = hasAlphaChannel ? 24 : 0; - SDL_Surface *surface32 = SDL_ConvertSurface( - surface, &format32, hasAlphaChannel ? SDL_SRCALPHA : 0); - SDL_FreeSurface(surface); - return surface32; + png_set_bgr(png); // BGRA in memory becomes ARGB in register } + + // Update the image info to the post-conversion state. + png_read_update_info(png, info); + png_get_IHDR( + png, info, &width, &height, &bitDepth, &colorType, NULL, NULL, NULL); + assert(bitDepth == 8); + assert(colorType == PNG_COLOR_TYPE_RGB_ALPHA); + + // Refuse to load outrageously large images. + if (width > 65536) { + WARNING("Refusing to load image because it is too wide\n"); + goto cleanup; + } + if (height > 2048) { + WARNING("Refusing to load image because it is too high\n"); + goto cleanup; + } + + // Allocate ARGB surface to hold the image. + surface = SDL_CreateRGBSurface( + SDL_SWSURFACE | SDL_SRCALPHA, width, height, 32, + 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000 + ); + if (!surface) { + // Failed to create surface, probably out of memory. + goto cleanup; + } + + // Compute row pointers. + png_bytep rowPointers[height]; + for (png_uint_32 y = 0; y < height; y++) { + rowPointers[y] = + static_cast(surface->pixels) + y * surface->pitch; + } + + // Read the entire image in one go. + png_read_image(png, rowPointers); + + // Read rest of file, and get additional chunks in the info struct. + // Note: We got all we need, so skip this step. + //png_read_end(png, info); + +cleanup: + // Clean up. + png_destroy_read_struct(&png, &info, NULL); + if (fp) fclose(fp); + + return surface; }