diff --git a/ChangeLog b/ChangeLog index 7474138a..c1115974 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,4 @@ -7/5/15 started 8.1.0 +7/5/15 starteld 8.1.0 - add vips_premultiply(), vips_unpremultiply() - change the alpha range rules for vips_flatten() to match vips_premultiply() - vipsthumbnail uses vips_resize() rather than its own code @@ -17,6 +17,7 @@ - can now set any jpeg exif tag, not just modify existing tags - add vips_hist_entropy() - vips_log(), vips_log10() are zero-avoiding +- better overlap handling for dzsave, thanks robclouth 7/5/15 started 8.0.3 - dzsave and tif pyr write could fail for some image dimensions, thanks Jonas diff --git a/libvips/foreign/dzsave.c b/libvips/foreign/dzsave.c index 8986eb1d..c77594f8 100644 --- a/libvips/foreign/dzsave.c +++ b/libvips/foreign/dzsave.c @@ -53,6 +53,8 @@ * - use a better temp dir name for fs dz output * 8/8/15 * - allow zip > 4gb if we have a recent libgsf + * 9/9/15 + * - better overlap handling, thanks robclouth */ /* @@ -375,8 +377,7 @@ struct _Layer { */ VipsImage *image; - /* The top of this strip of tiles, excluding the overlap. Go up from - * this to get to the top pixel we write in each one. + /* The top of this strip of tiles. */ int y; @@ -414,6 +415,11 @@ struct _VipsForeignSaveDz { Layer *layer; /* x2 shrink pyr layer */ + /* We step by tile_size - overlap as we move across the image ... + * make a note of it. + */ + int tile_step; + /* Count zoomify tiles we write. */ int tile_count; @@ -481,7 +487,8 @@ vips_foreign_save_dz_dispose( GObject *gobject ) VIPS_FREE( dz->root_name ); VIPS_FREE( dz->file_suffix ); - G_OBJECT_CLASS( vips_foreign_save_dz_parent_class )->dispose( gobject ); + G_OBJECT_CLASS( vips_foreign_save_dz_parent_class )-> + dispose( gobject ); } /* Build a pyramid. @@ -503,8 +510,8 @@ pyramid_build( VipsForeignSaveDz *dz, Layer *above, layer->width = width; layer->height = height; - layer->tiles_across = ROUND_UP( width, dz->tile_size ) / dz->tile_size; - layer->tiles_down = ROUND_UP( height, dz->tile_size ) / dz->tile_size; + layer->tiles_across = ROUND_UP( width, dz->tile_step ) / dz->tile_step; + layer->tiles_down = ROUND_UP( height, dz->tile_step ) / dz->tile_step; layer->real_pixels = *real_pixels; @@ -543,8 +550,7 @@ pyramid_build( VipsForeignSaveDz *dz, Layer *above, vips__region_no_ownership( layer->strip ); vips__region_no_ownership( layer->copy ); - /* Build a line of tiles here. Normally strips are height + 2 * - * overlap, but the first row is missing the top edge. + /* Build a line of tiles here. * * Expand the strip if necessary to make sure we have an even * number of lines. @@ -554,7 +560,7 @@ pyramid_build( VipsForeignSaveDz *dz, Layer *above, strip.left = 0; strip.top = 0; strip.width = layer->image->Xsize; - strip.height = dz->tile_size + dz->overlap; + strip.height = dz->tile_size; if( (strip.height & 1) == 1 ) strip.height += 1; if( vips_region_buffer( layer->strip, &strip ) ) { @@ -638,7 +644,8 @@ write_dzi( VipsForeignSaveDz *dz ) if( (p = (char *) vips__find_rightmost_brackets( buf )) ) *p = '\0'; - gsf_output_printf( out, "\n" ); + gsf_output_printf( out, "\n" ); gsf_output_printf( out, "height; line.left = 0; - line.top = layer->y - dz->overlap; + line.top = layer->y; line.width = image.width; - line.height = dz->tile_size + 2 * dz->overlap; + line.height = dz->tile_size; vips_rect_intersectrect( &image, &line, &line ); @@ -963,16 +970,16 @@ strip_allocate( VipsThreadState *state, void *a, gboolean *stop ) /* Position this tile. */ - state->pos.left = strip->x - dz->overlap; - state->pos.top = 0; - state->pos.width = dz->tile_size + 2 * dz->overlap; - state->pos.height = state->im->Ysize; + state->pos.left = strip->x; + state->pos.top = layer->y; + state->pos.width = dz->tile_size; + state->pos.height = dz->tile_size; vips_rect_intersectrect( &image, &state->pos, &state->pos ); state->x = strip->x; state->y = layer->y; - strip->x += dz->tile_size; + strip->x += dz->tile_step; if( vips_rect_isempty( &state->pos ) ) { *stop = TRUE; @@ -1055,6 +1062,10 @@ tile_name( Layer *layer, int x, int y ) return( NULL ); } +#ifdef DEBUG_VERBOSE + printf( "tile_name: writing to %s\n", name ); +#endif /*DEBUG_VERBOSE*/ + return( out ); } @@ -1124,10 +1135,6 @@ strip_work( VipsThreadState *state, void *a ) x = t; } -#ifdef DEBUG_VERBOSE - printf( "strip_work: writing to %s\n", buf ); -#endif /*DEBUG_VERBOSE*/ - vips_image_set_int( x, "hide-progress", 1 ); if( vips_image_write_to_buffer( x, dz->suffix, &buf, &len, NULL ) ) { g_object_unref( x ); @@ -1140,7 +1147,7 @@ strip_work( VipsThreadState *state, void *a ) g_mutex_lock( vips__global_lock ); out = tile_name( layer, - state->x / dz->tile_size, state->y / dz->tile_size ); + state->x / dz->tile_step, state->y / dz->tile_step ); status = gsf_output_write( out, len, buf ); dz->bytes_written += len; @@ -1201,6 +1208,10 @@ strip_save( Layer *layer ) } strip_free( &strip ); +#ifdef DEBUG + printf( "strip_save: success\n" ); +#endif /*DEBUG*/ + return( 0 ); } @@ -1272,6 +1283,11 @@ strip_shrink( Layer *layer ) VipsRect target; VipsRect source; +#ifdef DEBUG + printf( "strip_shrink: %d lines in layer %d to layer %d\n", + from->valid.height, layer->n, below->n ); +#endif/*DEBUG*/ + /* We may have an extra column of pixels on the right or * bottom that need filling: generate them. */ @@ -1336,7 +1352,7 @@ strip_shrink( Layer *layer ) * * - write a line of tiles * - shrink what we can to the layer below - * - move our strip down by the tile height + * - move our strip down by the tile step * - copy the overlap with the previous strip */ static int @@ -1348,6 +1364,11 @@ strip_arrived( Layer *layer ) VipsRect overlap; VipsRect image_area; +#ifdef DEBUG + printf( "strip_arrived: layer %d, strip at %d, height %d\n", + layer->n, layer->y, layer->strip->valid.height ); +#endif/*DEBUG*/ + if( strip_save( layer ) ) return( -1 ); @@ -1360,11 +1381,11 @@ strip_arrived( Layer *layer ) * Expand the strip if necessary to make sure we have an even * number of lines. */ - layer->y += dz->tile_size; + layer->y += dz->tile_step; new_strip.left = 0; - new_strip.top = layer->y - dz->overlap; + new_strip.top = layer->y; new_strip.width = layer->image->Xsize; - new_strip.height = dz->tile_size + 2 * dz->overlap; + new_strip.height = dz->tile_size; image_area.left = 0; image_area.top = 0; @@ -1534,6 +1555,9 @@ vips_foreign_save_dz_build( VipsObject *object ) save->ready = z; } + /* How much we step by as we write tiles. + */ + dz->tile_step = dz->tile_size - dz->overlap; /* The real pixels we have from our input. This is about to get * expanded with background. diff --git a/test/test_foreign.py b/test/test_foreign.py index 19b1de86..f1d0658d 100755 --- a/test/test_foreign.py +++ b/test/test_foreign.py @@ -4,6 +4,7 @@ from __future__ import division import unittest import math import os +import shutil #import logging #logging.basicConfig(level = logging.DEBUG) @@ -345,6 +346,115 @@ class TestForeign(unittest.TestCase): def test_rad(self): self.save_load("%s.hdr", self.colour) + def test_dzsave(self): + x = Vips.type_find("VipsForeign", "dzsave") + if not x.is_instantiatable(): + print("no dzsave support in this vips, skipping test") + return + + # dzsave is hard to test, there are so many options + # test each option separately and hope they all function together + # correctly + + # default deepzoom layout + self.colour.dzsave("test") + + # test right edge ... default is 256x256 tiles, overlap 1 + x = Vips.Image.new_from_file("test_files/10/3_2.jpeg") + self.assertEqual(x.width, 256) + y = Vips.Image.new_from_file("test_files/10/4_2.jpeg") + self.assertEqual(y.width, + self.colour.width - 255 * int(self.colour.width / 255)) + + # test bottom edge + x = Vips.Image.new_from_file("test_files/10/3_2.jpeg") + self.assertEqual(x.height, 256) + y = Vips.Image.new_from_file("test_files/10/3_3.jpeg") + self.assertEqual(y.height, + self.colour.height - 255 * int(self.colour.height / 255)) + + # there should be a bottom layer + x = Vips.Image.new_from_file("test_files/0/0_0.jpeg") + self.assertEqual(x.width, 1) + self.assertEqual(x.height, 1) + + # 10 should be the final layer + self.assertFalse(os.path.isdir("test_files/11")) + + shutil.rmtree("test_files") + os.unlink("test.dzi") + + # default google layout + self.colour.dzsave("test", layout = "google") + + # test bottom-right tile ... default is 256x256 tiles, overlap 0 + x = Vips.Image.new_from_file("test/2/2/3.jpg") + self.assertEqual(x.width, 256) + self.assertEqual(x.height, 256) + self.assertFalse(os.path.exists("test/2/2/4.jpg")) + self.assertFalse(os.path.exists("test/3")) + x = Vips.Image.new_from_file("test/blank.png") + self.assertEqual(x.width, 256) + self.assertEqual(x.height, 256) + + shutil.rmtree("test") + + # default zoomify layout + self.colour.dzsave("test", layout = "zoomify") + + # 256x256 tiles, no overlap + self.assertTrue(os.path.exists("test/ImageProperties.xml")) + x = Vips.Image.new_from_file("test/TileGroup0/2-3-2.jpg") + self.assertEqual(x.width, 256) + self.assertEqual(x.height, 256) + + shutil.rmtree("test") + + # test zip output + self.colour.dzsave("test.zip") + self.assertFalse(os.path.exists("test_files")) + self.assertFalse(os.path.exists("test.dzi")) + os.unlink("test.zip") + + # test suffix + self.colour.dzsave("test", suffix = ".png") + + x = Vips.Image.new_from_file("test_files/10/3_2.png") + self.assertEqual(x.width, 256) + + shutil.rmtree("test_files") + os.unlink("test.dzi") + + # test overlap + self.colour.dzsave("test", overlap = 200) + + y = Vips.Image.new_from_file("test_files/10/18_6.jpeg") + self.assertEqual(y.width, + self.colour.width - 56 * int(self.colour.width / 56)) + + shutil.rmtree("test_files") + os.unlink("test.dzi") + + # test tile-size + self.colour.dzsave("test", tile_size = 512) + + y = Vips.Image.new_from_file("test_files/10/2_1.jpeg") + self.assertEqual(y.width, + self.colour.width - 511 * int(self.colour.width / 511)) + + shutil.rmtree("test_files") + os.unlink("test.dzi") + + # test tile-size + self.colour.dzsave("test", tile_size = 512) + + y = Vips.Image.new_from_file("test_files/10/2_1.jpeg") + self.assertEqual(y.width, + self.colour.width - 511 * int(self.colour.width / 511)) + + shutil.rmtree("test_files") + os.unlink("test.dzi") + if __name__ == '__main__': unittest.main()