# HG changeset patch # User Tero Marttila # Date 1264376485 -7200 # Node ID d5e3089906da3096552a194d99a3655a56ad28d6 # Parent a3542e78ecd8c0ab5fd8700326ba0be55d55d220 major refactoring of pt_cache, split off all PNG processing into pt_png diff -r a3542e78ecd8 -r d5e3089906da include/pngtile.h --- 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, diff -r a3542e78ecd8 -r d5e3089906da src/lib/cache.c --- 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; } diff -r a3542e78ecd8 -r d5e3089906da src/lib/cache.h --- 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 #include -#include +/** + * 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. diff -r a3542e78ecd8 -r d5e3089906da src/lib/error.c --- 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", diff -r a3542e78ecd8 -r d5e3089906da src/lib/image.c --- 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 #include -#include - 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) { diff -r a3542e78ecd8 -r d5e3089906da src/lib/image.h --- 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 diff -r a3542e78ecd8 -r d5e3089906da src/lib/png.c --- /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 // sysmtem libpng header +#include + + +#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); +} + diff -r a3542e78ecd8 -r d5e3089906da src/lib/png.h --- /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 +#include + +/** + * 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 diff -r a3542e78ecd8 -r d5e3089906da src/lib/tile.c --- 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 +#include + +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) diff -r a3542e78ecd8 -r d5e3089906da src/lib/tile.h --- 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); diff -r a3542e78ecd8 -r d5e3089906da src/util/main.c --- 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 ); }