Files
2020-07-25 01:50:14 +03:00

265 lines
6.7 KiB
C

/* AirScan (a.k.a. eSCL) backend for SANE
*
* Copyright (C) 2019 and up by Alexander Pevzner (pzz@apevzner.com)
* See LICENSE for license terms and conditions
*
* PNG image decoder
*/
#include "airscan.h"
#include <png.h>
#include <setjmp.h>
#include <string.h>
/* PNG image decoder
*/
typedef struct {
image_decoder decoder; /* Base class */
png_struct *png_ptr; /* Underlying libpng decoder */
png_info *info_ptr; /* libpng info struct */
const uint8_t *image_data; /* Remaining image data */
size_t image_size; /* Remaining image data */
char error[1024]; /* Error message buffer */
png_uint_32 width, height; /* Image size in pixels */
int bit_depth; /* 1/2/4/8/16 */
int color_type; /* PNG_COLOR_TYPE_XXX */
int interlace_type; /* PNG_INTERLACE_XXX */
unsigned int num_lines; /* Num of lines left to read */
} image_decoder_png;
/* Free PNG decoder
*/
static void
image_decoder_png_free (image_decoder *decoder)
{
image_decoder_png *png = (image_decoder_png*) decoder;
image_decoder_reset(decoder);
mem_free(png);
}
/* libpng error callback
*/
static void
image_decoder_png_error_fn (png_struct *png_ptr, const char *message)
{
image_decoder_png *png = png_get_error_ptr(png_ptr);
snprintf(png->error, sizeof(png->error), "PNG: %s", message);
}
/* libpng warning callback
*/
static void
image_decoder_png_warning_fn (png_struct *png_ptr, const char *message)
{
(void) png_ptr;
(void) message;
}
/* libpng malloc callback
*/
static void*
image_decoder_png_malloc_fn (png_struct *png_ptr, size_t size)
{
(void) png_ptr;
return mem_new(char, size);
}
/* libpng free callback
*/
static void
image_decoder_png_free_fn (png_struct *png_ptr, void *p)
{
(void) png_ptr;
mem_free(p);
}
/* libpng read callback
*/
static void
image_decoder_png_read_fn (png_struct *png_ptr, png_bytep data, size_t size)
{
image_decoder_png *png = png_get_io_ptr(png_ptr);
if (size > png->image_size) {
png_error(png_ptr, "unexpected EOF");
}
memcpy(data, png->image_data, size);
png->image_data += size;
png->image_size -= size;
}
/* Begin PNG decoding
*/
static error
image_decoder_png_begin (image_decoder *decoder, const void *data,
size_t size)
{
image_decoder_png *png = (image_decoder_png*) decoder;
/* Create libpng structures */
png->png_ptr = png_create_read_struct_2(PNG_LIBPNG_VER_STRING,
png, image_decoder_png_error_fn, image_decoder_png_warning_fn,
png, image_decoder_png_malloc_fn, image_decoder_png_free_fn);
if (png->png_ptr == NULL) {
return ERROR("PNG: png_create_read_struct_2() failed");
}
png->info_ptr = png_create_info_struct(png->png_ptr);
if (png->info_ptr == NULL) {
image_decoder_reset(decoder);
return ERROR("PNG: png_create_info_struct() failed");
}
/* Setup read function */
png_set_read_fn(png->png_ptr, png, image_decoder_png_read_fn);
png->image_data = data;
png->image_size = size;
/* Read image info */
if (setjmp(png_jmpbuf(png->png_ptr))) {
image_decoder_reset(decoder);
return ERROR(png->error);
}
png_read_info(png->png_ptr, png->info_ptr);
png_get_IHDR(png->png_ptr, png->info_ptr, &png->width, &png->height,
&png->bit_depth, &png->color_type, &png->interlace_type, NULL, NULL);
png->num_lines = png->height;
/* Reject interlaced images */
if (png->interlace_type != PNG_INTERLACE_NONE) {
image_decoder_reset(decoder);
return ERROR("PNG: interlaced images not supported");
}
/* Setup input transformations */
if (png->color_type == PNG_COLOR_TYPE_PALETTE) {
png_set_palette_to_rgb(png->png_ptr);
}
if (png->color_type == PNG_COLOR_TYPE_GRAY && png->bit_depth < 8) {
png_set_expand_gray_1_2_4_to_8(png->png_ptr);
png->bit_depth = 8;
}
if ((png->color_type & PNG_COLOR_MASK_ALPHA) != 0) {
png_set_strip_alpha(png->png_ptr);
}
return NULL;
}
/* Reset PNG decoder
*/
static void
image_decoder_png_reset (image_decoder *decoder)
{
image_decoder_png *png = (image_decoder_png*) decoder;
if (png->png_ptr != NULL) {
png_destroy_read_struct(&png->png_ptr, &png->info_ptr, NULL);
png->png_ptr = NULL;
png->info_ptr = NULL;
}
}
/* Get bytes count per pixel
*/
static int
image_decoder_png_get_bytes_per_pixel (image_decoder *decoder)
{
image_decoder_png *png = (image_decoder_png*) decoder;
int bit_depth = png->bit_depth;
if ((png->color_type & PNG_COLOR_MASK_COLOR) != 0) {
bit_depth *= 3;
}
return bit_depth / 3;
}
/* Get image parameters
*/
static void
image_decoder_png_get_params (image_decoder *decoder, SANE_Parameters *params)
{
image_decoder_png *png = (image_decoder_png*) decoder;
params->last_frame = SANE_TRUE;
params->pixels_per_line = png->width;
params->lines = png->height;
params->depth = png->bit_depth;
if ((png->color_type & PNG_COLOR_MASK_COLOR) != 0) {
params->format = SANE_FRAME_RGB;
params->bytes_per_line = params->pixels_per_line * 3;
} else {
params->format = SANE_FRAME_GRAY;
params->bytes_per_line = params->pixels_per_line;
}
}
/* Set clipping window
*/
static error
image_decoder_png_set_window (image_decoder *decoder, image_window *win)
{
image_decoder_png *png = (image_decoder_png*) decoder;
win->x_off = win->y_off = 0;
win->wid = png->width;
win->hei = png->height;
return NULL;
}
/* Read next line of image
*/
static error
image_decoder_png_read_line (image_decoder *decoder, void *buffer)
{
image_decoder_png *png = (image_decoder_png*) decoder;
if (!png->num_lines) {
return ERROR("PNG: end of file");
}
if (setjmp(png_jmpbuf(png->png_ptr))) {
image_decoder_reset(decoder);
return ERROR(png->error);
}
png_read_row(png->png_ptr, buffer, NULL);
png->num_lines --;
return NULL;
}
/* Create PNG image decoder
*/
image_decoder*
image_decoder_png_new (void)
{
image_decoder_png *png = mem_new(image_decoder_png, 1);
png->decoder.content_type = "image/png";
png->decoder.free = image_decoder_png_free;
png->decoder.begin = image_decoder_png_begin;
png->decoder.reset = image_decoder_png_reset;
png->decoder.get_bytes_per_pixel = image_decoder_png_get_bytes_per_pixel;
png->decoder.get_params = image_decoder_png_get_params;
png->decoder.set_window = image_decoder_png_set_window;
png->decoder.read_line = image_decoder_png_read_line;
return &png->decoder;
}
/* vim:ts=8:sw=4:et
*/