1
1
s-lang/modules/png-module.c

799 строки
18 KiB
C

/* -*- mode: C; mode: fold; -*- */
/*
Copyright (C) 2005-2017,2018 John E. Davis
This file is part of the S-Lang Library.
The S-Lang Library is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
The S-Lang Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
USA.
*/
#include "config.h"
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <slang.h>
#include <png.h>
SLANG_MODULE(png);
static SLFUTURE_CONST char *Module_Version_String = "0.2.0";
#define MODULE_VERSION_NUMBER (0*10000 + 2*100 + 0)
/*{{{ Byte-swapping routines */
static int Is_Little_Endian;
static void byte_swap32 (unsigned char *p, unsigned char *t, SLuindex_Type n)
{
unsigned char *pmax;
/* t and p could point to the same buffer */
pmax = p + 4 * n;
while (p < pmax)
{
unsigned char ch = *p;
*t = *(p + 3);
*(t + 3) = ch;
ch = *(p + 1);
*(t + 1) = *(p + 2);
*(t + 2) = ch;
p += 4;
t += 4;
}
}
static void byte_swap16 (unsigned char *p, unsigned char *t, SLuindex_Type n)
{
unsigned char *pmax;
pmax = p + 2 * n;
while (p < pmax)
{
unsigned char ch = *p;
*t = *(p + 1);
*(t + 1) = ch;
p += 2;
t += 2;
}
}
/*}}}*/
/*{{{ Png_Type */
typedef struct
{
FILE *fp;
int mode; /* 'r' or 'w' */
png_struct *png;
png_info *info;
}
Png_Type;
static void free_png_type (Png_Type *p)
{
if (p == NULL)
return;
if (p->png != NULL)
{
if (p->mode == 'r')
{
if (p->info != NULL)
png_destroy_read_struct (&p->png, &p->info, NULL);
else
png_destroy_read_struct (&p->png, NULL, NULL);
}
else
{
if (p->info != NULL)
png_destroy_write_struct (&p->png, &p->info);
else
png_destroy_write_struct (&p->png, NULL);
}
}
if (p->fp != NULL)
fclose (p->fp);
SLfree ((char *) p);
}
static Png_Type *alloc_png_type (int mode)
{
Png_Type *p;
if (NULL != (p = (Png_Type *)SLmalloc (sizeof (Png_Type))))
{
memset ((char *) p, 0, sizeof (Png_Type));
p->mode = mode;
}
return p;
}
/*}}}*/
static png_byte **allocate_image_pointers (png_uint_32 height, png_byte *data, png_uint_32 rowbytes, int flip)
{
png_byte **image_pointers;
png_uint_32 i;
if (NULL == (image_pointers = (png_byte **) SLmalloc (height * sizeof (png_byte *))))
return NULL;
if (flip)
{
i = height;
while (i != 0)
{
i--;
image_pointers[i] = data;
data += rowbytes;
}
return image_pointers;
}
for (i = 0; i < height; i++)
{
image_pointers[i] = data;
data += rowbytes;
}
return image_pointers;
}
static void free_image_pointers (png_byte **image_pointers)
{
if (image_pointers == NULL)
return;
SLfree ((char *) image_pointers);
}
/*{{{ png read functions */
static Png_Type *open_png_file (char *file)
{
png_byte header[8];
Png_Type *p;
if (NULL == (p = alloc_png_type ('r')))
return NULL;
if ((NULL == (p->fp = fopen (file, "rb")))
|| (8 != fread (header, 1, 8, p->fp))
|| (0 != png_sig_cmp(header, 0, 8)))
{
SLang_verror (SL_Open_Error, "Unable to open %s as a png file", file);
free_png_type (p);
return NULL;
}
if (NULL == (p->png = png_create_read_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL)))
{
SLang_verror (SL_Open_Error, "Unable to read png structure from %s", file);
free_png_type (p);
return NULL;
}
if (NULL == (p->info = png_create_info_struct (p->png)))
{
SLang_verror (SL_Read_Error, "Unable to create info struct for %s", file);
free_png_type (p);
return NULL;
}
return p;
}
static void fixup_array_rgb (SLang_Array_Type *at)
{
SLindex_Type num_rows, num_cols, row;
unsigned char *data;
num_rows = at->dims[0];
num_cols = at->dims[1];
data = (unsigned char *) at->data;
/* Convert RGBRGBRGB.... to 0RGB0RGB0RGB ... */
for (row = 0; row < num_rows; row++)
{
unsigned char *p = data + 3*num_cols;
unsigned char *q = p + num_cols;
while (p != data)
{
*(--q) = *(--p);
*(--q) = *(--p);
*(--q) = *(--p);
*(--q) = 0; /* or 0xFF */
}
data += 4*num_cols;
}
if (Is_Little_Endian)
byte_swap32 ((unsigned char *)at->data, (unsigned char *)at->data, at->num_elements);
}
static void fixup_array_rgba (SLang_Array_Type *at)
{
unsigned char *data, *data_max;
data = (unsigned char *) at->data;
data_max = data + 4*at->num_elements;
/* RGBARGBA -> ARGBARGB */
while (data < data_max)
{
unsigned char a = data[3];
data[3] = data[2];
data[2] = data[1];
data[1] = data[0];
data[0] = a;
data += 4;
}
if (Is_Little_Endian)
byte_swap32 ((unsigned char *)at->data, (unsigned char *)at->data, at->num_elements);
}
static void fixup_array_ga (SLang_Array_Type *at)
{
if (Is_Little_Endian)
byte_swap16 ((unsigned char *)at->data, (unsigned char *) at->data, at->num_elements);
}
/* For little endian systems, ARGB is equivalent to the int32 BGRA.
* So, to read the image as RGB
*/
static SLang_Array_Type *read_image_internal (char *file, int flip, int *color_typep)
{
Png_Type *p;
png_uint_32 width, height, rowbytes;
png_struct *png;
png_info *info;
int bit_depth;
/* int interlace_type; */
int color_type;
unsigned int sizeof_type;
SLindex_Type dims[2];
SLtype data_type;
png_byte **image_pointers = NULL;
png_byte *data = NULL;
SLang_Array_Type *at;
void (*fixup_array_fun) (SLang_Array_Type *);
if (NULL == (p = open_png_file (file)))
return NULL;
png = p->png;
if (setjmp (png_jmpbuf (png)))
{
free_png_type (p);
if (data != NULL) SLfree ((char *) data);
free_image_pointers (image_pointers);
SLang_verror (SL_Read_Error, "Error encountered during I/O to %s", file);
return NULL;
}
png_init_io (png, p->fp);
png_set_sig_bytes (png, 8);
info = p->info;
png_read_info(png, info);
width = png_get_image_width (png, info);
height = png_get_image_height (png, info);
/* interlace_type = png_get_interlace_type (png, info); */
bit_depth = png_get_bit_depth (png, info);
if (bit_depth == 16)
png_set_strip_16 (png);
switch (png_get_color_type (png, info))
{
case PNG_COLOR_TYPE_GRAY:
#if defined(PNG_LIBPNG_VER) && (PNG_LIBPNG_VER >= 10209)
if (bit_depth < 8) png_set_expand_gray_1_2_4_to_8 (png);
#else /* deprecated */
if (bit_depth < 8) png_set_gray_1_2_4_to_8 (png);
#endif
break;
case PNG_COLOR_TYPE_GRAY_ALPHA:
/* png_set_gray_to_rgb (png); */
break;
case PNG_COLOR_TYPE_PALETTE:
png_set_palette_to_rgb (png);
break;
}
if (png_get_valid(png, info, PNG_INFO_tRNS))
png_set_tRNS_to_alpha(png);
png_read_update_info (png, info);
color_type = png_get_color_type (png, info);
switch (color_type)
{
case PNG_COLOR_TYPE_RGBA:
sizeof_type = 4;
fixup_array_fun = fixup_array_rgba;
data_type = SLang_get_int_type (32);
break;
case PNG_COLOR_TYPE_RGB:
sizeof_type = 4;
fixup_array_fun = fixup_array_rgb;
data_type = SLang_get_int_type (32);
break;
case PNG_COLOR_TYPE_GRAY_ALPHA:
sizeof_type = 2;
fixup_array_fun = fixup_array_ga;
data_type = SLang_get_int_type (16);
break;
case PNG_COLOR_TYPE_GRAY:
sizeof_type = 1;
fixup_array_fun = NULL;
data_type = SLANG_UCHAR_TYPE;
break;
default:
SLang_verror (SL_Read_Error, "Unsupported PNG color-type");
free_png_type (p);
return NULL;
}
*color_typep = color_type;
/* Use the high-level interface */
rowbytes = png_get_rowbytes (png, info);
if (rowbytes > width * sizeof_type)
{
SLang_verror (SL_INTERNAL_ERROR, "Unexpected value returned from png_get_rowbytes");
free_png_type (p);
return NULL;
}
if (NULL == (data = (png_byte *) SLmalloc (height * width * sizeof_type)))
{
free_png_type (p);
return NULL;
}
if (NULL == (image_pointers = allocate_image_pointers (height, data, width * sizeof_type, flip)))
{
SLfree ((char *) data);
free_png_type (p);
return NULL;
}
png_read_image(png, image_pointers);
dims[0] = height;
dims[1] = width;
if (NULL == (at = SLang_create_array (data_type, 0, (VOID_STAR) data, dims, 2)))
{
SLfree ((char *) data);
free_image_pointers (image_pointers);
free_png_type (p);
return NULL;
}
free_png_type (p);
free_image_pointers (image_pointers);
if (fixup_array_fun != NULL)
(*fixup_array_fun) (at);
return at;
}
static void read_image (int flipped)
{
int color_type;
char *file;
SLang_Ref_Type *ref = NULL;
SLang_Array_Type *at;
if ((SLang_Num_Function_Args == 2)
&& (-1 == SLang_pop_ref (&ref)))
return;
if (-1 == SLang_pop_slstring (&file))
{
file = NULL;
goto free_return;
}
if (NULL == (at = read_image_internal (file, flipped, &color_type)))
goto free_return;
if ((ref != NULL)
&& (-1 == SLang_assign_to_ref (ref, SLANG_INT_TYPE, &color_type)))
{
SLang_free_array (at);
goto free_return;
}
(void) SLang_push_array (at, 1);
free_return:
SLang_free_slstring (file);
if (ref != NULL)
SLang_free_ref (ref);
}
/*}}}*/
static void write_gray_to_gray (png_struct *png, png_byte *data, SLindex_Type num_cols, png_byte *tmpbuf)
{
(void) num_cols;
(void) tmpbuf;
png_write_row (png, data);
}
static void write_gray_to_gray_alpha (png_struct *png, png_byte *data, SLindex_Type num_cols, png_byte *tmpbuf)
{
SLindex_Type i, j;
j = 0;
for (i = 0; i < num_cols; i++)
{
tmpbuf[j] = data[i];
tmpbuf[j+1] = 0xFF;
j += 2;
}
png_write_row (png, tmpbuf);
}
static void write_gray_alpha_to_gray (png_struct *png, png_byte *data, SLindex_Type num_cols, png_byte *tmpbuf)
{
SLindex_Type i;
if (Is_Little_Endian == 0)
data++; /* AGAGAG... -> GAGAGA... */
for (i = 0; i < num_cols; i++)
{
tmpbuf[i] = *data;
data += 2;
}
png_write_row (png, tmpbuf);
}
static void write_gray_alpha_to_gray_alpha (png_struct *png, png_byte *data, SLindex_Type num_cols, png_byte *tmpbuf)
{
if (Is_Little_Endian == 0)
{
png_write_row (png, data);
return;
}
byte_swap16 ((unsigned char *) data, (unsigned char *) tmpbuf, num_cols);
png_write_row (png, tmpbuf);
}
static void write_rgb_alpha_to_rgb_alpha (png_struct *png, png_byte *data, SLindex_Type num_cols, png_byte *tmpbuf)
{
unsigned char *data_max;
unsigned char *p;
if (Is_Little_Endian)
{
byte_swap32 ((unsigned char *) data, (unsigned char *) tmpbuf, num_cols);
data = tmpbuf;
}
data_max = data + 4 * num_cols;
p = tmpbuf;
/* Change ARGBARGB... to RGBARGBA... */
while (data < data_max)
{
unsigned char a = data[0];
p[0] = data[1];
p[1] = data[2];
p[2] = data[3];
p[3] = a;
data += 4;
p += 4;
}
png_write_row (png, tmpbuf);
}
static void write_rgb_to_rgb (png_struct *png, png_byte *data, SLindex_Type num_cols, png_byte *tmpbuf)
{
SLindex_Type i;
png_byte *p, *q;
if (Is_Little_Endian)
{
byte_swap32 ((unsigned char *) data, (unsigned char *) tmpbuf, num_cols);
p = tmpbuf;
}
else p = data;
/* ARGBARGB... -> RGBRGBRGB */
q = tmpbuf;
for (i = 0; i < num_cols; i++)
{
p++;
*q++ = *p++;
*q++ = *p++;
*q++ = *p++;
}
png_write_row (png, tmpbuf);
}
static int write_array (png_struct *png, png_byte **image_pointers, SLindex_Type num_rows, SLindex_Type num_cols,
void (*write_row_func) (png_struct *, png_byte *, SLindex_Type, png_byte *),
png_byte *tmpbuf)
{
int num_pass;
SLindex_Type i;
num_pass = png_set_interlace_handling(png);
while (num_pass > 0)
{
num_pass--;
for (i = 0; i < num_rows; i++)
(*write_row_func) (png, image_pointers[i], num_cols, tmpbuf);
}
return 0;
}
static int write_image_internal (char *file, SLang_Array_Type *at,
int color_type,
void (*write_fun)(png_struct *, png_byte *p, SLindex_Type, png_byte *),
int flip, int compress_level)
{
FILE *fp;
Png_Type *p = NULL;
png_struct *png;
png_info *info;
SLindex_Type width, height;
png_byte **image_pointers;
int bit_depth;
int status = -1;
png_byte *tmpbuf;
bit_depth = 8;
height = at->dims[0];
width = at->dims[1];
if (NULL == (image_pointers = allocate_image_pointers (height, (png_byte *)at->data, width * at->sizeof_type, flip)))
return -1;
if (NULL == (tmpbuf = (png_byte *)SLmalloc (4*width)))
{
free_image_pointers (image_pointers);
return -1;
}
if (NULL == (fp = fopen (file, "wb")))
{
(void)SLerrno_set_errno (errno);
SLang_verror (SL_Open_Error, "Unable to open %s", file);
goto return_error;
}
if (NULL == (p = alloc_png_type ('w')))
goto return_error;
p->fp = fp;
if (NULL == (p->png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL)))
{
SLang_verror (SL_Open_Error, "png_create_write_struct failed");
goto return_error;
}
png = p->png;
if (NULL == (p->info = png_create_info_struct (png)))
{
SLang_verror (SL_Open_Error, "png_create_info_struct failed");
goto return_error;
}
info = p->info;
if (setjmp(png_jmpbuf(png)))
{
SLang_verror (SL_Write_Error, "PNG I/O error");
goto return_error;
}
png_init_io(png, fp);
if ((compress_level >= 0) && (compress_level <= 9))
png_set_compression_level (png, compress_level);
png_set_IHDR (png, info, width, height,
bit_depth, color_type, PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
png_write_info(png, info);
if (-1 == write_array (png, image_pointers, height, width, write_fun, tmpbuf))
goto return_error;
png_write_end(png, NULL);
if (EOF == fclose (p->fp))
{
SLang_verror (SL_Write_Error, "Error closing %s", file);
SLerrno_set_errno (errno);
}
else status = 0;
p->fp = NULL;
/* drop */
return_error:
if (tmpbuf != NULL)
SLfree ((char *) tmpbuf);
free_image_pointers (image_pointers);
if (p != NULL)
free_png_type (p);
return status;
}
static void write_image (int flip)
{
char *file;
SLang_Array_Type *at;
int with_alpha = 0;
int has_with_alpha = 0;
int compress_level;
int color_type;
void (*write_fun) (png_struct *, png_byte *, SLindex_Type, png_byte *);
if (SLang_Num_Function_Args == 3)
{
if (-1 == SLang_pop_int (&with_alpha))
return;
has_with_alpha = 1;
}
if (-1 == SLang_get_int_qualifier ("compress", &compress_level, -1))
return;
if (-1 == SLang_pop_array (&at, 0))
return;
if (at->num_dims != 2)
{
SLang_verror (SL_InvalidParm_Error, "Expecting a 2-d array");
SLang_free_array (at);
return;
}
switch (SLang_get_int_size (at->data_type))
{
case -8:
case 8:
if (with_alpha)
{
write_fun = write_gray_to_gray_alpha;
color_type = PNG_COLOR_TYPE_GRAY_ALPHA;
}
else
{
write_fun = write_gray_to_gray;
color_type = PNG_COLOR_TYPE_GRAY;
}
break;
case -16:
case 16:
if (has_with_alpha && (with_alpha == 0))
{
write_fun = write_gray_alpha_to_gray;
color_type = PNG_COLOR_TYPE_GRAY;
}
else
{
write_fun = write_gray_alpha_to_gray_alpha;
color_type = PNG_COLOR_TYPE_GRAY_ALPHA;
}
break;
case -32:
case 32:
if (with_alpha)
{
write_fun = write_rgb_alpha_to_rgb_alpha;
color_type = PNG_COLOR_TYPE_RGBA;
}
else
{
write_fun = write_rgb_to_rgb;
color_type = PNG_COLOR_TYPE_RGB;
}
break;
default:
SLang_verror (SL_InvalidParm_Error, "Expecting an 8, 16, or 32 bit integer array");
SLang_free_array (at);
return;
}
if (-1 == SLang_pop_slstring (&file))
{
SLang_free_array (at);
return;
}
(void) write_image_internal (file, at, color_type, write_fun, flip, compress_level);
SLang_free_slstring (file);
SLang_free_array (at);
}
static void read_intrin (void)
{
read_image (0);
}
static void read_flipped_intrin (void)
{
read_image (1);
}
static void write_intrin (void)
{
write_image (0);
}
static void write_flipped_intrin (void)
{
write_image (1);
}
static SLang_Intrin_Fun_Type Module_Intrinsics [] =
{
MAKE_INTRINSIC_0("png_read", read_intrin, SLANG_VOID_TYPE),
MAKE_INTRINSIC_0("png_read_flipped", read_flipped_intrin, SLANG_VOID_TYPE),
MAKE_INTRINSIC_0("png_write", write_intrin, SLANG_VOID_TYPE),
MAKE_INTRINSIC_0("png_write_flipped", write_flipped_intrin, SLANG_VOID_TYPE),
SLANG_END_INTRIN_FUN_TABLE
};
static SLang_Intrin_Var_Type Module_Variables [] =
{
MAKE_VARIABLE("_png_module_version_string", &Module_Version_String, SLANG_STRING_TYPE, 1),
SLANG_END_INTRIN_VAR_TABLE
};
static SLang_IConstant_Type Module_IConstants [] =
{
MAKE_ICONSTANT("PNG_COLOR_TYPE_GRAY", PNG_COLOR_TYPE_GRAY),
MAKE_ICONSTANT("PNG_COLOR_TYPE_GRAY_ALPHA", PNG_COLOR_TYPE_GRAY_ALPHA),
MAKE_ICONSTANT("PNG_COLOR_TYPE_RGB", PNG_COLOR_TYPE_RGB),
MAKE_ICONSTANT("PNG_COLOR_TYPE_RGBA", PNG_COLOR_TYPE_RGBA),
MAKE_ICONSTANT("_png_module_version", MODULE_VERSION_NUMBER),
SLANG_END_ICONST_TABLE
};
int init_png_module_ns (char *ns_name)
{
unsigned short x;
SLang_NameSpace_Type *ns = SLns_create_namespace (ns_name);
if (ns == NULL)
return -1;
x = 0xFF;
Is_Little_Endian = (*(unsigned char *)&x == 0xFF);
if (
(-1 == SLns_add_intrin_var_table (ns, Module_Variables, NULL))
|| (-1 == SLns_add_intrin_fun_table (ns, Module_Intrinsics, NULL))
|| (-1 == SLns_add_iconstant_table (ns, Module_IConstants, NULL))
)
return -1;
return 0;
}
/* This function is optional */
void deinit_png_module (void)
{
}