--- a/include/pngtile.h Sun Jan 24 23:20:39 2010 +0200
+++ b/include/pngtile.h Mon Jan 25 01:41:25 2010 +0200
@@ -45,18 +45,27 @@
/** Cache exists, but is stale */
PT_CACHE_STALE = 2,
+
+ /** Cache exists, but it was generated using an incompatible version of this library */
+ PT_CACHE_INCOMPAT = 3,
};
/** Metadata info for image. Values will be set to zero if not available */
struct pt_image_info {
- /** Dimensions of image */
- size_t width, height;
+ /** Dimensions of image. Only available if the cache is open */
+ size_t img_width, img_height;
+
+ /** Bits per pixel */
+ size_t img_bpp;
/** Last update of image file */
time_t image_mtime;
/** Size of image file in bytes */
size_t image_bytes;
+
+ /** Cache format version or -err */
+ int cache_version;
/** Last update of cache file */
time_t cache_mtime;
@@ -121,7 +130,9 @@
int pt_image_open (struct pt_image **image_ptr, struct pt_ctx *ctx, const char *png_path, int cache_mode);
/**
- * Get the image's metadata
+ * Get the image's metadata.
+ *
+ * XXX: return void, this never fails, just returns partial info
*/
int pt_image_info (struct pt_image *image, const struct pt_image_info **info_ptr);
@@ -141,6 +152,8 @@
/**
* Load the image's cache in read-only mode without trying to update it.
+ *
+ * Fails if the cache doesn't exist.
*/
// XXX: rename to pt_image_open?
int pt_image_load (struct pt_image *image);
@@ -207,6 +220,7 @@
PT_ERR_CACHE_TRUNC,
PT_ERR_CACHE_MMAP,
PT_ERR_CACHE_RENAME_TMP,
+ PT_ERR_CACHE_VERSION,
PT_ERR_TILE_CLIP,
--- a/src/lib/cache.c Sun Jan 24 23:20:39 2010 +0200
+++ b/src/lib/cache.c Mon Jan 25 01:41:25 2010 +0200
@@ -42,9 +42,89 @@
return err;
}
+/**
+ * Open the cache file as an fd for reading
+ *
+ * XXX: use some kind of locking?
+ */
+static int pt_cache_open_read_fd (struct pt_cache *cache, int *fd_ptr)
+{
+ int fd;
+
+ // actual open()
+ if ((fd = open(cache->path, O_RDONLY)) < 0)
+ RETURN_ERROR_ERRNO(PT_ERR_OPEN_MODE, EACCES);
+
+ // ok
+ *fd_ptr = fd;
+
+ return 0;
+}
+
+/**
+ * Read in the cache header from the open file
+ */
+static int pt_cache_read_header (int fd, struct pt_cache_header *header)
+{
+ size_t len = sizeof(*header);
+ char *buf = (char *) header;
+
+ // seek to start
+ if (lseek(fd, 0, SEEK_SET) != 0)
+ RETURN_ERROR(PT_ERR_CACHE_SEEK);
+
+ // write out full header
+ while (len) {
+ ssize_t ret;
+
+ // try and write out the header
+ if ((ret = read(fd, buf, len)) <= 0)
+ RETURN_ERROR(PT_ERR_CACHE_READ);
+
+ // update offset
+ buf += ret;
+ len -= ret;
+ }
+
+ // done
+ return 0;
+}
+
+/**
+ * Read and return the version number from the cache file, temporarily opening it if needed
+ */
+static int pt_cache_version (struct pt_cache *cache)
+{
+ int fd;
+ struct pt_cache_header header;
+ int ret;
+
+ // already open?
+ if (cache->file)
+ return cache->file->header.version;
+
+ // temp. open
+ if ((ret = pt_cache_open_read_fd(cache, &fd)))
+ return ret;
+
+ // read header
+ if ((ret = pt_cache_read_header(fd, &header)))
+ JUMP_ERROR(ret);
+
+ // ok
+ ret = header.version;
+
+error:
+ // close
+ close(fd);
+
+ return ret;
+}
+
int pt_cache_status (struct pt_cache *cache, const char *img_path)
{
struct stat st_img, st_cache;
+ int ver;
// test original file
if (stat(img_path, &st_img) < 0)
@@ -62,22 +142,27 @@
// compare mtime
if (st_img.st_mtime > st_cache.st_mtime)
return PT_CACHE_STALE;
+
+ // read version
+ if ((ver = pt_cache_version(cache)) < 0)
+ // fail
+ return ver;
- else
- return PT_CACHE_FRESH;
+ // compare version
+ if (ver != PT_CACHE_VERSION)
+ return PT_CACHE_INCOMPAT;
+
+ // ok, should be in order
+ return PT_CACHE_FRESH;
}
-int pt_cache_info (struct pt_cache *cache, struct pt_image_info *info)
+void pt_cache_info (struct pt_cache *cache, struct pt_image_info *info)
{
struct stat st;
- int err;
- // ensure open
- if ((err = pt_cache_open(cache)))
- return err;
-
- info->width = cache->header->width;
- info->height = cache->header->height;
+ if (cache->file)
+ // img info
+ pt_png_info(&cache->file->header.png, info);
// stat
if (stat(cache->path, &st) < 0) {
@@ -88,12 +173,11 @@
} else {
// store
+ info->cache_version = pt_cache_version(cache);
info->cache_mtime = st.st_mtime;
info->cache_bytes = st.st_size;
info->cache_blocks = st.st_blocks;
}
-
- return 0;
}
/**
@@ -101,11 +185,10 @@
*/
static void pt_cache_abort (struct pt_cache *cache)
{
- if (cache->header != NULL) {
- munmap(cache->header, PT_CACHE_HEADER_SIZE + cache->size);
+ if (cache->file != NULL) {
+ munmap(cache->file, sizeof(struct pt_cache_file) + cache->file->header.data_size);
- cache->header = NULL;
- cache->data = NULL;
+ cache->file = NULL;
}
if (cache->fd >= 0) {
@@ -116,25 +199,6 @@
}
/**
- * Open the cache file as an fd for reading
- *
- * XXX: needs locking
- */
-static int pt_cache_open_read_fd (struct pt_cache *cache, int *fd_ptr)
-{
- int fd;
-
- // actual open()
- if ((fd = open(cache->path, O_RDONLY)) < 0)
- RETURN_ERROR_ERRNO(PT_ERR_OPEN_MODE, EACCES);
-
- // ok
- *fd_ptr = fd;
-
- return 0;
-}
-
-/**
* Open the .tmp cache file as an fd for writing
*/
static int pt_cache_open_tmp_fd (struct pt_cache *cache, int *fd_ptr)
@@ -147,7 +211,7 @@
RETURN_ERROR(PT_ERR_PATH);
// open for write, create
- // XXX: locking?
+ // XXX: locking? At least O_EXCL...
if ((fd = open(tmp_path, O_RDWR | O_CREAT, 0644)) < 0)
RETURN_ERROR(PT_ERR_CACHE_OPEN_TMP);
@@ -159,9 +223,9 @@
/**
- * Mmap the opened cache file using PT_CACHE_HEADER_SIZE plus the calculated size stored in cache->size
+ * Mmap the pt_cache_file using sizeof(struct pt_cache_file) + data_size
*/
-static int pt_cache_open_mmap (struct pt_cache *cache, void **addr_ptr, bool readonly)
+static int pt_cache_open_mmap (struct pt_cache *cache, void **addr_ptr, size_t data_size, bool readonly)
{
int prot = 0;
void *addr;
@@ -176,7 +240,7 @@
}
// mmap() the full file including header
- if ((addr = mmap(NULL, PT_CACHE_HEADER_SIZE + cache->size, prot, MAP_SHARED, cache->fd, 0)) == MAP_FAILED)
+ if ((addr = mmap(NULL, sizeof(struct pt_cache_file) + data_size, prot, MAP_SHARED, cache->fd, 0)) == MAP_FAILED)
RETURN_ERROR(PT_ERR_CACHE_MMAP);
// ok
@@ -185,33 +249,39 @@
return 0;
}
-/**
- * Read in the cache header from the open file
- */
-static int pt_cache_read_header (struct pt_cache *cache, struct pt_cache_header *header)
+int pt_cache_open (struct pt_cache *cache)
{
- size_t len = sizeof(*header);
- char *buf = (char *) header;
-
- // seek to start
- if (lseek(cache->fd, 0, SEEK_SET) != 0)
- RETURN_ERROR(PT_ERR_CACHE_SEEK);
+ struct pt_cache_header header;
+ int err;
- // write out full header
- while (len) {
- ssize_t ret;
-
- // try and write out the header
- if ((ret = read(cache->fd, buf, len)) <= 0)
- RETURN_ERROR(PT_ERR_CACHE_READ);
+ // ignore if already open
+ if (cache->file)
+ return 0;
- // update offset
- buf += ret;
- len -= ret;
- }
+ // open the .cache in readonly mode
+ if ((err = pt_cache_open_read_fd(cache, &cache->fd)))
+ return err;
+
+ // read in header
+ if ((err = pt_cache_read_header(cache->fd, &header)))
+ JUMP_ERROR(err);
+
+ // check version
+ if (header.version != PT_CACHE_VERSION)
+ JUMP_SET_ERROR(err, PT_ERR_CACHE_VERSION);
+
+ // mmap the header + data
+ if ((err = pt_cache_open_mmap(cache, (void **) &cache->file, header.data_size, true)))
+ JUMP_ERROR(err);
// done
return 0;
+
+error:
+ // cleanup
+ pt_cache_abort(cache);
+
+ return err;
}
/**
@@ -248,7 +318,6 @@
*/
static int pt_cache_create (struct pt_cache *cache, struct pt_cache_header *header)
{
- void *base;
int err;
// no access
@@ -259,23 +328,16 @@
if ((err = pt_cache_open_tmp_fd(cache, &cache->fd)))
return err;
- // calculate data size
- cache->size = header->height * header->row_bytes;
+ // write header
+ if ((err = pt_cache_write_header(cache, header)))
+ JUMP_ERROR(err);
// grow file
- if (ftruncate(cache->fd, PT_CACHE_HEADER_SIZE + cache->size) < 0)
+ if (ftruncate(cache->fd, sizeof(struct pt_cache_file) + header->data_size) < 0)
JUMP_SET_ERROR(err, PT_ERR_CACHE_TRUNC);
// mmap header and data
- if ((err = pt_cache_open_mmap(cache, &base, false)))
- JUMP_ERROR(err);
-
- cache->header = base;
- cache->data = base + PT_CACHE_HEADER_SIZE;
-
- // write header
- // XXX: should just mmap...
- if ((err = pt_cache_write_header(cache, header)))
+ if ((err = pt_cache_open_mmap(cache, (void **) &cache->file, header->data_size, false)))
JUMP_ERROR(err);
// done
@@ -307,422 +369,43 @@
return 0;
}
-int pt_cache_open (struct pt_cache *cache)
-{
- struct pt_cache_header header;
- void *base;
- int err;
-
- // ignore if already open
- if (cache->header && cache->data)
- return 0;
-
- // open the .cache
- if ((err = pt_cache_open_read_fd(cache, &cache->fd)))
- return err;
-
- // read in header
- if ((err = pt_cache_read_header(cache, &header)))
- JUMP_ERROR(err);
-
- // calculate data size
- cache->size = header.height * header.row_bytes;
-
- // mmap header and data
- if ((err = pt_cache_open_mmap(cache, &base, true)))
- JUMP_ERROR(err);
-
- cache->header = base;
- cache->data = base + PT_CACHE_HEADER_SIZE;
-
- // done
- return 0;
-
-error:
- // cleanup
- pt_cache_abort(cache);
-
- return err;
-}
-
-#define min(a, b) (((a) < (b)) ? (a) : (b))
-
-/**
- * Decode the PNG data directly to mmap() - not good for sparse backgrounds
- */
-static int decode_png_raw (struct pt_cache *cache, png_structp png, png_infop info)
-{
- // write out raw image data a row at a time
- for (size_t row = 0; row < cache->header->height; row++) {
- // read row data, non-interlaced
- png_read_row(png, cache->data + row * cache->header->row_bytes, NULL);
- }
-
- return 0;
-}
-
-static int decode_png_sparse (struct pt_cache *cache, png_structp png, png_infop info)
-{
- // one row of pixel data
- uint8_t *row_buf;
-
- // alloc
- if ((row_buf = malloc(cache->header->row_bytes)) == NULL)
- RETURN_ERROR(PT_ERR_MEM);
-
- // decode each row at a time
- for (size_t row = 0; row < cache->header->height; row++) {
- // read row data, non-interlaced
- png_read_row(png, row_buf, NULL);
-
- // skip background-colored regions to keep the cache file sparse
- // ...in blocks of PT_CACHE_BLOCK_SIZE bytes
- for (size_t col_base = 0; col_base < cache->header->width; col_base += PT_CACHE_BLOCK_SIZE) {
- // size of this block in bytes
- size_t block_size = min(PT_CACHE_BLOCK_SIZE * cache->header->col_bytes, cache->header->row_bytes - col_base);
-
- // ...each pixel
- for (
- size_t col = col_base;
-
- // BLOCK_SIZE * col_bytes wide, don't go over the edge
- col < col_base + block_size;
-
- col += cache->header->col_bytes
- ) {
- // test this pixel
- if (bcmp(row_buf + col, cache->header->params.background_color, cache->header->col_bytes)) {
- // differs
- memcpy(
- cache->data + row * cache->header->row_bytes + col_base,
- row_buf + col_base,
- block_size
- );
-
- // skip to next block
- break;
- }
- }
-
- // skip this block
- continue;
- }
- }
-
- return 0;
-}
-
-int pt_cache_update_png (struct pt_cache *cache, png_structp png, png_infop info, const struct pt_image_params *params)
+int pt_cache_update (struct pt_cache *cache, struct pt_png_img *img, const struct pt_image_params *params)
{
struct pt_cache_header header;
int err;
// XXX: check cache_mode
- // XXX: check image doesn't use any options we don't handle
// XXX: close any already-opened cache file
-
- memset(&header, 0, sizeof(header));
-
- // fill in basic info
- header.width = png_get_image_width(png, info);
- header.height = png_get_image_height(png, info);
- header.bit_depth = png_get_bit_depth(png, info);
- header.color_type = png_get_color_type(png, info);
-
- log_debug("width=%u, height=%u, bit_depth=%u, color_type=%u",
- header.width, header.height, header.bit_depth, header.color_type
- );
-
- // only pack 1 pixel per byte, changes rowbytes
- if (header.bit_depth < 8)
- png_set_packing(png);
-
- // fill in other info
- header.row_bytes = png_get_rowbytes(png, info);
+
+ // prep header
+ header.version = PT_CACHE_VERSION;
+ header.format = PT_IMG_PNG;
- // calculate bpp as num_channels * bpc
- // XXX: this assumes the packed bit depth will be either 8 or 16
- header.col_bytes = png_get_channels(png, info) * (header.bit_depth == 16 ? 2 : 1);
-
- log_debug("row_bytes=%u, col_bytes=%u", header.row_bytes, header.col_bytes);
-
- // palette etc.
- if (header.color_type == PNG_COLOR_TYPE_PALETTE) {
- int num_palette;
- png_colorp palette;
+ // read img header
+ if ((err = pt_png_read_header(img, &header.png, &header.data_size)))
+ return err;
- if (png_get_PLTE(png, info, &palette, &num_palette) == 0)
- // XXX: PLTE chunk not read?
- RETURN_ERROR(PT_ERR_PNG);
-
- // should only be 256 of them at most
- assert(num_palette <= PNG_MAX_PALETTE_LENGTH);
-
- // copy
- header.num_palette = num_palette;
- memcpy(&header.palette, palette, num_palette * sizeof(*palette));
-
- log_debug("num_palette=%u", num_palette);
- }
-
- // any params
+ // save any params
if (params)
header.params = *params;
- // create .tmp and write out header
+ // create/open .tmp and write out header
if ((err = pt_cache_create(cache, &header)))
return err;
-
- // decode
- if ((err = decode_png_sparse(cache, png, info)))
+
+ // decode to disk
+ if ((err = pt_png_decode(img, &cache->file->header.png, &cache->file->header.params, cache->file->data)))
return err;
- // move from .tmp to .cache
+ // done, commit .tmp
if ((err = pt_cache_create_done(cache)))
// XXX: pt_cache_abort?
return err;
- // done!
- return 0;
-}
-
-/**
- * Return a pointer to the pixel data on \a row, starting at \a col.
- */
-static inline void* tile_row_col (struct pt_cache *cache, size_t row, size_t col)
-{
- return cache->data + (row * cache->header->row_bytes) + (col * cache->header->col_bytes);
-}
-
-/**
- * Fill in a clipped region of \a width_px pixels at the given row segment
- */
-static inline void tile_row_fill_clip (struct pt_cache *cache, png_byte *row, size_t width_px)
-{
- // XXX: use a configureable background color, or full transparency?
- memset(row, /* 0xd7 */ 0x00, width_px * cache->header->col_bytes);
-}
-
-/**
- * Write raw tile image data, directly from the cache
- */
-static int write_png_data_direct (struct pt_cache *cache, png_structp png, png_infop info, const struct pt_tile_info *ti)
-{
- for (size_t row = ti->y; row < ti->y + ti->height; row++)
- // write data directly
- png_write_row(png, tile_row_col(cache, row, ti->x));
-
return 0;
}
-/**
- * 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
- */
-static int write_png_data_clipped (struct pt_cache *cache, png_structp png, png_infop info, const struct pt_tile_info *ti)
-{
- png_byte *rowbuf;
- size_t row;
-
- // image data goes from (ti->x ... clip_x, ti->y ... clip_y), remaining region is filled
- size_t clip_x, clip_y;
-
-
- // figure out if the tile clips over the right edge
- // XXX: use min()
- if (ti->x + ti->width > cache->header->width)
- clip_x = cache->header->width;
- else
- clip_x = ti->x + ti->width;
-
- // figure out if the tile clips over the bottom edge
- // XXX: use min()
- if (ti->y + ti->height > cache->header->height)
- clip_y = cache->header->height;
- else
- clip_y = ti->y + ti->height;
-
-
- // allocate buffer for a single row of image data
- if ((rowbuf = malloc(ti->width * cache->header->col_bytes)) == NULL)
- RETURN_ERROR(PT_ERR_MEM);
-
- // how much data we actually have for each row, in px and bytes
- // from [(tile x)---](clip x)
- size_t row_px = clip_x - ti->x;
- size_t row_bytes = row_px * cache->header->col_bytes;
-
- // write the rows that we have
- // from [(tile y]---](clip y)
- for (row = ti->y; row < clip_y; row++) {
- // copy in the actual tile data...
- memcpy(rowbuf, tile_row_col(cache, row, ti->x), row_bytes);
-
- // generate the data for the remaining, clipped, columns
- tile_row_fill_clip(cache, rowbuf + row_bytes, (ti->width - row_px));
-
- // write
- png_write_row(png, rowbuf);
- }
-
- // generate the data for the remaining, clipped, rows
- tile_row_fill_clip(cache, rowbuf, ti->width);
-
- // write out the remaining rows as clipped data
- for (; row < ti->y + ti->height; row++)
- png_write_row(png, rowbuf);
-
- // ok
- return 0;
-}
-
-static size_t scale_by_zoom_factor (size_t value, int z)
-{
- if (z > 0)
- return value << z;
-
- else if (z < 0)
- return value >> -z;
-
- else
- return value;
-}
-
-#define ADD_AVG(l, r) (l) = ((l) + (r)) / 2
-
-static int png_pixel_data (png_color *out, struct pt_cache *cache, size_t row, size_t col)
-{
- if (cache->header->color_type == PNG_COLOR_TYPE_PALETTE) {
- // palette entry number
- int p;
-
- if (cache->header->bit_depth == 8)
- p = *((uint8_t *) tile_row_col(cache, row, col));
- else
- return -1;
-
- if (p >= cache->header->num_palette)
- return -1;
-
- // reference data from palette
- *out = cache->header->palette[p];
-
- return 0;
-
- } else {
- return -1;
- }
-}
-
-/**
- * Write unscaled tile data
- */
-static int write_png_data_unzoomed (struct pt_cache *cache, png_structp png, png_infop info, const struct pt_tile_info *ti)
-{
- int err;
-
- // set basic info
- png_set_IHDR(png, info, ti->width, ti->height, cache->header->bit_depth, cache->header->color_type,
- PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT
- );
-
- // set palette?
- if (cache->header->color_type == PNG_COLOR_TYPE_PALETTE)
- png_set_PLTE(png, info, cache->header->palette, cache->header->num_palette);
-
- // write meta-info
- png_write_info(png, info);
-
- // our pixel data is packed into 1 pixel per byte (8bpp or 16bpp)
- png_set_packing(png);
-
- // figure out if the tile clips
- if (ti->x + ti->width <= cache->header->width && ti->y + ti->height <= cache->header->height)
- // doesn't clip, just use the raw data
- err = write_png_data_direct(cache, png, info, ti);
-
- else
- // fill in clipped regions
- err = write_png_data_clipped(cache, png, info, ti);
-
- return err;
-}
-
-/**
- * Write scaled tile data
- */
-static int write_png_data_zoomed (struct pt_cache *cache, png_structp png, png_infop info, const struct pt_tile_info *ti)
-{
- // size of the image data in px
- size_t data_width = scale_by_zoom_factor(ti->width, -ti->zoom);
- size_t data_height = scale_by_zoom_factor(ti->height, -ti->zoom);
-
- // input pixels per output pixel
- size_t pixel_size = scale_by_zoom_factor(1, -ti->zoom);
-
- // bytes per output pixel
- size_t pixel_bytes = 3;
-
- // size of the output tile in px
- size_t row_width = ti->width;
-
- // size of an output row in bytes (RGB)
- size_t row_bytes = row_width * 3;
-
- // buffer to hold output rows
- uint8_t *row_buf;
-
- // XXX: only supports zooming out...
- if (ti->zoom >= 0)
- RETURN_ERROR(PT_ERR_ZOOM);
-
- if ((row_buf = malloc(row_bytes)) == NULL)
- RETURN_ERROR(PT_ERR_MEM);
-
-
- // define pixel format: 8bpp RGB
- png_set_IHDR(png, info, ti->width, ti->height, 8, PNG_COLOR_TYPE_RGB,
- PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT
- );
-
- // write meta-info
- png_write_info(png, info);
-
- // ...each output row
- for (size_t out_row = 0; out_row < ti->height; out_row++) {
- memset(row_buf, 0, row_bytes);
-
- // ...includes pixels starting from this row.
- size_t in_row_offset = ti->y + scale_by_zoom_factor(out_row, -ti->zoom);
-
- // ...each out row includes pixel_size in rows
- for (size_t in_row = in_row_offset; in_row < in_row_offset + pixel_size && in_row < cache->header->height; in_row++) {
- // and includes each input pixel
- for (size_t in_col = ti->x; in_col < ti->x + data_width && in_col < cache->header->width; in_col++) {
- png_color c;
-
- // ...for this output pixel
- size_t out_col = scale_by_zoom_factor(in_col - ti->x, ti->zoom);
-
- // get pixel RGB data
- if (png_pixel_data(&c, cache, in_row, in_col))
- return -1;
-
- // average the RGB data
- ADD_AVG(row_buf[out_col * pixel_bytes + 0], c.red);
- ADD_AVG(row_buf[out_col * pixel_bytes + 1], c.green);
- ADD_AVG(row_buf[out_col * pixel_bytes + 2], c.blue);
- }
- }
-
- // output
- png_write_row(png, row_buf);
- }
-
- // done
- return 0;
-}
-
-int pt_cache_tile_png (struct pt_cache *cache, png_structp png, png_infop info, const struct pt_tile_info *ti)
+int pt_cache_tile (struct pt_cache *cache, struct pt_tile *tile)
{
int err;
@@ -730,25 +413,10 @@
if ((err = pt_cache_open(cache)))
return err;
- // check within bounds
- if (ti->x >= cache->header->width || ti->y >= cache->header->height)
- // completely outside
- RETURN_ERROR(PT_ERR_TILE_CLIP);
-
- // unscaled or scaled?
- if (ti->zoom)
- err = write_png_data_zoomed(cache, png, info, ti);
+ // render
+ if ((err = pt_png_tile(&cache->file->header.png, cache->file->data, tile)))
+ return err;
- else
- err = write_png_data_unzoomed(cache, png, info, ti);
-
- if (err)
- return err;
-
- // done, flush remaining output
- png_write_flush(png);
-
- // ok
return 0;
}
--- a/src/lib/cache.h Sun Jan 24 23:20:39 2010 +0200
+++ b/src/lib/cache.h Mon Jan 25 01:41:25 2010 +0200
@@ -7,14 +7,61 @@
* Internal image cache implementation
*/
#include "image.h"
+#include "png.h"
#include <stdint.h>
#include <stdbool.h>
-#include <png.h>
+/**
+ * Cache format version
+ */
+#define PT_CACHE_VERSION 2
/**
- * State for cache access
+ * Size used to store the cache header
+ */
+#define PT_CACHE_HEADER_SIZE 4096
+
+/**
+ * On-disk header
+ */
+struct pt_cache_header {
+ /** Set to PT_CACHE_VERSION */
+ uint16_t version;
+
+ /** Image format */
+ enum pt_img_format {
+ PT_IMG_PNG, ///< @see pt_png
+ } format;
+
+ /** Data header by format */
+ union {
+ struct pt_png_header png;
+ };
+
+ /** Parameters used */
+ struct pt_image_params params;
+
+ /** Size of the data segment */
+ size_t data_size;
+};
+
+/**
+ * On-disk data format. This struct is always exactly PT_CACHE_HEADER_SIZE long
+ */
+struct pt_cache_file {
+ /** Header */
+ struct pt_cache_header header;
+
+ /** Padding for data */
+ uint8_t padding[PT_CACHE_HEADER_SIZE - sizeof(struct pt_cache_header)];
+
+ /** Data follows, header.data_size bytes */
+ uint8_t data[];
+};
+
+/**
+ * Cache state
*/
struct pt_cache {
/** Filesystem path to cache file */
@@ -26,53 +73,14 @@
/** Opened file */
int fd;
- /** The mmap'd header */
- struct pt_cache_header *header;
-
- /** Memory-mapped file data, starting at PT_CACHE_HEADER_SIZE */
- uint8_t *data;
-
/** Size of the data segment in bytes, starting at PT_CACHE_HEADER_SIZE */
- size_t size;
-};
-
-/**
- * Size used to store the cache header
- */
-#define PT_CACHE_HEADER_SIZE 4096
+ size_t data_size;
-/**
- * On-disk header
- */
-struct pt_cache_header {
- /** Pixel dimensions of image */
- uint32_t width, height;
-
- /** Pixel format */
- uint8_t bit_depth, color_type;
-
- /** Number of png_color entries that follow */
- uint16_t num_palette;
-
- /** Number of bytes per row */
- uint32_t row_bytes;
-
- /** Number of bytes per pixel */
- uint8_t col_bytes;
-
- /** Palette entries, up to 256 entries used */
- png_color palette[PNG_MAX_PALETTE_LENGTH];
-
- /** Parameters used */
- struct pt_image_params params;
+ /** The mmap'd file */
+ struct pt_cache_file *file;
};
/**
- * Handle sparse data at this granularity (pixels)
- */
-#define PT_CACHE_BLOCK_SIZE 64
-
-/**
* Construct the image cache info object associated with the given image.
*/
int pt_cache_new (struct pt_cache **cache_ptr, const char *path, int mode);
@@ -85,14 +93,16 @@
int pt_cache_status (struct pt_cache *cache, const char *img_path);
/**
- * Get info for the cached image, open it if not already open.
+ * Get info for the cached image.
+ *
+ * Does not open it if not yet opened.
*/
-int pt_cache_info (struct pt_cache *cache, struct pt_image_info *info);
+void pt_cache_info (struct pt_cache *cache, struct pt_image_info *info);
/**
- * Update the cache data from the given PNG image.
+ * Update the cache data from the given image data
*/
-int pt_cache_update_png (struct pt_cache *cache, png_structp png, png_infop info, const struct pt_image_params *params);
+int pt_cache_update (struct pt_cache *cache, struct pt_png_img *img, const struct pt_image_params *params);
/**
* Open the existing .cache for use. If already opened, does nothing.
@@ -100,11 +110,11 @@
int pt_cache_open (struct pt_cache *cache);
/**
- * Render out a PNG tile as given, into the established png object, up to (but not including) the png_write_end.
+ * Render out the given tile
*
* If the cache is not yet open, this will open it
*/
-int pt_cache_tile_png (struct pt_cache *cache, png_structp png, png_infop info, const struct pt_tile_info *ti);
+int pt_cache_tile (struct pt_cache *cache, struct pt_tile *tile);
/**
* Release all resources associated with the given cache object without any cleanup.
--- a/src/lib/error.c Sun Jan 24 23:20:39 2010 +0200
+++ b/src/lib/error.c Mon Jan 25 01:41:25 2010 +0200
@@ -25,6 +25,7 @@
[PT_ERR_CACHE_TRUNC] = "truncate(.cache)",
[PT_ERR_CACHE_MMAP] = "mmap(.cache)",
[PT_ERR_CACHE_RENAME_TMP] = "rename(.tmp, .cache)",
+ [PT_ERR_CACHE_VERSION] = "Incompatible .cache version",
[PT_ERR_PTHREAD_CREATE] = "pthread_create",
[PT_ERR_CTX_SHUTDOWN] = "pt_ctx is shutting down",
--- a/src/lib/image.c Sun Jan 24 23:20:39 2010 +0200
+++ b/src/lib/image.c Mon Jan 25 01:41:25 2010 +0200
@@ -1,5 +1,6 @@
#include "image.h"
#include "ctx.h"
+#include "png.h"
#include "cache.h"
#include "tile.h"
#include "error.h"
@@ -11,8 +12,6 @@
#include <unistd.h>
#include <errno.h>
-#include <png.h>
-
static int pt_image_new (struct pt_image **image_ptr, struct pt_ctx *ctx, const char *path)
{
struct pt_image *image;
@@ -40,128 +39,6 @@
}
/**
- * Open the image's FILE
- */
-static int pt_image_open_file (struct pt_image *image, FILE **file_ptr)
-{
- FILE *fp;
-
- // open
- if ((fp = fopen(image->path, "rb")) == NULL)
- RETURN_ERROR(PT_ERR_IMG_FOPEN);
-
- // ok
- *file_ptr = fp;
-
- return 0;
-}
-
-/**
- * Open the PNG image, setting up the I/O and returning the png_structp and png_infop
- */
-static int pt_image_open_png (struct pt_image *image, png_structp *png_ptr, png_infop *info_ptr)
-{
- FILE *fp = NULL;
- png_structp png = NULL;
- png_infop info = NULL;
- int err;
-
- // open I/O
- if ((err = pt_image_open_file(image, &fp)))
- JUMP_ERROR(err);
-
- // create the struct
- if ((png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL)) == NULL)
- JUMP_SET_ERROR(err, PT_ERR_PNG_CREATE);
-
- // create the info
- if ((info = png_create_info_struct(png)) == NULL)
- JUMP_SET_ERROR(err, PT_ERR_PNG_CREATE);
-
- // setup error trap for the I/O
- if (setjmp(png_jmpbuf(png)))
- JUMP_SET_ERROR(err, PT_ERR_PNG);
-
- // setup I/O to FILE
- png_init_io(png, fp);
-
- // ok
- // XXX: what to do with fp? Should fclose() when done?
- *png_ptr = png;
- *info_ptr = info;
-
- return 0;
-
-error:
- // cleanup file
- if (fp) fclose(fp);
-
- // cleanup PNG state
- png_destroy_read_struct(&png, &info, NULL);
-
- return err;
-}
-
-/**
- * Update the image_info field from the given png object.
- *
- * Must be called under libpng-error-trap!
- *
- * XXX: currently this info is not used, pulled from the cache instead
- */
-static int pt_image_update_info (struct pt_image *image, png_structp png, png_infop info)
-{
- // query png_get_*
- image->info.width = png_get_image_width(png, info);
- image->info.height = png_get_image_height(png, info);
-
- return 0;
-}
-
-/**
- * Open the PNG image, and write out to the cache
- */
-static int pt_image_update_cache (struct pt_image *image, const struct pt_image_params *params)
-{
- png_structp png;
- png_infop info;
- int err = 0;
-
- // pre-check enabled
- if (!(image->cache->mode & PT_OPEN_UPDATE))
- RETURN_ERROR_ERRNO(PT_ERR_OPEN_MODE, EACCES);
-
- // open .png
- if ((err = pt_image_open_png(image, &png, &info)))
- return err;
-
- // setup error trap
- if (setjmp(png_jmpbuf(png)))
- JUMP_SET_ERROR(err, PT_ERR_PNG);
-
- // read meta-info
- png_read_info(png, info);
-
- // update our meta-info
- if ((err = pt_image_update_info(image, png, info)))
- JUMP_ERROR(err);
-
- // pass to cache object
- if ((err = pt_cache_update_png(image->cache, png, info, params)))
- JUMP_ERROR(err);
-
- // finish off, ignore trailing data
- png_read_end(png, NULL);
-
-error:
- // clean up
- // XXX: we need to close the fopen'd .png
- png_destroy_read_struct(&png, &info, NULL);
-
- return err;
-}
-
-/**
* Build a filesystem path representing the appropriate path for this image's cache entry, and store it in the given
* buffer.
*/
@@ -204,14 +81,54 @@
return err;
}
+int pt_image_open_file (struct pt_image *image, FILE **file_ptr)
+{
+ FILE *fp;
+
+ // open
+ if ((fp = fopen(image->path, "rb")) == NULL)
+ RETURN_ERROR(PT_ERR_IMG_FOPEN);
+
+ // ok
+ *file_ptr = fp;
+
+ return 0;
+}
+
+/**
+ * Open the PNG image, and write out to the cache
+ */
+int pt_image_update (struct pt_image *image, const struct pt_image_params *params)
+{
+ struct pt_png_img img;
+ int err = 0;
+
+ // pre-check enabled
+ if (!(image->cache->mode & PT_OPEN_UPDATE))
+ RETURN_ERROR_ERRNO(PT_ERR_OPEN_MODE, EACCES);
+
+ // open .png
+ if ((err = pt_png_open(image, &img)))
+ return err;
+
+ // pass to cache object
+ if ((err = pt_cache_update(image->cache, &img, params)))
+ JUMP_ERROR(err);
+
+error:
+ // clean up
+ pt_png_release_read(&img);
+
+ return err;
+}
+
+
int pt_image_info (struct pt_image *image, const struct pt_image_info **info_ptr)
{
struct stat st;
- int err;
- // update info
- if ((err = pt_cache_info(image->cache, &image->info)))
- return err;
+ // update info from cache
+ pt_cache_info(image->cache, &image->info);
// stat our info
if (stat(image->path, &st) < 0) {
@@ -236,10 +153,6 @@
return pt_cache_status(image->cache, image->path);
}
-int pt_image_update (struct pt_image *image, const struct pt_image_params *params)
-{
- return pt_image_update_cache(image, params);
-}
int pt_image_load (struct pt_image *image)
{
--- a/src/lib/image.h Sun Jan 24 23:20:39 2010 +0200
+++ b/src/lib/image.h Mon Jan 25 01:41:25 2010 +0200
@@ -22,5 +22,9 @@
struct pt_image_info info;
};
+/**
+ * Open the image's FILE
+ */
+int pt_image_open_file (struct pt_image *image, FILE **file_ptr);
#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/png.c Mon Jan 25 01:41:25 2010 +0200
@@ -0,0 +1,550 @@
+#include "png.h" // pt_png header
+#include "error.h"
+#include "shared/log.h" // debug only
+
+#include <png.h> // sysmtem libpng header
+#include <assert.h>
+
+
+#define min(a, b) (((a) < (b)) ? (a) : (b))
+
+int pt_png_open (struct pt_image *image, struct pt_png_img *img)
+{
+ FILE *fp = NULL;
+ int err;
+
+ // open I/O
+ if ((err = pt_image_open_file(image, &fp)))
+ JUMP_ERROR(err);
+
+ // create the struct
+ if ((img->png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL)) == NULL)
+ JUMP_SET_ERROR(err, PT_ERR_PNG_CREATE);
+
+ // create the info
+ if ((img->info = png_create_info_struct(img->png)) == NULL)
+ JUMP_SET_ERROR(err, PT_ERR_PNG_CREATE);
+
+ // setup error trap for the I/O
+ if (setjmp(png_jmpbuf(img->png)))
+ JUMP_SET_ERROR(err, PT_ERR_PNG);
+
+ // setup error trap
+ if (setjmp(png_jmpbuf(img->png)))
+ JUMP_SET_ERROR(err, PT_ERR_PNG);
+
+
+ // setup I/O to FILE
+ png_init_io(img->png, fp);
+
+ // read meta-info
+ png_read_info(img->png, img->info);
+
+
+ // XXX: what to do with fp? Should fclose() when done?
+ return 0;
+
+error:
+ // cleanup
+ pt_png_release_read(img);
+
+ if (fp) fclose(fp);
+
+ return err;
+}
+
+int pt_png_read_header (struct pt_png_img *img, struct pt_png_header *header, size_t *data_size)
+{
+ // XXX: check image doesn't use any options we don't handle
+
+ // initialize
+ memset(header, 0, sizeof(*header));
+
+ // fill in basic info
+ header->width = png_get_image_width(img->png, img->info);
+ header->height = png_get_image_height(img->png, img->info);
+ header->bit_depth = png_get_bit_depth(img->png, img->info);
+ header->color_type = png_get_color_type(img->png, img->info);
+
+ log_debug("width=%u, height=%u, bit_depth=%u, color_type=%u",
+ header->width, header->height, header->bit_depth, header->color_type
+ );
+
+ // only pack 1 pixel per byte, changes rowbytes
+ if (header->bit_depth < 8)
+ png_set_packing(img->png);
+
+ // fill in other info
+ header->row_bytes = png_get_rowbytes(img->png, img->info);
+
+ // calculate bpp as num_channels * bpc
+ // XXX: this assumes the packed bit depth will be either 8 or 16
+ header->col_bytes = png_get_channels(img->png, img->info) * (header->bit_depth == 16 ? 2 : 1);
+
+ log_debug("row_bytes=%u, col_bytes=%u", header->row_bytes, header->col_bytes);
+
+ // palette etc.
+ if (header->color_type == PNG_COLOR_TYPE_PALETTE) {
+ int num_palette;
+ png_colorp palette;
+
+ if (png_get_PLTE(img->png, img->info, &palette, &num_palette) == 0)
+ // XXX: PLTE chunk not read?
+ RETURN_ERROR(PT_ERR_PNG);
+
+ // should only be 256 of them at most
+ assert(num_palette <= PNG_MAX_PALETTE_LENGTH);
+
+ // copy
+ header->num_palette = num_palette;
+ memcpy(&header->palette, palette, num_palette * sizeof(*palette));
+
+ log_debug("num_palette=%u", num_palette);
+ }
+
+ // calculate data size
+ *data_size = header->height * header->row_bytes;
+
+ return 0;
+}
+
+/**
+ * Decode the PNG data directly to memory - not good for sparse backgrounds
+ */
+static int pt_png_decode_raw (struct pt_png_img *img, const struct pt_png_header *header, const struct pt_image_params *params, uint8_t *out)
+{
+ // write out raw image data a row at a time
+ for (size_t row = 0; row < header->height; row++) {
+ // read row data, non-interlaced
+ png_read_row(img->png, out + row * header->row_bytes, NULL);
+ }
+
+ return 0;
+}
+
+/**
+ * Decode the PNG data, filtering it for sparse regions
+ */
+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)
+{
+ // one row of pixel data
+ uint8_t *row_buf;
+
+ // alloc
+ if ((row_buf = malloc(header->row_bytes)) == NULL)
+ RETURN_ERROR(PT_ERR_MEM);
+
+ // decode each row at a time
+ for (size_t row = 0; row < header->height; row++) {
+ // read row data, non-interlaced
+ png_read_row(img->png, row_buf, NULL);
+
+ // skip background-colored regions to keep the cache file sparse
+ // ...in blocks of PT_CACHE_BLOCK_SIZE bytes
+ for (size_t col_base = 0; col_base < header->width; col_base += PT_IMG_BLOCK_SIZE) {
+ // size of this block in bytes
+ size_t block_size = min(PT_IMG_BLOCK_SIZE * header->col_bytes, header->row_bytes - col_base);
+
+ // ...each pixel
+ for (
+ size_t col = col_base;
+
+ // BLOCK_SIZE * col_bytes wide, don't go over the edge
+ col < col_base + block_size;
+
+ col += header->col_bytes
+ ) {
+ // test this pixel
+ if (bcmp(row_buf + col, params->background_color, header->col_bytes)) {
+ // differs
+ memcpy(
+ out + row * header->row_bytes + col_base,
+ row_buf + col_base,
+ block_size
+ );
+
+ // skip to next block
+ break;
+ }
+ }
+
+ // skip this block
+ continue;
+ }
+ }
+
+ return 0;
+}
+
+int pt_png_decode (struct pt_png_img *img, const struct pt_png_header *header, const struct pt_image_params *params, uint8_t *out)
+{
+ int err;
+
+ if ((err = pt_png_decode_sparse(img, header, params, out)))
+ return err;
+
+ // finish off, ignore trailing data
+ png_read_end(img->png, NULL);
+
+ return 0;
+}
+
+int pt_png_info (struct pt_png_header *header, struct pt_image_info *info)
+{
+ // fill in info from header
+ info->img_width = header->width;
+ info->img_height = header->height;
+ info->img_bpp = header->bit_depth;
+
+ return 0;
+}
+
+/**
+ * libpng I/O callback: write out data
+ */
+static void pt_png_mem_write (png_structp png, png_bytep data, png_size_t length)
+{
+ struct pt_tile_mem *buf = png_get_io_ptr(png);
+ int err;
+
+ // write to buffer
+ if ((err = pt_tile_mem_write(buf, data, length)))
+ // XXX: log pt_strerror(err)
+ png_error(png, "pt_tile_mem_write: ...");
+}
+
+/**
+ * libpng I/O callback: flush buffered data
+ */
+static void pt_png_mem_flush (png_structp png_ptr)
+{
+ // no-op
+}
+
+
+/**
+ * Return a pointer to the pixel data on \a row, starting at \a col.
+ */
+static inline void* tile_row_col (const struct pt_png_header *header, uint8_t *data, size_t row, size_t col)
+{
+ return data + (row * header->row_bytes) + (col * header->col_bytes);
+}
+
+/**
+ * Fill in a clipped region of \a width_px pixels at the given row segment
+ */
+static inline void tile_row_fill_clip (const struct pt_png_header *header, png_byte *row, size_t width_px)
+{
+ // XXX: use a configureable background color, or full transparency?
+ memset(row, /* 0xd7 */ 0x00, width_px * header->col_bytes);
+}
+
+/**
+ * Write raw tile image data, directly from the cache
+ */
+static int pt_png_encode_direct (struct pt_png_img *img, const struct pt_png_header *header, uint8_t *data, const struct pt_tile_info *ti)
+{
+ for (size_t row = ti->y; row < ti->y + ti->height; row++)
+ // write data directly
+ png_write_row(img->png, tile_row_col(header, data, row, ti->x));
+
+ return 0;
+}
+
+/**
+ * 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
+ */
+static int pt_png_encode_clipped (struct pt_png_img *img, const struct pt_png_header *header, uint8_t *data, const struct pt_tile_info *ti)
+{
+ png_byte *rowbuf;
+ size_t row;
+
+ // image data goes from (ti->x ... clip_x, ti->y ... clip_y), remaining region is filled
+ size_t clip_x, clip_y;
+
+
+ // figure out if the tile clips over the right edge
+ // XXX: use min()
+ if (ti->x + ti->width > header->width)
+ clip_x = header->width;
+ else
+ clip_x = ti->x + ti->width;
+
+ // figure out if the tile clips over the bottom edge
+ // XXX: use min()
+ if (ti->y + ti->height > header->height)
+ clip_y = header->height;
+ else
+ clip_y = ti->y + ti->height;
+
+
+ // allocate buffer for a single row of image data
+ if ((rowbuf = malloc(ti->width * header->col_bytes)) == NULL)
+ RETURN_ERROR(PT_ERR_MEM);
+
+ // how much data we actually have for each row, in px and bytes
+ // from [(tile x)---](clip x)
+ size_t row_px = clip_x - ti->x;
+ size_t row_bytes = row_px * header->col_bytes;
+
+ // write the rows that we have
+ // from [(tile y]---](clip y)
+ for (row = ti->y; row < clip_y; row++) {
+ // copy in the actual tile data...
+ memcpy(rowbuf, tile_row_col(header, data, row, ti->x), row_bytes);
+
+ // generate the data for the remaining, clipped, columns
+ tile_row_fill_clip(header, rowbuf + row_bytes, (ti->width - row_px));
+
+ // write
+ png_write_row(img->png, rowbuf);
+ }
+
+ // generate the data for the remaining, clipped, rows
+ tile_row_fill_clip(header, rowbuf, ti->width);
+
+ // write out the remaining rows as clipped data
+ for (; row < ti->y + ti->height; row++)
+ png_write_row(img->png, rowbuf);
+
+ // ok
+ return 0;
+}
+
+static size_t scale_by_zoom_factor (size_t value, int z)
+{
+ if (z > 0)
+ return value << z;
+
+ else if (z < 0)
+ return value >> -z;
+
+ else
+ return value;
+}
+
+#define ADD_AVG(l, r) (l) = ((l) + (r)) / 2
+
+static int png_pixel_data (png_color *out, const struct pt_png_header *header, uint8_t *data, size_t row, size_t col)
+{
+ if (header->color_type == PNG_COLOR_TYPE_PALETTE) {
+ // palette entry number
+ int p;
+
+ if (header->bit_depth == 8)
+ p = *((uint8_t *) tile_row_col(header, data, row, col));
+ else
+ return -1;
+
+ if (p >= header->num_palette)
+ return -1;
+
+ // reference data from palette
+ *out = header->palette[p];
+
+ return 0;
+
+ } else {
+ return -1;
+ }
+}
+
+/**
+ * Write unscaled tile data
+ */
+static int pt_png_encode_unzoomed (struct pt_png_img *img, const struct pt_png_header *header, uint8_t *data, const struct pt_tile_info *ti)
+{
+ int err;
+
+ // set basic info
+ png_set_IHDR(img->png, img->info, ti->width, ti->height, header->bit_depth, header->color_type,
+ PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT
+ );
+
+ // set palette?
+ if (header->color_type == PNG_COLOR_TYPE_PALETTE)
+ png_set_PLTE(img->png, img->info, header->palette, header->num_palette);
+
+ // write meta-info
+ png_write_info(img->png, img->info);
+
+ // our pixel data is packed into 1 pixel per byte (8bpp or 16bpp)
+ png_set_packing(img->png);
+
+ // figure out if the tile clips
+ if (ti->x + ti->width <= header->width && ti->y + ti->height <= header->height)
+ // doesn't clip, just use the raw data
+ err = pt_png_encode_direct(img, header, data, ti);
+
+ else
+ // fill in clipped regions
+ err = pt_png_encode_clipped(img, header, data, ti);
+
+ return err;
+}
+
+/**
+ * Write scaled tile data
+ */
+static int pt_png_encode_zoomed (struct pt_png_img *img, const struct pt_png_header *header, uint8_t *data, const struct pt_tile_info *ti)
+{
+ // size of the image data in px
+ size_t data_width = scale_by_zoom_factor(ti->width, -ti->zoom);
+ size_t data_height = scale_by_zoom_factor(ti->height, -ti->zoom);
+
+ // input pixels per output pixel
+ size_t pixel_size = scale_by_zoom_factor(1, -ti->zoom);
+
+ // bytes per output pixel
+ size_t pixel_bytes = 3;
+
+ // size of the output tile in px
+ size_t row_width = ti->width;
+
+ // size of an output row in bytes (RGB)
+ size_t row_bytes = row_width * 3;
+
+ // buffer to hold output rows
+ uint8_t *row_buf;
+
+ // XXX: only supports zooming out...
+ if (ti->zoom >= 0)
+ RETURN_ERROR(PT_ERR_ZOOM);
+
+ if ((row_buf = malloc(row_bytes)) == NULL)
+ RETURN_ERROR(PT_ERR_MEM);
+
+ // suppress warning...
+ (void) data_height;
+
+ // define pixel format: 8bpp RGB
+ png_set_IHDR(img->png, img->info, ti->width, ti->height, 8, PNG_COLOR_TYPE_RGB,
+ PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT
+ );
+
+ // write meta-info
+ png_write_info(img->png, img->info);
+
+ // ...each output row
+ for (size_t out_row = 0; out_row < ti->height; out_row++) {
+ memset(row_buf, 0, row_bytes);
+
+ // ...includes pixels starting from this row.
+ size_t in_row_offset = ti->y + scale_by_zoom_factor(out_row, -ti->zoom);
+
+ // ...each out row includes pixel_size in rows
+ for (size_t in_row = in_row_offset; in_row < in_row_offset + pixel_size && in_row < header->height; in_row++) {
+ // and includes each input pixel
+ for (size_t in_col = ti->x; in_col < ti->x + data_width && in_col < header->width; in_col++) {
+ png_color c;
+
+ // ...for this output pixel
+ size_t out_col = scale_by_zoom_factor(in_col - ti->x, ti->zoom);
+
+ // get pixel RGB data
+ if (png_pixel_data(&c, header, data, in_row, in_col))
+ return -1;
+
+ // average the RGB data
+ ADD_AVG(row_buf[out_col * pixel_bytes + 0], c.red);
+ ADD_AVG(row_buf[out_col * pixel_bytes + 1], c.green);
+ ADD_AVG(row_buf[out_col * pixel_bytes + 2], c.blue);
+ }
+ }
+
+ // output
+ png_write_row(img->png, row_buf);
+ }
+
+ // done
+ return 0;
+}
+
+int pt_png_tile (struct pt_png_header *header, uint8_t *data, struct pt_tile *tile)
+{
+ struct pt_png_img _img, *img = &_img;
+ struct pt_tile_info *ti = &tile->info;
+ int err;
+
+ // check within bounds
+ if (ti->x >= header->width || ti->y >= header->height)
+ // completely outside
+ RETURN_ERROR(PT_ERR_TILE_CLIP);
+
+ // XXX: init img
+ // open PNG writer
+ if ((img->png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL)) == NULL)
+ JUMP_SET_ERROR(err, PT_ERR_PNG_CREATE);
+
+ if ((img->info = png_create_info_struct(img->png)) == NULL)
+ JUMP_SET_ERROR(err, PT_ERR_PNG_CREATE);
+
+ // libpng error trap
+ if (setjmp(png_jmpbuf(img->png)))
+ JUMP_SET_ERROR(err, PT_ERR_PNG);
+
+
+
+ // setup output I/O
+ switch (tile->out_type) {
+ case PT_TILE_OUT_FILE:
+ // use default FILE* operation
+ png_init_io(img->png, tile->out.file);
+
+ break;
+
+ case PT_TILE_OUT_MEM:
+ // use pt_tile_mem struct via pt_png_mem_* callbacks
+ png_set_write_fn(img->png, &tile->out.mem, pt_png_mem_write, pt_png_mem_flush);
+
+ break;
+
+ default:
+ FATAL("tile->out_type: %d", tile->out_type);
+ }
+
+
+
+ // unscaled or scaled?
+ if (ti->zoom)
+ err = pt_png_encode_zoomed(img, header, data, ti);
+
+ else
+ err = pt_png_encode_unzoomed(img, header, data, ti);
+
+ if (err)
+ goto error;
+
+
+ // flush remaining output
+ png_write_flush(img->png);
+
+ // done
+ png_write_end(img->png, img->info);
+
+error:
+ // cleanup
+ pt_png_release_write(img);
+
+ return err;
+}
+
+
+
+
+
+
+
+
+
+
+void pt_png_release_read (struct pt_png_img *img)
+{
+ png_destroy_read_struct(&img->png, &img->info, NULL);
+}
+
+void pt_png_release_write (struct pt_png_img *img)
+{
+ png_destroy_write_struct(&img->png, &img->info);
+}
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/png.h Mon Jan 25 01:41:25 2010 +0200
@@ -0,0 +1,89 @@
+#ifndef PNGTILE_PNG_H
+#define PNGTILE_PNG_H
+
+/**
+ * @file
+ * PNG-specific handling
+ */
+#include <png.h>
+#include <stdint.h>
+
+/**
+ * Handle sparse data at this granularity (pixels)
+ */
+#define PT_IMG_BLOCK_SIZE 64
+
+/**
+ * PNG img state
+ */
+struct pt_png_img {
+ /** libpng state */
+ png_struct *png;
+ png_info *info;
+
+};
+
+/**
+ * Cache header layout for PNG-format images
+ */
+struct pt_png_header {
+ /** Pixel dimensions of image */
+ uint32_t width, height;
+
+ /** Pixel format */
+ uint8_t bit_depth, color_type;
+
+ /** Number of png_color entries that follow */
+ uint16_t num_palette;
+
+ /** Number of bytes per row */
+ uint32_t row_bytes;
+
+ /** Number of bytes per pixel */
+ uint8_t col_bytes;
+
+ /** Palette entries, up to 256 entries used */
+ png_color palette[PNG_MAX_PALETTE_LENGTH];
+};
+
+
+#include "image.h"
+#include "tile.h"
+
+
+/**
+ * Open the given .png image, and read info
+ */
+int pt_png_open (struct pt_image *image, struct pt_png_img *img);
+
+/**
+ * Fill in the PNG header and return the size of the pixel data
+ */
+int pt_png_read_header (struct pt_png_img *img, struct pt_png_header *header, size_t *data_size);
+
+/**
+ * Decode the PNG data into the given data segment, using the header as decoded by pt_png_read_header
+ */
+int pt_png_decode (struct pt_png_img *img, const struct pt_png_header *header, const struct pt_image_params *params, uint8_t *out);
+
+/**
+ * Fill in img_* fields of pt_image_info from header
+ */
+int pt_png_info (struct pt_png_header *header, struct pt_image_info *info);
+
+/**
+ * Render out a tile
+ */
+int pt_png_tile (struct pt_png_header *header, uint8_t *data, struct pt_tile *tile);
+
+/**
+ * Release pt_png_ctx resources as allocated by pt_png_open
+ */
+void pt_png_release_read (struct pt_png_img *img);
+
+/**
+ * Release pt_png_ctx resources as allocated by pt_png_...
+ */
+void pt_png_release_write (struct pt_png_img *img);
+
+#endif
--- a/src/lib/tile.c Sun Jan 24 23:20:39 2010 +0200
+++ b/src/lib/tile.c Mon Jan 25 01:41:25 2010 +0200
@@ -3,6 +3,34 @@
#include "shared/log.h" // only FATAL
#include <stdlib.h>
+#include <assert.h>
+
+int pt_tile_mem_write (struct pt_tile_mem *buf, void *data, size_t len)
+{
+ size_t buf_len = buf->len;
+
+ // grow?
+ while (buf->off + len > buf_len)
+ buf_len *= 2;
+
+ if (buf_len != buf->len) {
+ char *tmp;
+
+ if ((tmp = realloc(buf->base, buf_len)) == NULL)
+ RETURN_ERROR(PT_ERR_MEM);
+
+ buf->base = tmp;
+ buf->len = buf_len;
+ }
+
+ // copy
+ memcpy(buf->base + buf->off, data, len);
+
+ buf->off += len;
+
+ return 0;
+}
+
int pt_tile_new (struct pt_tile **tile_ptr)
{
@@ -47,84 +75,10 @@
return 0;
}
-static void pt_tile_mem_write (png_structp png, png_bytep data, png_size_t length)
-{
- struct pt_tile_mem *buf = png_get_io_ptr(png);
- size_t buf_len = buf->len;
-
- // grow?
- while (buf->off + length > buf_len)
- buf_len *= 2;
-
- if (buf_len != buf->len) {
- char *tmp;
-
- if ((tmp = realloc(buf->base, buf_len)) == NULL)
- png_error(png, "pt_tile_buf_write - realloc failed");
-
- buf->base = tmp;
- buf->len = buf_len;
- }
-
- // copy
- memcpy(buf->base + buf->off, data, length);
-
- buf->off += length;
-}
-
-static void pt_tile_mem_flush (png_structp png_ptr)
-{
- // no-op
-}
-
int pt_tile_render (struct pt_tile *tile)
{
- png_structp png = NULL;
- png_infop info = NULL;
- int err = 0;
-
- // open PNG writer
- if ((png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL)) == NULL)
- JUMP_SET_ERROR(err, PT_ERR_PNG_CREATE);
-
- if ((info = png_create_info_struct(png)) == NULL)
- JUMP_SET_ERROR(err, PT_ERR_PNG_CREATE);
-
- // libpng error trap
- if (setjmp(png_jmpbuf(png)))
- JUMP_SET_ERROR(err, PT_ERR_PNG);
-
- // setup output I/O
- switch (tile->out_type) {
- case PT_TILE_OUT_FILE:
- // use default FILE* operation
- png_init_io(png, tile->out.file);
-
- break;
-
- case PT_TILE_OUT_MEM:
- // use pt_tile_mem struct via pt_tile_mem_* callbacks
- png_set_write_fn(png, &tile->out.mem, pt_tile_mem_write, pt_tile_mem_flush);
-
- break;
-
- default:
- FATAL("tile->out_type: %d", tile->out_type);
- }
-
- // render tile
- if ((err = pt_cache_tile_png(tile->cache, png, info, &tile->info)))
- JUMP_ERROR(err);
-
- // done
- png_write_end(png, info);
-
-error:
- // cleanup
- png_destroy_write_struct(&png, &info);
-
- return err;
+ return pt_cache_tile(tile->cache, tile);
}
void pt_tile_abort (struct pt_tile *tile)
--- a/src/lib/tile.h Sun Jan 24 23:20:39 2010 +0200
+++ b/src/lib/tile.h Mon Jan 25 01:41:25 2010 +0200
@@ -2,8 +2,12 @@
#define PNGTILE_TILE_H
/**
+ * @file
* Generating PNG tiles from a cache
*/
+
+struct pt_tile;
+
#include "pngtile.h"
#include "cache.h"
@@ -42,6 +46,12 @@
};
/**
+ * Write to the tile's output buffer
+ */
+int pt_tile_mem_write (struct pt_tile_mem *buf, void *data, size_t len);
+
+
+/**
* Alloc a new pt_tile, which must be initialized using pt_tile_init_*
*/
int pt_tile_new (struct pt_tile **tile_ptr);
--- a/src/util/main.c Sun Jan 24 23:20:39 2010 +0200
+++ b/src/util/main.c Mon Jan 25 01:41:25 2010 +0200
@@ -15,6 +15,7 @@
{ "verbose", false, NULL, 'v' },
{ "debug", false, NULL, 'D' },
{ "force-update", false, NULL, 'U' },
+ { "no-update", false, NULL, 'N' },
{ "background", true, NULL, 'B' },
{ "width", true, NULL, 'W' },
{ "height", true, NULL, 'H' },
@@ -39,6 +40,7 @@
"\t-v, --verbose display more informational output\n"
"\t-D, --debug equivalent to -v\n"
"\t-U, --force-update unconditionally update image caches\n"
+ "\t-N, --no-update do not update the image cache\n"
"\t-B, --background set background pattern for cache update\n"
"\t-W, --width set tile width\n"
"\t-H, --height set tile height\n"
@@ -52,14 +54,14 @@
int main (int argc, char **argv)
{
int opt;
- bool force_update = false;
+ bool force_update = false, no_update = false;
struct pt_tile_info ti = {0, 0, 0, 0, 0};
struct pt_image_params update_params = { };
int threads = 2;
int tmp, err;
// parse arguments
- while ((opt = getopt_long(argc, argv, "hqvDUB:W:H:x:y:z:j:", options, NULL)) != -1) {
+ while ((opt = getopt_long(argc, argv, "hqvDUNB:W:H:x:y:z:j:", options, NULL)) != -1) {
switch (opt) {
case 'h':
// display help
@@ -85,14 +87,20 @@
force_update = true;
break;
+
+ case 'N':
+ // supress update of image caches
+ no_update = true;
+
+ break;
case 'B':
// background pattern
{
- unsigned int b1, b2, b3, b4;
+ unsigned int b1 = 0, b2 = 0, b3 = 0, b4 = 0;
// parse 0xXXXXXXXX
- if (sscanf(optarg, "0x%02x%02x%02x%02x", &b1, &b2, &b3, &b4) != 4)
+ if (sscanf(optarg, "0x%02x%02x%02x%02x", &b1, &b2, &b3, &b4) < 1)
FATAL("Invalid hex value for -B/--background: %s", optarg);
// store
@@ -182,33 +190,41 @@
else if (status == PT_CACHE_STALE)
log_debug("\tImage cache is stale");
+
+ else if (status == PT_CACHE_INCOMPAT)
+ log_debug("\tImage cache is incompatible");
else if (status == PT_CACHE_FRESH)
log_debug("\tImage cache is fresh");
+
+ if (!no_update) {
+ log_debug("\tUpdating image cache...");
- log_debug("\tUpdating image cache...");
+ if ((err = pt_image_update(image, &update_params))) {
+ log_warn_errno("pt_image_update: %s: %s", img_path, pt_strerror(err));
+ }
- if ((err = pt_image_update(image, &update_params))) {
- log_warn_errno("pt_image_update: %s: %s", img_path, pt_strerror(err));
+ log_info("\tUpdated image cache");
+
+ } else {
+ log_warn("\tSupressing cache update");
}
- log_info("\tUpdated image cache");
-
- } else {
+ } else {
log_debug("\tImage cache is fresh");
}
// show info
- const struct pt_image_info *img_info;
+ const struct pt_image_info *info;
- if ((err = pt_image_info(image, &img_info))) {
+ if ((err = pt_image_info(image, &info))) {
log_warn_errno("pt_image_info: %s: %s", img_path, pt_strerror(err));
} else {
- log_info("\tImage dimensions: %zux%zu", img_info->width, img_info->height);
- log_info("\tImage mtime=%u, bytes=%zu", img_info->image_mtime, img_info->image_bytes);
+ log_info("\tImage dimensions: %zux%zu", info->img_width, info->img_height);
+ log_info("\tImage mtime=%u, bytes=%zu", info->image_mtime, info->image_bytes);
log_info("\tCache mtime=%u, bytes=%zu, blocks=%zu (%zu bytes)",
- img_info->cache_mtime, img_info->cache_bytes, img_info->cache_blocks, img_info->cache_blocks * 512
+ info->cache_mtime, info->cache_bytes, info->cache_blocks, info->cache_blocks * 512
);
}