chimera2/image/png.c

315 lines
8.1 KiB
C

/*
* Portable Network Graphics (PNG) image format
*/
#include "port_before.h"
#include <png.h>
#include <string.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;
}