terom@133: from libc.errno cimport ( terom@133: errno, terom@133: ) terom@133: from libc.string cimport ( terom@133: strerror, terom@133: memset, terom@133: memcpy, terom@133: ) terom@133: from libc.stdio cimport ( terom@133: FILE, terom@133: ) terom@133: from libc.stdlib cimport ( terom@133: free, terom@133: ) terom@133: from cpython.string cimport ( terom@133: PyString_FromStringAndSize, terom@133: ) terom@133: from pypngtile cimport * terom@30: terom@30: cdef extern from "Python.h" : terom@30: int PyFile_Check (object p) terom@133: FILE* PyFile_AsFile (object p) terom@30: terom@78: ## constants terom@78: # Image() terom@78: OPEN_READ = PT_OPEN_READ terom@78: OPEN_UPDATE = PT_OPEN_UPDATE terom@78: terom@78: # Image.status -> ... terom@78: CACHE_FRESH = PT_CACHE_FRESH terom@78: CACHE_NONE = PT_CACHE_NONE terom@78: CACHE_STALE = PT_CACHE_STALE terom@78: CACHE_INCOMPAT = PT_CACHE_INCOMPAT terom@30: terom@170: import datetime terom@170: terom@134: class Error (Exception) : terom@91: """ terom@91: Base class for errors raised by pypngtile. terom@91: """ terom@30: terom@91: def __init__ (self, func, err) : terom@91: super(Error, self).__init__("%s: %s: %s" % (func, pt_strerror(err), strerror(errno))) terom@30: terom@30: cdef class Image : terom@78: """ terom@78: An image file on disk (.png) and an associated .cache file. terom@78: terom@78: Open the .png file at the given path using the given mode. terom@78: terom@78: path - filesystem path to .png file terom@78: mode - mode to operate cache in terom@78: OPEN_READ - read-only access to cache terom@78: OPEN_UPDATE - allow .update() terom@78: """ terom@78: terom@30: cdef pt_image *image terom@30: terom@104: # XXX: should really be a pt_image property... terom@104: cdef readonly object path terom@104: terom@78: terom@78: # open the pt_image terom@78: def __cinit__ (self, char *path, int mode = 0) : terom@91: cdef int err terom@104: terom@104: # store terom@104: self.path = path terom@91: terom@91: # open terom@91: with nogil : terom@91: # XXX: I hope use of path doesn't break... terom@91: err = pt_image_open(&self.image, NULL, path, mode) terom@91: terom@91: if err : terom@91: raise Error("pt_image_open", err) terom@78: terom@78: terom@30: def info (self) : terom@78: """ terom@78: Return a dictionary containing various information about the image. terom@78: terom@78: img_width - pixel dimensions of the source image terom@78: img_height only available if the cache was opened terom@78: img_bpp - bits per pixel for the source image terom@78: terom@78: image_mtime - last modification timestamp for source image terom@78: image_bytes - size of source image file in bytes terom@78: terom@78: cache_version - version of cache file available terom@78: cache_mtime - last modification timestamp for cache file terom@78: cache_bytes - size of cache file in bytes terom@78: cache_blocks - size of cache file in disk blocks - 512 bytes / block terom@78: """ terom@78: terom@91: cdef const_image_info_ptr infop terom@91: cdef int err terom@91: terom@91: with nogil : terom@91: err = pt_image_info_(self.image, &infop) terom@91: terom@91: if err : terom@91: raise Error("pt_image_info", err) terom@30: terom@78: # return as a struct terom@91: return infop[0] terom@170: terom@170: def cache_mtime (self) : terom@170: """ terom@170: Return cache's mtime as an UTC datetime. terom@170: """ terom@78: terom@170: info = self.info() terom@170: terom@170: return datetime.datetime.utcfromtimestamp(info['cache_mtime']) terom@78: terom@30: def status (self) : terom@78: """ terom@78: Return a code describing the status of the underlying cache file. terom@78: terom@78: CACHE_FRESH - the cache file exists and is up-to-date terom@78: CACHE_NONE - the cache file does not exist terom@78: CACHE_STALE - the cache file exists, but is older than the source image terom@78: CACHE_INCOMPAT - the cache file exists, but is incompatible with this version of the library terom@78: """ terom@78: terom@91: cdef int ret terom@91: terom@91: with nogil : terom@91: ret = pt_image_status(self.image) terom@91: terom@91: if ret : terom@91: raise Error("pt_image_status", ret) terom@91: terom@91: return ret terom@87: terom@87: def open (self) : terom@87: """ terom@87: Open the underlying cache file for reading, if available. terom@87: """ terom@87: terom@91: cdef int err terom@91: terom@91: with nogil : terom@91: err = pt_image_load(self.image) terom@91: terom@91: if err : terom@91: raise Error("pt_image_load", err) terom@78: terom@78: terom@78: def update (self, background_color = None) : terom@78: """ terom@78: Update the underlying cache file from the source image. terom@78: terom@78: background_color - skip consecutive pixels that match this byte pattern in output terom@78: terom@78: Requires that the Image was opened using OPEN_UPDATE. terom@78: """ terom@78: terom@78: cdef pt_image_params params terom@78: cdef char *bgcolor terom@91: cdef int err terom@91: terom@78: memset(¶ms, 0, sizeof(params)) terom@78: terom@78: # params terom@78: if background_color : terom@78: # cast terom@78: bgcolor = background_color terom@78: terom@78: if 0 >= len(bgcolor) > 4 : terom@78: raise ValueError("background_color must be a str of between 1 and 4 bytes") terom@78: terom@78: # decode terom@78: memcpy(params.background_color, bgcolor, len(bgcolor)) terom@30: terom@78: # run update terom@91: with nogil : terom@91: err = pt_image_update(self.image, ¶ms) terom@91: terom@91: if err : terom@91: raise Error("pt_image_update", err) terom@30: terom@78: terom@34: def tile_file (self, size_t width, size_t height, size_t x, size_t y, int zoom, object out) : terom@83: """ terom@83: Render a region of the source image as a PNG tile to the given output file. terom@83: terom@83: width - dimensions of the output tile in px terom@83: height terom@83: x - coordinates in the source file terom@83: y terom@83: zoom - zoom level: out = 2**(-zoom) * in terom@83: out - output file terom@83: terom@133: Note that the given file object MUST be a *real* FILE*, not a fake Python object. terom@83: """ terom@83: terom@133: cdef FILE *outf terom@30: cdef pt_tile_info ti terom@91: cdef int err terom@30: terom@83: memset(&ti, 0, sizeof(ti)) terom@83: terom@83: # convert to FILE terom@30: if not PyFile_Check(out) : terom@30: raise TypeError("out: must be a file object") terom@30: terom@30: outf = PyFile_AsFile(out) terom@30: terom@30: if not outf : terom@30: raise TypeError("out: must have a FILE*") terom@83: terom@83: # pack params terom@30: ti.width = width terom@30: ti.height = height terom@30: ti.x = x terom@30: ti.y = y terom@34: ti.zoom = zoom terom@30: terom@83: # render terom@91: with nogil : terom@91: err = pt_image_tile_file(self.image, &ti, outf) terom@91: terom@91: if err : terom@91: raise Error("pt_image_tile_file", err) terom@30: terom@78: terom@34: def tile_mem (self, size_t width, size_t height, size_t x, size_t y, int zoom) : terom@83: """ terom@83: Render a region of the source image as a PNG tile, and return the PNG data a a string. terom@83: terom@83: width - dimensions of the output tile in px terom@83: height terom@83: x - coordinates in the source file terom@83: y terom@83: zoom - zoom level: out = 2**(-zoom) * in terom@83: """ terom@83: terom@30: cdef pt_tile_info ti terom@30: cdef char *buf terom@30: cdef size_t len terom@91: cdef int err terom@83: terom@83: memset(&ti, 0, sizeof(ti)) terom@83: terom@83: # pack params terom@30: ti.width = width terom@30: ti.height = height terom@30: ti.x = x terom@30: ti.y = y terom@34: ti.zoom = zoom terom@30: terom@83: # render and return via buf/len terom@91: with nogil : terom@91: err = pt_image_tile_mem(self.image, &ti, &buf, &len) terom@91: terom@91: if err : terom@91: raise Error("pt_image_tile_mem", err) terom@30: terom@30: # copy buffer as str... terom@133: data = PyString_FromStringAndSize(buf, len) terom@30: terom@30: # drop buffer... terom@133: free(buf) terom@30: terom@30: return data terom@30: terom@78: # release the pt_image terom@30: def __dealloc__ (self) : terom@30: if self.image : terom@30: pt_image_destroy(self.image) terom@30: terom@83: self.image = NULL terom@83: