// 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 "debug.h" #include #include #include #ifdef HAVE_LIBOPK #include struct OpkParams { std::string *sqfs_file, *icon_file; unsigned int offset; char *buf; }; static void __readFromOpk(png_structp png_ptr, png_bytep ptr, png_size_t length) { struct OpkParams *params = (struct OpkParams *) png_get_io_ptr(png_ptr); if (!params->buf) { params->buf = opk_extract_file(params->sqfs_file->c_str(), params->icon_file->c_str()); if (!params->buf) { png_error(png_ptr, "Unable to open OPK package\n"); return; } } memcpy(ptr, params->buf + params->offset, length); params->offset += length; } #endif SDL_Surface *loadPNG(const std::string &path) { // 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; #ifdef HAVE_LIBOPK struct OpkParams *params = NULL; std::string::size_type pos; #endif // 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; } #ifdef HAVE_LIBOPK pos = path.find('#'); if (pos != path.npos) { DEBUG("Registering specific callback for icon %s\n", path.c_str()); params = (struct OpkParams *) malloc(sizeof(*params)); params->sqfs_file = new std::string(path.substr(0, pos)); params->icon_file = new std::string(path.substr(pos + 1)); params->offset = 0; params->buf = NULL; png_set_read_fn(png, params, __readFromOpk); } else { #else if (1) { #endif /* HAVE_LIBOPK */ 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 { 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); #ifdef HAVE_LIBOPK if (params) { if (params->buf) free(params->buf); free(params); } #endif return surface; }