terom@56: #include "png.h" // pt_png header terom@56: #include "error.h" terom@56: #include "shared/log.h" // debug only terom@56: terom@56: #include // sysmtem libpng header terom@56: #include terom@56: terom@56: terom@56: #define min(a, b) (((a) < (b)) ? (a) : (b)) terom@56: terom@69: int pt_png_check (const char *path) terom@69: { terom@69: FILE *fp; terom@69: uint8_t header[8]; terom@69: int ret; terom@69: terom@69: // fopen terom@69: if ((fp = fopen(path, "rb")) == NULL) terom@69: RETURN_ERROR(PT_ERR_IMG_OPEN); terom@69: terom@69: // read terom@69: if (fread(header, 1, sizeof(header), fp) != sizeof(header)) terom@69: JUMP_SET_ERROR(ret, PT_ERR_IMG_FORMAT); terom@69: terom@69: // compare signature terom@69: if (png_sig_cmp(header, 0, sizeof(header))) terom@69: // not a PNG file terom@69: ret = 1; terom@69: terom@69: else terom@69: // valid PNG file terom@69: ret = 0; terom@69: terom@69: error: terom@69: // cleanup terom@69: fclose(fp); terom@69: terom@69: return ret; terom@69: } terom@69: terom@56: int pt_png_open (struct pt_image *image, struct pt_png_img *img) terom@56: { terom@56: int err; terom@69: terom@69: // init terom@69: memset(img, 0, sizeof(*img)); terom@56: terom@56: // open I/O terom@69: if ((err = pt_image_open_file(image, &img->fh))) terom@56: JUMP_ERROR(err); terom@56: terom@56: // create the struct terom@56: if ((img->png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL)) == NULL) terom@56: JUMP_SET_ERROR(err, PT_ERR_PNG_CREATE); terom@56: terom@56: // create the info terom@56: if ((img->info = png_create_info_struct(img->png)) == NULL) terom@56: JUMP_SET_ERROR(err, PT_ERR_PNG_CREATE); terom@56: terom@56: // setup error trap for the I/O terom@56: if (setjmp(png_jmpbuf(img->png))) terom@56: JUMP_SET_ERROR(err, PT_ERR_PNG); terom@56: terom@56: // setup error trap terom@56: if (setjmp(png_jmpbuf(img->png))) terom@56: JUMP_SET_ERROR(err, PT_ERR_PNG); terom@56: terom@56: terom@56: // setup I/O to FILE terom@69: png_init_io(img->png, img->fh); terom@56: terom@56: // read meta-info terom@56: png_read_info(img->png, img->info); terom@56: terom@56: terom@69: // img->fh will be closed by pt_png_release_read terom@56: return 0; terom@56: terom@56: error: terom@56: // cleanup terom@56: pt_png_release_read(img); terom@56: terom@56: return err; terom@56: } terom@56: terom@56: int pt_png_read_header (struct pt_png_img *img, struct pt_png_header *header, size_t *data_size) terom@56: { terom@69: // check image doesn't use any options we don't handle terom@69: if (png_get_interlace_type(img->png, img->info) != PNG_INTERLACE_NONE) { terom@69: log_warn("Can't handle interlaced PNG"); terom@69: terom@69: RETURN_ERROR(PT_ERR_IMG_FORMAT); terom@69: } terom@69: terom@56: terom@56: // initialize terom@56: memset(header, 0, sizeof(*header)); terom@56: terom@56: // fill in basic info terom@56: header->width = png_get_image_width(img->png, img->info); terom@56: header->height = png_get_image_height(img->png, img->info); terom@56: header->bit_depth = png_get_bit_depth(img->png, img->info); terom@56: header->color_type = png_get_color_type(img->png, img->info); terom@56: terom@56: log_debug("width=%u, height=%u, bit_depth=%u, color_type=%u", terom@56: header->width, header->height, header->bit_depth, header->color_type terom@56: ); terom@56: terom@56: // only pack 1 pixel per byte, changes rowbytes terom@56: if (header->bit_depth < 8) terom@56: png_set_packing(img->png); terom@56: terom@56: // fill in other info terom@56: header->row_bytes = png_get_rowbytes(img->png, img->info); terom@56: terom@56: // calculate bpp as num_channels * bpc terom@69: // this assumes the packed bit depth will be either 8 or 16 terom@56: header->col_bytes = png_get_channels(img->png, img->info) * (header->bit_depth == 16 ? 2 : 1); terom@56: terom@56: log_debug("row_bytes=%u, col_bytes=%u", header->row_bytes, header->col_bytes); terom@56: terom@56: // palette etc. terom@56: if (header->color_type == PNG_COLOR_TYPE_PALETTE) { terom@56: int num_palette; terom@56: png_colorp palette; terom@56: terom@56: if (png_get_PLTE(img->png, img->info, &palette, &num_palette) == 0) terom@69: // PLTE chunk not read? terom@56: RETURN_ERROR(PT_ERR_PNG); terom@56: terom@56: // should only be 256 of them at most terom@56: assert(num_palette <= PNG_MAX_PALETTE_LENGTH); terom@56: terom@56: // copy terom@56: header->num_palette = num_palette; terom@56: memcpy(&header->palette, palette, num_palette * sizeof(*palette)); terom@56: terom@56: log_debug("num_palette=%u", num_palette); terom@56: } terom@56: terom@56: // calculate data size terom@56: *data_size = header->height * header->row_bytes; terom@56: terom@56: return 0; terom@56: } terom@56: terom@56: /** terom@56: * Decode the PNG data directly to memory - not good for sparse backgrounds terom@56: */ terom@61: static int pt_png_decode_direct (struct pt_png_img *img, const struct pt_png_header *header, const struct pt_image_params *params, uint8_t *out) terom@56: { terom@56: // write out raw image data a row at a time terom@56: for (size_t row = 0; row < header->height; row++) { terom@56: // read row data, non-interlaced terom@56: png_read_row(img->png, out + row * header->row_bytes, NULL); terom@56: } terom@56: terom@56: return 0; terom@56: } terom@56: terom@56: /** terom@56: * Decode the PNG data, filtering it for sparse regions terom@56: */ terom@56: static int pt_png_decode_sparse (struct pt_png_img *img, const struct pt_png_header *header, const struct pt_image_params *params, uint8_t *out) terom@56: { terom@56: // one row of pixel data terom@56: uint8_t *row_buf; terom@56: terom@56: // alloc terom@56: if ((row_buf = malloc(header->row_bytes)) == NULL) terom@56: RETURN_ERROR(PT_ERR_MEM); terom@56: terom@56: // decode each row at a time terom@56: for (size_t row = 0; row < header->height; row++) { terom@56: // read row data, non-interlaced terom@56: png_read_row(img->png, row_buf, NULL); terom@56: terom@56: // skip background-colored regions to keep the cache file sparse terom@56: // ...in blocks of PT_CACHE_BLOCK_SIZE bytes terom@56: for (size_t col_base = 0; col_base < header->width; col_base += PT_IMG_BLOCK_SIZE) { terom@56: // size of this block in bytes terom@56: size_t block_size = min(PT_IMG_BLOCK_SIZE * header->col_bytes, header->row_bytes - col_base); terom@56: terom@56: // ...each pixel terom@56: for ( terom@56: size_t col = col_base; terom@56: terom@56: // BLOCK_SIZE * col_bytes wide, don't go over the edge terom@56: col < col_base + block_size; terom@56: terom@56: col += header->col_bytes terom@56: ) { terom@56: // test this pixel terom@56: if (bcmp(row_buf + col, params->background_color, header->col_bytes)) { terom@56: // differs terom@56: memcpy( terom@56: out + row * header->row_bytes + col_base, terom@56: row_buf + col_base, terom@56: block_size terom@56: ); terom@56: terom@56: // skip to next block terom@56: break; terom@56: } terom@56: } terom@56: terom@56: // skip this block terom@56: continue; terom@56: } terom@56: } terom@56: terom@56: return 0; terom@56: } terom@56: terom@56: int pt_png_decode (struct pt_png_img *img, const struct pt_png_header *header, const struct pt_image_params *params, uint8_t *out) terom@56: { terom@56: int err; terom@56: terom@61: // decode terom@108: // XXX: it's an array, you silly terom@61: if (params->background_color) terom@61: err = pt_png_decode_sparse(img, header, params, out); terom@61: terom@61: else terom@61: err = pt_png_decode_direct(img, header, params, out); terom@61: terom@61: if (err) terom@56: return err; terom@56: terom@56: // finish off, ignore trailing data terom@56: png_read_end(img->png, NULL); terom@56: terom@56: return 0; terom@56: } terom@56: terom@56: int pt_png_info (struct pt_png_header *header, struct pt_image_info *info) terom@56: { terom@56: // fill in info from header terom@56: info->img_width = header->width; terom@56: info->img_height = header->height; terom@56: info->img_bpp = header->bit_depth; terom@56: terom@56: return 0; terom@56: } terom@56: terom@56: /** terom@56: * libpng I/O callback: write out data terom@56: */ terom@56: static void pt_png_mem_write (png_structp png, png_bytep data, png_size_t length) terom@56: { terom@56: struct pt_tile_mem *buf = png_get_io_ptr(png); terom@56: int err; terom@56: terom@56: // write to buffer terom@56: if ((err = pt_tile_mem_write(buf, data, length))) terom@69: // drop err, because png_error doesn't do formatted output terom@56: png_error(png, "pt_tile_mem_write: ..."); terom@56: } terom@56: terom@56: /** terom@56: * libpng I/O callback: flush buffered data terom@56: */ terom@56: static void pt_png_mem_flush (png_structp png_ptr) terom@56: { terom@56: // no-op terom@56: } terom@56: terom@56: terom@56: /** terom@56: * Return a pointer to the pixel data on \a row, starting at \a col. terom@56: */ terom@70: static inline const void* tile_row_col (const struct pt_png_header *header, const uint8_t *data, size_t row, size_t col) terom@56: { terom@56: return data + (row * header->row_bytes) + (col * header->col_bytes); terom@56: } terom@56: terom@56: /** terom@56: * Write raw tile image data, directly from the cache terom@56: */ terom@70: static int pt_png_encode_direct (struct pt_png_img *img, const struct pt_png_header *header, const uint8_t *data, const struct pt_tile_info *ti) terom@56: { terom@56: for (size_t row = ti->y; row < ti->y + ti->height; row++) terom@56: // write data directly terom@70: // missing const... terom@70: png_write_row(img->png, (const png_bytep) tile_row_col(header, data, row, ti->x)); terom@56: terom@56: return 0; terom@56: } terom@56: terom@56: /** terom@100: * Fill in a clipped region of \a width_px pixels at the given row segment terom@100: */ terom@100: static inline void tile_row_fill_clip (const struct pt_png_header *header, png_byte *row, size_t width_px) terom@100: { terom@100: // XXX: use a configureable background color, or full transparency? terom@100: memset(row, /* 0xd7 */ 0x00, width_px * header->col_bytes); terom@100: } terom@100: terom@100: /** terom@56: * Write clipped tile image data (a tile that goes over the edge of the actual image) by aligning the data from the cache as needed terom@56: */ terom@70: static int pt_png_encode_clipped (struct pt_png_img *img, const struct pt_png_header *header, const uint8_t *data, const struct pt_tile_info *ti) terom@56: { terom@56: png_byte *rowbuf; terom@56: size_t row; terom@56: terom@56: // image data goes from (ti->x ... clip_x, ti->y ... clip_y), remaining region is filled terom@56: size_t clip_x, clip_y; terom@56: terom@56: terom@69: // fit the left/bottom edge against the image dimensions terom@69: clip_x = min(ti->x + ti->width, header->width); terom@69: clip_y = min(ti->y + ti->height, header->height); terom@56: terom@56: terom@56: // allocate buffer for a single row of image data terom@56: if ((rowbuf = malloc(ti->width * header->col_bytes)) == NULL) terom@56: RETURN_ERROR(PT_ERR_MEM); terom@56: terom@56: // how much data we actually have for each row, in px and bytes terom@56: // from [(tile x)---](clip x) terom@56: size_t row_px = clip_x - ti->x; terom@56: size_t row_bytes = row_px * header->col_bytes; terom@56: terom@56: // write the rows that we have terom@56: // from [(tile y]---](clip y) terom@56: for (row = ti->y; row < clip_y; row++) { terom@56: // copy in the actual tile data... terom@56: memcpy(rowbuf, tile_row_col(header, data, row, ti->x), row_bytes); terom@56: terom@56: // generate the data for the remaining, clipped, columns terom@56: tile_row_fill_clip(header, rowbuf + row_bytes, (ti->width - row_px)); terom@56: terom@56: // write terom@56: png_write_row(img->png, rowbuf); terom@56: } terom@56: terom@56: // generate the data for the remaining, clipped, rows terom@56: tile_row_fill_clip(header, rowbuf, ti->width); terom@56: terom@56: // write out the remaining rows as clipped data terom@56: for (; row < ti->y + ti->height; row++) terom@56: png_write_row(img->png, rowbuf); terom@56: terom@56: // ok terom@56: return 0; terom@56: } terom@56: terom@56: /** terom@56: * Write unscaled tile data terom@56: */ terom@70: static int pt_png_encode_unzoomed (struct pt_png_img *img, const struct pt_png_header *header, const uint8_t *data, const struct pt_tile_info *ti) terom@56: { terom@56: int err; terom@56: terom@56: // set basic info terom@56: png_set_IHDR(img->png, img->info, ti->width, ti->height, header->bit_depth, header->color_type, terom@56: PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT terom@56: ); terom@56: terom@56: // set palette? terom@56: if (header->color_type == PNG_COLOR_TYPE_PALETTE) terom@61: // oops... missing const terom@61: png_set_PLTE(img->png, img->info, (png_colorp) header->palette, header->num_palette); terom@56: terom@56: // write meta-info terom@56: png_write_info(img->png, img->info); terom@56: terom@56: // our pixel data is packed into 1 pixel per byte (8bpp or 16bpp) terom@56: png_set_packing(img->png); terom@56: terom@56: // figure out if the tile clips terom@56: if (ti->x + ti->width <= header->width && ti->y + ti->height <= header->height) terom@56: // doesn't clip, just use the raw data terom@56: err = pt_png_encode_direct(img, header, data, ti); terom@56: terom@56: else terom@56: // fill in clipped regions terom@56: err = pt_png_encode_clipped(img, header, data, ti); terom@56: terom@56: return err; terom@56: } terom@56: terom@56: /** terom@100: * Manipulate powers of two terom@100: */ terom@100: static inline size_t scale_by_zoom_factor (size_t value, int z) terom@100: { terom@100: if (z > 0) terom@100: return value << z; terom@100: terom@100: else if (z < 0) terom@100: return value >> -z; terom@100: terom@100: else terom@100: return value; terom@100: } terom@100: terom@100: #define ADD_AVG(l, r) (l) = ((l) + (r)) / 2 terom@100: terom@100: /** terom@100: * Converts a pixel's data into a png_color terom@100: */ terom@108: static inline void png_pixel_data (const png_color **outp, const struct pt_png_header *header, const uint8_t *data, size_t row, size_t col) terom@100: { terom@100: if (header->color_type == PNG_COLOR_TYPE_PALETTE) { terom@100: // palette entry number terom@100: int p; terom@100: terom@100: if (header->bit_depth == 8) terom@100: p = *((uint8_t *) tile_row_col(header, data, row, col)); terom@100: else terom@100: // unknown terom@100: return; terom@100: terom@110: // hrhr - assume our working data is valid (or we have 255 palette entries, so it doesn't matter...) terom@110: assert(p < header->num_palette); terom@100: terom@100: // reference data from palette terom@108: *outp = &header->palette[p]; terom@100: terom@100: } else { terom@100: // unknown terom@100: } terom@100: } terom@100: terom@100: /** terom@56: * Write scaled tile data terom@56: */ terom@70: static int pt_png_encode_zoomed (struct pt_png_img *img, const struct pt_png_header *header, const uint8_t *data, const struct pt_tile_info *ti) terom@56: { terom@56: // size of the image data in px terom@56: size_t data_width = scale_by_zoom_factor(ti->width, -ti->zoom); terom@56: size_t data_height = scale_by_zoom_factor(ti->height, -ti->zoom); terom@56: terom@56: // input pixels per output pixel terom@56: size_t pixel_size = scale_by_zoom_factor(1, -ti->zoom); terom@56: terom@56: // bytes per output pixel terom@56: size_t pixel_bytes = 3; terom@56: terom@56: // size of the output tile in px terom@56: size_t row_width = ti->width; terom@56: terom@56: // size of an output row in bytes (RGB) terom@56: size_t row_bytes = row_width * 3; terom@56: terom@56: // buffer to hold output rows terom@56: uint8_t *row_buf; terom@100: terom@108: // color entry for pixel terom@108: const png_color *c = &header->palette[0]; terom@56: terom@69: // only supports zooming out... terom@56: if (ti->zoom >= 0) terom@86: RETURN_ERROR(PT_ERR_TILE_ZOOM); terom@56: terom@56: if ((row_buf = malloc(row_bytes)) == NULL) terom@56: RETURN_ERROR(PT_ERR_MEM); terom@56: terom@56: // suppress warning... terom@56: (void) data_height; terom@56: terom@56: // define pixel format: 8bpp RGB terom@56: png_set_IHDR(img->png, img->info, ti->width, ti->height, 8, PNG_COLOR_TYPE_RGB, terom@56: PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT terom@56: ); terom@56: terom@56: // write meta-info terom@56: png_write_info(img->png, img->info); terom@56: terom@56: // ...each output row terom@56: for (size_t out_row = 0; out_row < ti->height; out_row++) { terom@56: memset(row_buf, 0, row_bytes); terom@56: terom@56: // ...includes pixels starting from this row. terom@56: size_t in_row_offset = ti->y + scale_by_zoom_factor(out_row, -ti->zoom); terom@56: terom@56: // ...each out row includes pixel_size in rows terom@56: for (size_t in_row = in_row_offset; in_row < in_row_offset + pixel_size && in_row < header->height; in_row++) { terom@56: // and includes each input pixel terom@56: for (size_t in_col = ti->x; in_col < ti->x + data_width && in_col < header->width; in_col++) { terom@56: terom@56: // ...for this output pixel terom@56: size_t out_col = scale_by_zoom_factor(in_col - ti->x, ti->zoom); terom@56: terom@56: // get pixel RGB data terom@100: png_pixel_data(&c, header, data, in_row, in_col); terom@56: terom@56: // average the RGB data terom@108: ADD_AVG(row_buf[out_col * pixel_bytes + 0], c->red); terom@108: ADD_AVG(row_buf[out_col * pixel_bytes + 1], c->green); terom@108: ADD_AVG(row_buf[out_col * pixel_bytes + 2], c->blue); terom@56: } terom@56: } terom@56: terom@56: // output terom@56: png_write_row(img->png, row_buf); terom@56: } terom@56: terom@56: // done terom@56: return 0; terom@56: } terom@56: terom@70: int pt_png_tile (const struct pt_png_header *header, const uint8_t *data, struct pt_tile *tile) terom@56: { terom@56: struct pt_png_img _img, *img = &_img; terom@56: struct pt_tile_info *ti = &tile->info; terom@56: int err; terom@56: terom@69: // init img terom@84: memset(img, 0, sizeof(*img)); terom@69: terom@56: // check within bounds terom@56: if (ti->x >= header->width || ti->y >= header->height) terom@56: // completely outside terom@56: RETURN_ERROR(PT_ERR_TILE_CLIP); terom@56: terom@56: // open PNG writer terom@56: if ((img->png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL)) == NULL) terom@56: JUMP_SET_ERROR(err, PT_ERR_PNG_CREATE); terom@56: terom@56: if ((img->info = png_create_info_struct(img->png)) == NULL) terom@56: JUMP_SET_ERROR(err, PT_ERR_PNG_CREATE); terom@56: terom@56: // libpng error trap terom@56: if (setjmp(png_jmpbuf(img->png))) terom@56: JUMP_SET_ERROR(err, PT_ERR_PNG); terom@56: terom@56: terom@56: terom@56: // setup output I/O terom@56: switch (tile->out_type) { terom@56: case PT_TILE_OUT_FILE: terom@56: // use default FILE* operation terom@69: // do NOT store in img->fh terom@56: png_init_io(img->png, tile->out.file); terom@56: terom@56: break; terom@56: terom@56: case PT_TILE_OUT_MEM: terom@56: // use pt_tile_mem struct via pt_png_mem_* callbacks terom@56: png_set_write_fn(img->png, &tile->out.mem, pt_png_mem_write, pt_png_mem_flush); terom@56: terom@56: break; terom@56: terom@56: default: terom@56: FATAL("tile->out_type: %d", tile->out_type); terom@56: } terom@56: terom@56: terom@56: terom@56: // unscaled or scaled? terom@56: if (ti->zoom) terom@56: err = pt_png_encode_zoomed(img, header, data, ti); terom@56: terom@56: else terom@56: err = pt_png_encode_unzoomed(img, header, data, ti); terom@56: terom@56: if (err) terom@56: goto error; terom@56: terom@56: terom@56: // flush remaining output terom@56: png_write_flush(img->png); terom@56: terom@56: // done terom@56: png_write_end(img->png, img->info); terom@56: terom@56: error: terom@56: // cleanup terom@56: pt_png_release_write(img); terom@56: terom@56: return err; terom@56: } terom@56: terom@56: terom@56: void pt_png_release_read (struct pt_png_img *img) terom@56: { terom@56: png_destroy_read_struct(&img->png, &img->info, NULL); terom@69: terom@69: // close possible filehandle terom@69: if (img->fh) { terom@69: if (fclose(img->fh)) terom@69: log_warn_errno("fclose"); terom@69: } terom@56: } terom@56: terom@56: void pt_png_release_write (struct pt_png_img *img) terom@56: { terom@56: png_destroy_write_struct(&img->png, &img->info); terom@69: terom@69: // close possible filehandle terom@69: if (img->fh) { terom@69: if (fclose(img->fh)) terom@69: log_warn_errno("fclose"); terom@69: } terom@69: terom@56: } terom@56: