314 lines
8 KiB
C
314 lines
8 KiB
C
/*
|
|
* Portable Network Graphics (PNG) image format
|
|
*/
|
|
|
|
#include "port_before.h"
|
|
|
|
#include <png.h>
|
|
|
|
#include "port_after.h"
|
|
|
|
#include "common.h"
|
|
#include "imagep.h"
|
|
#include "image_format.h"
|
|
#include "image_endian.h"
|
|
#include <assert.h>
|
|
|
|
#define PM_SCALE(a, b, c) (long)((a) * (c))/(b)
|
|
|
|
enum {
|
|
image_success = 0, image_need_data = 1, image_error = -1
|
|
};
|
|
|
|
typedef struct {
|
|
png_struct *state;
|
|
png_info *info;
|
|
Image *image;
|
|
Intensity cmap[3][256];
|
|
void (*lineProc)(void *, int, int);
|
|
void *closure;
|
|
int lenSoFar;
|
|
int done;
|
|
} pngState;
|
|
|
|
|
|
static Image *
|
|
pngGetImage(void *pointer)
|
|
{
|
|
return ((pngState *) pointer)->image;
|
|
}
|
|
|
|
|
|
static void lc_reverse_byte(byte *row, int n)
|
|
{
|
|
int i;
|
|
byte c;
|
|
|
|
for (i = 0; i < n; ++i) {
|
|
c = row[i];
|
|
c = ((c << 4) & 0xf0) | ((c >> 4) & 0x0f);
|
|
c = ((c << 2) & 0xcc) | ((c >> 2) & 0x33);
|
|
c = ((c << 1) & 0xaa) | ((c >> 1) & 0x55);
|
|
row[i] = c;
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
lf_info_callback(png_struct *state, png_info *info)
|
|
{
|
|
int orig_depth = 0;
|
|
pngState *png = (pngState *) png_get_progressive_ptr(state);
|
|
|
|
png_byte bit_depth = png_get_bit_depth(state, info),
|
|
color_type = png_get_color_type(state, info);
|
|
png_uint_32 width = png_get_image_width(state, info),
|
|
height = png_get_image_height(state, info),
|
|
valid_tRNS = png_get_valid(state, info, PNG_INFO_tRNS),
|
|
valid_gAMA = png_get_valid(state, info, PNG_INFO_gAMA);
|
|
png_colorp palette;
|
|
png_color_16p trans_values;
|
|
png_bytep trans;
|
|
int num_palette, num_trans;
|
|
double gamma;
|
|
|
|
png_get_PLTE(state, info, &palette, &num_palette);
|
|
png_get_tRNS(state, info, &trans, &num_trans, &trans_values);
|
|
png_get_gAMA(state, info, &gamma);
|
|
|
|
if (bit_depth < 8 && (PNG_COLOR_TYPE_RGB == color_type ||
|
|
PNG_COLOR_TYPE_RGB_ALPHA == color_type))
|
|
png_set_expand(state);
|
|
|
|
/* I wish the frame's background colour was available here */
|
|
if (color_type & PNG_COLOR_MASK_ALPHA) {
|
|
png_color_16 bg;
|
|
int gflag = PNG_BACKGROUND_GAMMA_SCREEN;
|
|
double gval = 1.0;
|
|
int expand = 0;
|
|
|
|
bg.red = bg.green = bg.blue = bg.gray = 0;
|
|
if (PNG_COLOR_TYPE_PALETTE == color_type)
|
|
png_set_expand(state);
|
|
|
|
png_set_background(state, &bg, gflag, expand, gval);
|
|
}
|
|
|
|
if (bit_depth < 8 && (bit_depth > 1 ||
|
|
PNG_COLOR_TYPE_GRAY != color_type)) {
|
|
if (PNG_COLOR_TYPE_GRAY == color_type)
|
|
orig_depth = bit_depth;
|
|
png_set_packing(state);
|
|
}
|
|
|
|
/* tell libpng to strip 16 bit depth files down to 8 bits */
|
|
if (bit_depth > 8)
|
|
png_set_strip_16(state);
|
|
|
|
png_set_interlace_handling(state);
|
|
|
|
/* update palette with transformations, update the info structure */
|
|
png_read_update_info(state, info);
|
|
|
|
/* allocate the memory to hold the image using the fields of png_info. */
|
|
if (PNG_COLOR_TYPE_GRAY == color_type && 1 == bit_depth) {
|
|
png->image = newBitImage(width, height);
|
|
if (!png->image) {
|
|
png->done = image_error;
|
|
return;
|
|
}
|
|
|
|
png_set_invert_mono(state);
|
|
} else if (PNG_COLOR_TYPE_PALETTE == color_type) {
|
|
int i;
|
|
|
|
png->image = newRGBImage(width, height, bit_depth);
|
|
if (!png->image) {
|
|
png->done = image_error;
|
|
return;
|
|
}
|
|
|
|
png->image->rgb.red = png->cmap[0];
|
|
png->image->rgb.green = png->cmap[1];
|
|
png->image->rgb.blue = png->cmap[2];
|
|
for (i = 0; i < num_palette; ++i) {
|
|
png->image->rgb.red[i] = palette[i].red << 8;
|
|
png->image->rgb.green[i] = palette[i].green << 8;
|
|
png->image->rgb.blue[i] = palette[i].blue << 8;
|
|
}
|
|
png->image->rgb.used = num_palette;
|
|
if (valid_tRNS) {
|
|
int val, i;
|
|
|
|
val = 0;
|
|
for (i = 0; i < num_trans; ++i) {
|
|
if (trans[i] < trans[val])
|
|
val = i;
|
|
}
|
|
png->image->transparent = val;
|
|
}
|
|
} else if (PNG_COLOR_TYPE_GRAY == color_type) {
|
|
int i;
|
|
int depth = orig_depth ? orig_depth : bit_depth;
|
|
int maxval = (1 << depth) - 1;
|
|
|
|
png->image = newRGBImage(width, height, depth);
|
|
if (!png->image) {
|
|
png->done = image_error;
|
|
return;
|
|
}
|
|
|
|
/* png->image->type = IGRAY; */
|
|
png->image->rgb.red = png->cmap[0];
|
|
png->image->rgb.green = png->cmap[1];
|
|
png->image->rgb.blue = png->cmap[2];
|
|
for (i = 0; i <= maxval; i++) {
|
|
png->image->rgb.red[i] = PM_SCALE(i, maxval, 0xffff);
|
|
png->image->rgb.green[i] = PM_SCALE(i, maxval, 0xffff);
|
|
png->image->rgb.blue[i] = PM_SCALE(i, maxval, 0xffff);
|
|
}
|
|
png->image->rgb.used = maxval + 1;
|
|
|
|
if (valid_tRNS)
|
|
png->image->transparent = trans_values->gray;
|
|
} else {
|
|
png->image = newTrueImage(width, height);
|
|
if (!png->image) {
|
|
png->done = image_error;
|
|
return;
|
|
}
|
|
|
|
}
|
|
|
|
if (valid_gAMA && png->image->type != IBITMAP)
|
|
png->image->gamma = 1.0 / gamma;
|
|
|
|
assert((png->image->width * png->image->pixlen + 7) / 8
|
|
== png_get_rowbytes(state, info));
|
|
}
|
|
|
|
|
|
static void
|
|
lf_row_callback(png_struct *state, png_byte *new_row, png_uint_32 row_num,
|
|
int pass)
|
|
{
|
|
pngState *png;
|
|
byte *old_row;
|
|
int rowbytes;
|
|
|
|
if (!new_row)
|
|
return;
|
|
|
|
png = (pngState *) png_get_progressive_ptr(state);
|
|
if (!png->image)
|
|
return;
|
|
|
|
rowbytes = png_get_rowbytes(png->state, png->info);
|
|
old_row = png->image->data + png->image->bytes_per_line * row_num;
|
|
|
|
png_progressive_combine_row(state, old_row, new_row);
|
|
if (png->lineProc) {
|
|
/* I can't say I'm too fond of this endian business. */
|
|
#ifdef CHIMERA_LITTLE_ENDIAN
|
|
if (IBITMAP == png->image->type)
|
|
lc_reverse_byte(old_row, rowbytes);
|
|
#endif
|
|
(png->lineProc)(png->closure, row_num, row_num);
|
|
#ifdef CHIMERA_LITTLE_ENDIAN
|
|
if (IBITMAP == png->image->type)
|
|
lc_reverse_byte(old_row, rowbytes);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
lf_end_callback(png_struct *state, png_info *info)
|
|
{
|
|
pngState *png= (pngState *) png_get_progressive_ptr(state);
|
|
png->done = image_success;
|
|
}
|
|
|
|
|
|
static void
|
|
pngDestroy(void *pointer)
|
|
{
|
|
pngState *png = (pngState *) pointer;
|
|
|
|
if (!png)
|
|
return;
|
|
|
|
if (setjmp(png_jmpbuf((png->state))))
|
|
return;
|
|
|
|
if (png->state) {
|
|
png_destroy_read_struct(&png->state, &png->info, NULL);
|
|
png->state = 0;
|
|
png->info = 0;
|
|
}
|
|
|
|
if (png->image) {
|
|
freeImage(png->image);
|
|
png->image = 0;
|
|
}
|
|
|
|
free_mem(png);
|
|
}
|
|
|
|
static int
|
|
pngAddData(void *pointer, byte *data, int len, bool data_ended)
|
|
{
|
|
pngState *png = (pngState *) pointer;
|
|
|
|
if (setjmp(png_jmpbuf((png->state))))
|
|
return image_error;
|
|
|
|
if (len > png->lenSoFar) {
|
|
png_process_data(png->state, png->info, data + png->lenSoFar,
|
|
len - png->lenSoFar);
|
|
png->lenSoFar = len;
|
|
}
|
|
|
|
if (image_need_data == png->done && data_ended)
|
|
return image_error;
|
|
|
|
return png->done;
|
|
}
|
|
|
|
|
|
void
|
|
pngInit(void (*lineProc)(void *, int, int), void *closure, struct ifs_vector *ifsv)
|
|
{
|
|
pngState *png;
|
|
|
|
ifsv->image_format_closure = 0;
|
|
png = (pngState *) alloc_mem(sizeof(pngState));
|
|
if (!png)
|
|
return;
|
|
|
|
memset(png, 0, sizeof(pngState));
|
|
png->lineProc = lineProc;
|
|
png->closure = closure;
|
|
png->state = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL,NULL,NULL);
|
|
png->info = png_create_info_struct(png->state);
|
|
assert(png->state && png->info);
|
|
|
|
if (setjmp(png_jmpbuf((png->state)))) {
|
|
png_destroy_read_struct(&png->state, &png->info, NULL);
|
|
png->state = 0;
|
|
png->info = 0;
|
|
return;
|
|
}
|
|
|
|
png_set_progressive_read_fn(png->state, (void *) png, lf_info_callback,
|
|
lf_row_callback, lf_end_callback);
|
|
png->done = image_need_data;
|
|
png->lenSoFar = 0;
|
|
|
|
ifsv->initProc = &pngInit;
|
|
ifsv->destroyProc = &pngDestroy;
|
|
ifsv->addDataProc = &pngAddData;
|
|
ifsv->getImageProc = &pngGetImage;
|
|
ifsv->image_format_closure = (void *) png;
|
|
}
|