implement zoom
authorTero Marttila <terom@fixme.fi>
Wed, 06 Jan 2010 16:23:35 +0200
changeset 34 a387bc77ad52
parent 33 0ed40e11b0e8
child 35 314aa047a910
implement zoom
include/pngtile.h
pngtile/wsgi.py
python/pypngtile.pyx
src/lib/cache.c
src/lib/error.c
src/util/main.c
static/tiles2.js
--- a/include/pngtile.h	Wed Jan 06 16:05:02 2010 +0200
+++ b/include/pngtile.h	Wed Jan 06 16:23:35 2010 +0200
@@ -63,8 +63,8 @@
     /** Pixel coordinates of top-left corner */
     size_t x, y;
 
-    /** Zoom factor (out < zero < in) */
-    // TODO: int zoom;
+    /** Zoom factor of 2^z (out < zero < in) */
+    int zoom;
 };
 
 /**
@@ -186,6 +186,8 @@
     PT_ERR_PTHREAD_CREATE,
     PT_ERR_CTX_SHUTDOWN,
 
+    PT_ERR_ZOOM,
+
     PT_ERR_MAX,
 };
 
--- a/pngtile/wsgi.py	Wed Jan 06 16:05:02 2010 +0200
+++ b/pngtile/wsgi.py	Wed Jan 06 16:23:35 2010 +0200
@@ -66,7 +66,7 @@
         </div>
 
         <script type="text/javascript">
-            var tile_source = new Source("%(tile_url)s", %(tile_width)d, %(tile_height)d, 0, 0);
+            var tile_source = new Source("%(tile_url)s", %(tile_width)d, %(tile_height)d, -4, 0);
             var main = new Viewport(tile_source, "viewport");
         </script>
     </body>
@@ -79,8 +79,22 @@
         tile_height     = TILE_HEIGHT,
     )
 
-def render_tile (image, x, y) :
-    return image.tile_mem(TILE_WIDTH, TILE_HEIGHT, x, y)
+def scale_by_zoom (val, zoom) :
+    if zoom > 0 :
+        return val << zoom
+
+    elif zoom > 0 :
+        return val >> -zoom
+
+    else :
+        return val
+
+def render_tile (image, x, y, zoom) :
+    return image.tile_mem(
+        TILE_WIDTH, TILE_HEIGHT, 
+        scale_by_zoom(x, -zoom), scale_by_zoom(y, -zoom), 
+        zoom
+    )
 
 def handle_main (req) :
     # path to image
@@ -128,9 +142,10 @@
         # tile
         x = int(req.args['x'])
         y = int(req.args['y'])
+        zoom = int(req.args.get('zoom', "0"))
         
         # yay render
-        return Response(render_tile(image, x, y), content_type="image/png")
+        return Response(render_tile(image, x, y, zoom), content_type="image/png")
    
     else :
         raise exceptions.BadRequest("Unknown args")
--- a/python/pypngtile.pyx	Wed Jan 06 16:05:02 2010 +0200
+++ b/python/pypngtile.pyx	Wed Jan 06 16:23:35 2010 +0200
@@ -36,6 +36,7 @@
     struct pt_tile_info :
         size_t width, height
         size_t x, y
+        int zoom
         
     int pt_image_open (pt_image **image_ptr, pt_ctx *ctx, char *png_path, int cache_mode)
     int pt_image_info_func "pt_image_info" (pt_image *image, pt_image_info **info_ptr)
@@ -90,7 +91,7 @@
             pt_image_update(self.image)
         )
 
-    def tile_file (self, size_t width, size_t height, size_t x, size_t y, object out) :
+    def tile_file (self, size_t width, size_t height, size_t x, size_t y, int zoom, object out) :
         cdef stdio.FILE *outf
         cdef pt_tile_info ti
 
@@ -106,12 +107,13 @@
         ti.height = height
         ti.x = x
         ti.y = y
+        ti.zoom = zoom
         
         trap_err("pt_image_tile_file", 
             pt_image_tile_file(self.image, &ti, outf)
         )
 
-    def tile_mem (self, size_t width, size_t height, size_t x, size_t y) :
+    def tile_mem (self, size_t width, size_t height, size_t x, size_t y, int zoom) :
         cdef pt_tile_info ti
         cdef char *buf
         cdef size_t len
@@ -120,6 +122,7 @@
         ti.height = height
         ti.x = x
         ti.y = y
+        ti.zoom = zoom
         
         # render and return ptr to buffer
         trap_err("pt_image_tile_mem", 
--- a/src/lib/cache.c	Wed Jan 06 16:05:02 2010 +0200
+++ b/src/lib/cache.c	Wed Jan 06 16:23:35 2010 +0200
@@ -493,19 +493,51 @@
     return 0;
 }
 
-int pt_cache_tile_png (struct pt_cache *cache, png_structp png, png_infop info, const struct pt_tile_info *ti)
+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;
 
-    // ensure open
-    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);
-
     // 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
@@ -529,6 +561,104 @@
     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 err;
+
+    // ensure open
+    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);
+
+    else
+        err = write_png_data_unzoomed(cache, png, info, ti);
 
     if (err)
         return err;
--- a/src/lib/error.c	Wed Jan 06 16:05:02 2010 +0200
+++ b/src/lib/error.c	Wed Jan 06 16:23:35 2010 +0200
@@ -30,6 +30,7 @@
     [PT_ERR_CTX_SHUTDOWN]       = "pt_ctx is shutting down",
 
     [PT_ERR_TILE_CLIP]          = "Tile outside of image",
+    [PT_ERR_ZOOM]               = "Invalid zoom level",
 };
 
 const char *pt_strerror (int err)
--- a/src/util/main.c	Wed Jan 06 16:05:02 2010 +0200
+++ b/src/util/main.c	Wed Jan 06 16:23:35 2010 +0200
@@ -19,6 +19,7 @@
     { "height",         true,   NULL,   'H' },
     { "x",              true,   NULL,   'x' },
     { "y",              true,   NULL,   'y' },
+    { "zoom",           true,   NULL,   'z' },
     { "threads",        true,   NULL,   'j' },
     { 0,                0,      0,      0   }
 };
@@ -41,6 +42,7 @@
         "\t-H, --height         set tile height\n"
         "\t-x, --x              set tile x offset\n"
         "\t-y, --y              set tile z offset\n"
+        "\t-z, --zoom           set zoom factor (<0)\n"
         "\t-j, --threads        number of threads\n"
     );
 }
@@ -49,12 +51,12 @@
 {
     int opt;
     bool force_update = false;
-    struct pt_tile_info ti = {0, 0, 0, 0};
+    struct pt_tile_info ti = {0, 0, 0, 0, 0};
     int threads = 2;
     int tmp, err;
     
     // parse arguments
-    while ((opt = getopt_long(argc, argv, "hqvDUW:H:x:y:j:", options, NULL)) != -1) {
+    while ((opt = getopt_long(argc, argv, "hqvDUW:H:x:y:z:j:", options, NULL)) != -1) {
         switch (opt) {
             case 'h':
                 // display help
@@ -93,6 +95,9 @@
             case 'y':
                 ti.y = strtol(optarg, NULL, 0); break;
 
+            case 'z':
+                ti.zoom = strtol(optarg, NULL, 0); break;
+
             case 'j':
                 if ((tmp = strtol(optarg, NULL, 0)) < 1)
                     FATAL("Invalid value for -j/--threads");
--- a/static/tiles2.js	Wed Jan 06 16:05:02 2010 +0200
+++ b/static/tiles2.js	Wed Jan 06 16:23:35 2010 +0200
@@ -26,7 +26,7 @@
         var x = col * this.tile_width;
         var y = row * this.tile_height;
 
-        var url = this.path + "?x=" + x + "&y=" + y + "&z=" + zl + "&sw=" + sw + "&sh=" + sh;
+        var url = this.path + "?x=" + x + "&y=" + y + "&zoom=" + zl + "&sw=" + sw + "&sh=" + sh;
 
         if (this.refresh)
             url += "&ts=" + new Date().getTime();