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; }