mirror of
https://github.com/aseprite/aseprite.git
synced 2024-12-30 12:19:04 +00:00
407 lines
11 KiB
C
407 lines
11 KiB
C
/* loadpng, Allegro wrapper routines for libpng
|
|
* by Peter Wang (tjaden@users.sf.net).
|
|
*/
|
|
|
|
|
|
#include <png.h>
|
|
#include <zlib.h>
|
|
#include <allegro.h>
|
|
#include <allegro/internal/aintern.h>
|
|
#include "loadpng.h"
|
|
|
|
/* We need internals _color_load_depth and _fixup_loaded_bitmap. The
|
|
* first can be replaced by the new get_color_depth() function which
|
|
* is in Allegro 4.1 branch. But it's not worth it to break 4.0
|
|
* compatibility.
|
|
*/
|
|
|
|
|
|
|
|
double _png_screen_gamma = -1.0;
|
|
int _png_compression_level = Z_BEST_COMPRESSION;
|
|
|
|
|
|
|
|
/* get_gamma:
|
|
* Get screen gamma value one of three ways.
|
|
*/
|
|
static double get_gamma(void)
|
|
{
|
|
if (_png_screen_gamma == -1.0) {
|
|
/* Use the environment variable if available.
|
|
* 2.2 is a good guess for PC monitors.
|
|
* 1.1 is good for my laptop.
|
|
*/
|
|
AL_CONST char *gamma_str = getenv("SCREEN_GAMMA");
|
|
return (gamma_str) ? atof(gamma_str) : 2.2;
|
|
}
|
|
|
|
return _png_screen_gamma;
|
|
}
|
|
|
|
|
|
|
|
static void user_error_fn(png_structp png_ptr, png_const_charp message)
|
|
{
|
|
jmp_buf *jmpbuf = (jmp_buf *)png_get_error_ptr(png_ptr);
|
|
(void)message;
|
|
longjmp(*jmpbuf, 1);
|
|
}
|
|
|
|
|
|
|
|
/* read_data:
|
|
* Custom read function to use Allegro packfile routines,
|
|
* rather than C streams (so we can read from datafiles!)
|
|
*/
|
|
static void read_data(png_structp png_ptr, png_bytep data, png_uint_32 length)
|
|
{
|
|
PACKFILE *f = (PACKFILE *)png_get_io_ptr(png_ptr);
|
|
if ((png_uint_32)pack_fread(data, length, f) != length)
|
|
png_error(png_ptr, "read error (loadpng calling pack_fread)");
|
|
}
|
|
|
|
|
|
|
|
/* check_if_png:
|
|
* Check if input file is really PNG format.
|
|
*/
|
|
#define PNG_BYTES_TO_CHECK 4
|
|
|
|
static int check_if_png(PACKFILE *fp)
|
|
{
|
|
unsigned char buf[PNG_BYTES_TO_CHECK];
|
|
|
|
ASSERT(fp);
|
|
|
|
if (pack_fread(buf, PNG_BYTES_TO_CHECK, fp) != PNG_BYTES_TO_CHECK)
|
|
return 0;
|
|
|
|
return (png_sig_cmp(buf, (png_size_t)0, PNG_BYTES_TO_CHECK) == 0);
|
|
}
|
|
|
|
|
|
|
|
/* really_load_png:
|
|
* Worker routine, used by load_png and load_memory_png.
|
|
*/
|
|
static BITMAP *really_load_png(png_structp png_ptr, png_infop info_ptr, RGB *pal)
|
|
{
|
|
BITMAP *bmp;
|
|
PALETTE tmppal;
|
|
png_uint_32 width, height, rowbytes;
|
|
int bit_depth, color_type, interlace_type;
|
|
double image_gamma, screen_gamma;
|
|
int intent;
|
|
int bpp, dest_bpp;
|
|
int tRNS_to_alpha = FALSE;
|
|
int number_passes, pass;
|
|
|
|
ASSERT(png_ptr && info_ptr);
|
|
|
|
/* 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_ptr, info_ptr);
|
|
|
|
png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type,
|
|
&interlace_type, NULL, NULL);
|
|
|
|
/* Extract multiple pixels with bit depths of 1, 2, and 4 from a single
|
|
* byte into separate bytes (useful for paletted and grayscale images).
|
|
*/
|
|
png_set_packing(png_ptr);
|
|
|
|
/* Expand grayscale images to the full 8 bits from 1, 2, or 4 bits/pixel */
|
|
if ((color_type == PNG_COLOR_TYPE_GRAY) && (bit_depth < 8))
|
|
png_set_expand(png_ptr);
|
|
|
|
/* Adds a full alpha channel if there is transparency information
|
|
* in a tRNS chunk. */
|
|
if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
|
|
png_set_tRNS_to_alpha(png_ptr);
|
|
tRNS_to_alpha = TRUE;
|
|
}
|
|
|
|
/* Convert 16-bits per colour component to 8-bits per colour component. */
|
|
if (bit_depth == 16)
|
|
png_set_strip_16(png_ptr);
|
|
|
|
/* Convert grayscale to RGB triplets */
|
|
if ((color_type == PNG_COLOR_TYPE_GRAY) ||
|
|
(color_type == PNG_COLOR_TYPE_GRAY_ALPHA))
|
|
png_set_gray_to_rgb(png_ptr);
|
|
|
|
/* Optionally, tell libpng to handle the gamma correction for us. */
|
|
if (_png_screen_gamma != 0.0) {
|
|
screen_gamma = get_gamma();
|
|
|
|
if (png_get_sRGB(png_ptr, info_ptr, &intent))
|
|
png_set_gamma(png_ptr, screen_gamma, 0.45455);
|
|
else {
|
|
if (png_get_gAMA(png_ptr, info_ptr, &image_gamma))
|
|
png_set_gamma(png_ptr, screen_gamma, image_gamma);
|
|
else
|
|
png_set_gamma(png_ptr, screen_gamma, 0.45455);
|
|
}
|
|
}
|
|
|
|
/* Turn on interlace handling. */
|
|
number_passes = png_set_interlace_handling(png_ptr);
|
|
|
|
/* Call to gamma correct and add the background to the palette
|
|
* and update info structure.
|
|
*/
|
|
png_read_update_info(png_ptr, info_ptr);
|
|
|
|
/* Even if the user doesn't supply space for a palette, we want
|
|
* one for the load process.
|
|
*/
|
|
if (!pal)
|
|
pal = tmppal;
|
|
|
|
/* Palettes. */
|
|
if (color_type & PNG_COLOR_MASK_PALETTE) {
|
|
int num_palette, i;
|
|
png_colorp palette;
|
|
|
|
if (png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette)) {
|
|
/* We don't actually dither, we just copy the palette. */
|
|
for (i = 0; ((i < num_palette) && (i < 256)); i++) {
|
|
pal[i].r = palette[i].red >> 2; /* 256 -> 64 */
|
|
pal[i].g = palette[i].green >> 2;
|
|
pal[i].b = palette[i].blue >> 2;
|
|
}
|
|
|
|
for (; i < 256; i++)
|
|
pal[i].r = pal[i].g = pal[i].b = 0;
|
|
}
|
|
}
|
|
else {
|
|
generate_332_palette(pal);
|
|
}
|
|
|
|
rowbytes = png_get_rowbytes(png_ptr, info_ptr);
|
|
|
|
/* Allocate the memory to hold the image using the fields of info_ptr. */
|
|
bpp = rowbytes * 8 / width;
|
|
|
|
/* Allegro cannot handle less than 8 bpp. */
|
|
if (bpp < 8)
|
|
bpp = 8;
|
|
|
|
dest_bpp = _color_load_depth(bpp, (bpp == 32));
|
|
bmp = create_bitmap_ex(bpp, width, height);
|
|
|
|
/* Maybe flip RGB to BGR. */
|
|
if ((bpp == 24) || (bpp == 32)) {
|
|
int c = makecol_depth(bpp, 0, 0, 255);
|
|
unsigned char *pc = (unsigned char *)&c;
|
|
if (pc[0] == 255)
|
|
png_set_bgr(png_ptr);
|
|
#ifdef ALLEGRO_BIG_ENDIAN
|
|
png_set_swap_alpha(png_ptr);
|
|
#endif
|
|
}
|
|
|
|
/* Read the image, one line at a line (easier to debug!) */
|
|
for (pass = 0; pass < number_passes; pass++) {
|
|
png_uint_32 y;
|
|
for (y = 0; y < height; y++)
|
|
png_read_row(png_ptr, bmp->line[y], NULL);
|
|
}
|
|
|
|
/* Let Allegro convert the image into the desired colour depth. */
|
|
if (dest_bpp != bpp)
|
|
bmp = _fixup_loaded_bitmap(bmp, pal, dest_bpp);
|
|
|
|
/* Read rest of file, and get additional chunks in info_ptr. */
|
|
png_read_end(png_ptr, info_ptr);
|
|
|
|
return bmp;
|
|
}
|
|
|
|
|
|
|
|
/* load_png:
|
|
* Load a PNG file from disk, doing colour coversion if required.
|
|
*/
|
|
BITMAP *load_png(AL_CONST char *filename, RGB *pal)
|
|
{
|
|
PACKFILE *fp;
|
|
BITMAP *bmp;
|
|
|
|
ASSERT(filename);
|
|
|
|
fp = pack_fopen(filename, "r");
|
|
if (!fp)
|
|
return NULL;
|
|
|
|
bmp = load_png_pf(fp, pal);
|
|
|
|
pack_fclose(fp);
|
|
|
|
return bmp;
|
|
}
|
|
|
|
|
|
|
|
/* load_png_pf:
|
|
* Load a PNG file from disk, doing colour coversion if required.
|
|
*/
|
|
BITMAP *load_png_pf(PACKFILE *fp, RGB *pal)
|
|
{
|
|
jmp_buf jmpbuf;
|
|
BITMAP *bmp;
|
|
png_structp png_ptr;
|
|
png_infop info_ptr;
|
|
|
|
ASSERT(fp);
|
|
|
|
if (!check_if_png(fp)) {
|
|
return NULL;
|
|
}
|
|
|
|
/* Create and initialize the png_struct with the desired error handler
|
|
* functions. If you want to use the default stderr and longjump method,
|
|
* you can supply NULL for the last three parameters. We also supply the
|
|
* the compiler header file version, so that we know if the application
|
|
* was compiled with a compatible version of the library.
|
|
*/
|
|
png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
|
|
(void *)NULL, NULL, NULL);
|
|
if (!png_ptr) {
|
|
return NULL;
|
|
}
|
|
|
|
/* Allocate/initialize the memory for image information. */
|
|
info_ptr = png_create_info_struct(png_ptr);
|
|
if (!info_ptr) {
|
|
png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL);
|
|
return NULL;
|
|
}
|
|
|
|
/* Set error handling. */
|
|
if (setjmp(jmpbuf)) {
|
|
/* Free all of the memory associated with the png_ptr and info_ptr */
|
|
png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
|
|
/* If we get here, we had a problem reading the file */
|
|
return NULL;
|
|
}
|
|
png_set_error_fn(png_ptr, jmpbuf, user_error_fn, NULL);
|
|
|
|
/* Use Allegro packfile routines. */
|
|
png_set_read_fn(png_ptr, fp, (png_rw_ptr)read_data);
|
|
|
|
/* We have already read some of the signature. */
|
|
png_set_sig_bytes(png_ptr, PNG_BYTES_TO_CHECK);
|
|
|
|
/* Really load the image now. */
|
|
bmp = really_load_png(png_ptr, info_ptr, pal);
|
|
|
|
/* Clean up after the read, and free any memory allocated. */
|
|
png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
|
|
|
|
return bmp;
|
|
}
|
|
|
|
|
|
|
|
/* read_data_memory:
|
|
* Custom reader function to read a PNG file from a memory buffer.
|
|
*/
|
|
|
|
typedef struct {
|
|
AL_CONST unsigned char *buffer;
|
|
png_uint_32 bufsize;
|
|
png_uint_32 current_pos;
|
|
} MEMORY_READER_STATE;
|
|
|
|
static void read_data_memory(png_structp png_ptr, png_bytep data, png_uint_32 length)
|
|
{
|
|
MEMORY_READER_STATE *f = (MEMORY_READER_STATE *)png_get_io_ptr(png_ptr);
|
|
|
|
if (length > (f->bufsize - f->current_pos))
|
|
png_error(png_ptr, "read error in read_data_memory (loadpng)");
|
|
|
|
memcpy(data, f->buffer + f->current_pos, length);
|
|
f->current_pos += length;
|
|
}
|
|
|
|
|
|
|
|
/* check_if_png_memory:
|
|
* Check if input buffer is really PNG format.
|
|
*/
|
|
static int check_if_png_memory(AL_CONST void *buffer)
|
|
{
|
|
unsigned char *buf = (unsigned char *)buffer;
|
|
return (png_sig_cmp(buf, (png_size_t)0, PNG_BYTES_TO_CHECK) == 0);
|
|
}
|
|
|
|
|
|
|
|
/* load_memory_png:
|
|
* Load a PNG file from memory, doing colour coversion if required.
|
|
*/
|
|
BITMAP *load_memory_png(AL_CONST void *buffer, int bufsize, RGB *pal)
|
|
{
|
|
jmp_buf jmpbuf;
|
|
MEMORY_READER_STATE memory_reader_state;
|
|
BITMAP *bmp;
|
|
png_structp png_ptr;
|
|
png_infop info_ptr;
|
|
|
|
if (!buffer || (bufsize <= 0))
|
|
return NULL;
|
|
|
|
if (!check_if_png_memory(buffer))
|
|
return NULL;
|
|
|
|
/* Create and initialize the png_struct with the desired error handler
|
|
* functions. If you want to use the default stderr and longjump method,
|
|
* you can supply NULL for the last three parameters. We also supply the
|
|
* the compiler header file version, so that we know if the application
|
|
* was compiled with a compatible version of the library.
|
|
*/
|
|
png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
|
|
(void *)NULL, NULL, NULL);
|
|
if (!png_ptr)
|
|
return NULL;
|
|
|
|
/* Allocate/initialize the memory for image information. */
|
|
info_ptr = png_create_info_struct(png_ptr);
|
|
if (!info_ptr) {
|
|
png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL);
|
|
return NULL;
|
|
}
|
|
|
|
/* Set error handling. */
|
|
if (setjmp(jmpbuf)) {
|
|
/* Free all of the memory associated with the png_ptr and info_ptr */
|
|
png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
|
|
/* If we get here, we had a problem reading the file */
|
|
return NULL;
|
|
}
|
|
png_set_error_fn(png_ptr, jmpbuf, user_error_fn, NULL);
|
|
|
|
/* Set up the reader state. */
|
|
memory_reader_state.buffer = (unsigned char *)buffer;
|
|
memory_reader_state.bufsize = bufsize;
|
|
memory_reader_state.current_pos = PNG_BYTES_TO_CHECK;
|
|
|
|
/* Tell libpng to use our custom reader. */
|
|
png_set_read_fn(png_ptr, &memory_reader_state, (png_rw_ptr)read_data_memory);
|
|
|
|
/* We have already read some of the signature. */
|
|
png_set_sig_bytes(png_ptr, PNG_BYTES_TO_CHECK);
|
|
|
|
/* Really load the image now. */
|
|
bmp = really_load_png(png_ptr, info_ptr, pal);
|
|
|
|
/* Clean up after the read, and free any memory allocated. */
|
|
png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
|
|
|
|
return bmp;
|
|
}
|