diff --git a/.gitignore b/.gitignore index acd1ac3..aae6782 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,9 @@ *.jar *.war *.ear -/target +target +.classpath +.project +.settings +*.iml +.idea \ No newline at end of file diff --git a/README.md b/README.md index 933885c..582c6ff 100644 --- a/README.md +++ b/README.md @@ -102,20 +102,20 @@ exported in scale level 0 pixels in xyz orientation (long, 0)
width of exported image tiles in pixels (int, 256)
tileHeight
height of exported image tiles in pixels (int, 256)
-
exportMinZ
+
exportedMinZ
first z-section index to be exported (long, 0)
-
exportMaxZ
+
exportedMaxZ
last z-section index to be exported (long, depth-1)
-
exportMinR
-
first row of tiles to be exported (long, 0)
-
exportMaxR
-
last row of tiles to be exported (long, depth-1)
-
exportMinC
-
first column of tiles to be exported (long, 0)
-
exportMaxC
-
last column of tiles to be exported (long, depth-1)
+
exportedMinX
+
first X in level 0 pixel coordinates to be exported (long, 0)
+
exportedMaxX
+
last X in level 0 pixel coordinates to be exported (long, width-1)
+
exportedMinY
+
first Y in level 0 pixel coordinates to be exported (long, 0)
+
exportedMaxY
+
last Y in level 0 pixel coordinates to be exported (long, depth-1)
exportBasePath
-
base path for the stakc to be exported (string, "")
+
base path for the stack to be exported (string, "")
tilePattern
tilePattern the file name convention for export tile coordinates without extension and base path, must contain "<s>","<z>", "<r>", @@ -155,7 +155,7 @@ contains all parameters in key=value rows (escaped according to Bash's needs). depth=100 orientation=xy exportBasePath=/var/www/catmaid/test/xy/ - tilePattern="/__" + tilePattern="%5$d/%8$d_%9$d_%1$d.jpg" format=jpg quality=0.85 type=gray diff --git a/pom.xml b/pom.xml index 0a6cf5e..3de7c81 100644 --- a/pom.xml +++ b/pom.xml @@ -22,11 +22,17 @@ imglib2 2.0.0-SNAPSHOT - + net.imglib2 imglib2-realtransform 2.0.0-SNAPSHOT + + com.google.guava + guava + 19.0 + + install @@ -35,8 +41,8 @@ maven-compiler-plugin 2.5 - 1.6 - 1.6 + 1.7 + 1.7 diff --git a/src/main/java/org/catmaid/CATMAIDRandomAccessibleInterval.java b/src/main/java/org/catmaid/CATMAIDRandomAccessibleInterval.java index 72002eb..0154fb8 100644 --- a/src/main/java/org/catmaid/CATMAIDRandomAccessibleInterval.java +++ b/src/main/java/org/catmaid/CATMAIDRandomAccessibleInterval.java @@ -39,13 +39,15 @@ import java.awt.image.BufferedImage; import java.awt.image.PixelGrabber; -import java.io.IOException; -import java.lang.ref.SoftReference; import java.net.URL; -import java.util.HashMap; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; import javax.imageio.ImageIO; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; + import net.imglib2.AbstractInterval; import net.imglib2.AbstractLocalizable; import net.imglib2.Interval; @@ -122,30 +124,17 @@ public int hashCode() { return ( int )( value ^ ( value >>> 32 ) ); } } - + class Entry { - final protected Key key; - final protected int[] data; + final int[] data; - public Entry( final Key key, final int[] data ) + public Entry( final int[] data ) { - this.key = key; this.data = data; } - - @Override - public void finalize() - { - synchronized ( cache ) - { -// System.out.println( "finalizing..." ); - cache.remove( key ); -// System.out.println( cache.size() + " tiles chached." ); - } - } } - + public class CATMAIDRandomAccess extends AbstractLocalizable implements RandomAccess< ARGBType > { protected long r, c; @@ -590,14 +579,15 @@ public CATMAIDRandomAccess copyRandomAccess() return copy(); } } - - final protected HashMap< Key, SoftReference< Entry > > cache = new HashMap< CATMAIDRandomAccessibleInterval.Key, SoftReference< Entry > >(); - final protected String urlFormat; - final protected long rows, cols, s; - final protected int tileWidth, tileHeight; - final protected double scale; - - + + private static final int MAX_CACHE_SIZE = 2048; + + final private Cache< Key, Entry > tileCache; + final private String urlFormat; + final private long rows, cols, s; + final private int tileWidth, tileHeight; + final private double scale; + public CATMAIDRandomAccessibleInterval( final String urlFormat, final long width, @@ -605,7 +595,8 @@ public CATMAIDRandomAccessibleInterval( final long depth, final long s, final int tileWidth, - final int tileHeight ) + final int tileHeight, + final int cacheSize ) { super( 3 ); this.urlFormat = urlFormat; @@ -618,8 +609,12 @@ public CATMAIDRandomAccessibleInterval( max[ 0 ] = ( long )( width * scale ) - 1; max[ 1 ] = ( long )( height * scale ) - 1; max[ 2 ] = depth - 1; + tileCache = CacheBuilder.newBuilder() + .maximumSize(cacheSize > 0 ? cacheSize : MAX_CACHE_SIZE) + .weakValues() + .build(); } - + @Override public int numDimensions() { @@ -646,6 +641,7 @@ protected int[] fetchPixels( final long r, final long c, final long z ) } catch ( final OutOfMemoryError e ) { + System.err.println("Out of memory error while fetching tile (" + c + "," + r + "," + z + "). Trying to recover"); System.gc(); return fetchPixels2( r, c, z ); } @@ -653,50 +649,38 @@ protected int[] fetchPixels( final long r, final long c, final long z ) protected int[] fetchPixels2( final long r, final long c, final long z ) { + Entry tileEntry = null; final Key key = new Key( r, c, z ); - synchronized ( cache ) - { - final SoftReference< Entry > cachedReference = cache.get( key ); - if ( cachedReference != null ) - { - final Entry cachedEntry = cachedReference.get(); - if ( cachedEntry != null ) - return cachedEntry.data; - } - - final String urlString = String.format( urlFormat, s, scale, c * tileWidth, r * tileHeight, z, tileWidth, tileHeight, r, c ); + try { + tileEntry = tileCache.get(key, new Callable() { + @Override + public Entry call() { + final String urlString = String.format( urlFormat, s, scale, c * tileWidth, r * tileHeight, z, tileWidth, tileHeight, r, c ); + final int[] pixels = new int[ tileWidth * tileHeight ]; + try { + System.out.println( "Load s=" + s + " r=" + r + " c=" + c + " z=" + z + " url(" + urlString + ")" ); + final URL url = new URL( urlString ); + final BufferedImage jpg = ImageIO.read( url ); - final int[] pixels = new int[ tileWidth * tileHeight ]; - try - { - final URL url = new URL( urlString ); -// final Image image = toolkit.createImage( url ); - final BufferedImage jpg = ImageIO.read( url ); - - /* This gymnastic is necessary to get reproducible gray - * values, just opening a JPG or PNG, even when saved by - * ImageIO, and grabbing its pixels results in gray values - * with a non-matching gamma transfer function, I cannot tell - * why... */ - final BufferedImage image = new BufferedImage( tileWidth, tileHeight, BufferedImage.TYPE_INT_RGB ); - image.createGraphics().drawImage( jpg, 0, 0, null ); - final PixelGrabber pg = new PixelGrabber( image, 0, 0, tileWidth, tileHeight, pixels, 0, tileWidth ); - pg.grabPixels(); - - cache.put( key, new SoftReference< Entry >( new Entry( key, pixels ) ) ); -// System.out.println( "success loading r=" + r + " c=" + c + " url(" + urlString + ")" ); - - } - catch (final IOException e) - { - System.out.println( "failed loading r=" + r + " c=" + c + " url(" + urlString + ")" ); - cache.put( key, new SoftReference< Entry >( new Entry( key, pixels ) ) ); - } - catch (final InterruptedException e) - { - e.printStackTrace(); - } - return pixels; + /* This gymnastic is necessary to get reproducible gray + * values, just opening a JPG or PNG, even when saved by + * ImageIO, and grabbing its pixels results in gray values + * with a non-matching gamma transfer function, I cannot tell + * why... */ + final BufferedImage image = new BufferedImage( tileWidth, tileHeight, BufferedImage.TYPE_INT_RGB ); + image.createGraphics().drawImage( jpg, 0, 0, null ); + final PixelGrabber pg = new PixelGrabber( image, 0, 0, tileWidth, tileHeight, pixels, 0, tileWidth ); + pg.grabPixels(); + System.out.println( "Successfully loaded s=" + s + " r=" + r + " c=" + c + " z=" + z + " url(" + urlString + ")" ); + } catch (final Exception e) { + System.out.println( "Failed loading s=" + s + " r=" + r + " c=" + c + " z=" + z + " url(" + urlString + ")" ); + } + return new Entry(pixels); + } + }); + } catch (ExecutionException ee) { + ee.printStackTrace(); } + return tileEntry != null ? tileEntry.data : null; } } diff --git a/src/main/java/org/catmaid/Downsampler.java b/src/main/java/org/catmaid/Downsampler.java index 49e29f9..3d86360 100644 --- a/src/main/java/org/catmaid/Downsampler.java +++ b/src/main/java/org/catmaid/Downsampler.java @@ -98,8 +98,8 @@ final static public void downsampleBytes( final byte[] aPixels, final byte[] bPi } } } - - final static public void downsampleRGB( final int[] aPixels, final int[] bPixels, final int wa, final int ha ) + + final static public boolean downsampleRGB( final int[] aPixels, final int[] bPixels, final int wa, final int ha, int bgValue ) { assert aPixels.length == wa * ha && bPixels.length == wa / 2 * ( ha / 2 ) : "Input dimensions do not match."; @@ -108,15 +108,20 @@ final static public void downsampleRGB( final int[] aPixels, final int[] bPixels final int wb = wa / 2; final int hb = ha / 2; final int nb = hb * wb; - + boolean bresult = false; for ( int ya = 0, yb = 0; yb < nb; ya += wa2, yb += wb ) { final int ya1 = ya + wa; for ( int xa = 0, xb = 0; xb < wb; xa += 2, ++xb ) { final int xa1 = xa + 1; - bPixels[ yb + xb ] = averageColor( ya + xa, ya + xa1, ya1 + xa, ya1 + xa1, aPixels ); + int c = averageColor( ya + xa, ya + xa1, ya1 + xa, ya1 + xa1, aPixels ); + bPixels[ yb + xb ] = c; + if ((c & 0xFFFFFF) != bgValue) { + bresult = true; // return true since at least one pixel is non black + } } } + return bresult; } } diff --git a/src/main/java/org/catmaid/ScaleCATMAID.java b/src/main/java/org/catmaid/ScaleCATMAID.java index 07ee868..aa8d5a3 100644 --- a/src/main/java/org/catmaid/ScaleCATMAID.java +++ b/src/main/java/org/catmaid/ScaleCATMAID.java @@ -16,14 +16,11 @@ */ package org.catmaid; +import java.awt.Color; import java.awt.Graphics2D; import java.awt.Image; import java.awt.image.BufferedImage; import java.awt.image.PixelGrabber; -import java.io.File; -import java.io.IOException; - -import javax.imageio.ImageIO; /** *

A standalone command line application to generate the scale pyramid of an @@ -68,72 +65,72 @@ */ public class ScaleCATMAID { - static protected class Param + static class Param { - public int tileWidth; - public int tileHeight; - public long minZ; - public long maxZ; - public String tileFormat; - public String format; - public float quality; - public int type; + int tileWidth; + int tileHeight; + long minX; + long width; + long minY; + long height; + long minZ; + long maxZ; + String tileFormat; + String format; + float quality; + int type; + boolean ignoreEmptyTiles; + int bgValue; } - - private ScaleCATMAID(){} - - static protected Param parseParameters() - { + + private ScaleCATMAID() {} + + static protected Param parseParameters() { final Param p = new Param(); p.tileWidth = Integer.parseInt( System.getProperty( "tileWidth", "256" ) ); p.tileHeight = Integer.parseInt( System.getProperty( "tileHeight", "256" ) ); - + + p.minX = Long.parseLong( System.getProperty( "minX", "0" ) ); + p.width = Long.parseLong( System.getProperty( "width", "-1") ); + p.minY = Long.parseLong( System.getProperty( "minY", "0" ) ); + p.height = Long.parseLong( System.getProperty( "height", "-1" ) ); p.minZ = Long.parseLong( System.getProperty( "minZ", "0" ) ); p.maxZ = Long.parseLong( System.getProperty( "maxZ", "" + Long.MAX_VALUE ) ); final String basePath = System.getProperty( "basePath", "" ); - p.tileFormat = System.getProperty( "tileFormat", basePath + "%5$d/%8$d_%9$d_%1$d.jpg" ); - - System.out.println( p.tileFormat ); - p.format = System.getProperty( "format", "jpg" ); + p.tileFormat = System.getProperty( "tileFormat", basePath + "%5$d/%8$d_%9$d_%1$d" + "." + p.format); + + System.out.println("Tile pattern: " + p.tileFormat ); + p.quality = Float.parseFloat( System.getProperty( "quality", "0.85" ) ); final String type = System.getProperty( "type", "rgb" ); if ( type.equalsIgnoreCase( "gray" ) || type.equalsIgnoreCase( "grey" ) ) p.type = BufferedImage.TYPE_BYTE_GRAY; else p.type = BufferedImage.TYPE_INT_RGB; - - return p; - } - - final static protected BufferedImage open( - final String path, - final BufferedImage alternative, - final int type ) - { -// System.out.println( path ); - final File file = new File( path ); - if ( file.exists() ) - { - try - { - return ImageIO.read( new File( path ) ); + p.ignoreEmptyTiles = Boolean.valueOf(System.getProperty( "ignoreEmptyTiles")); + if (p.ignoreEmptyTiles) { + if (p.width < 0) { + throw new IllegalArgumentException("Width must be defined when empty files are not generated"); + } else if (p.minX + p.width < 0) { + throw new IllegalArgumentException("Max X value overflow"); } - catch ( final IOException e ) - { - return alternative; + if (p.height < 0) { + throw new IllegalArgumentException("Height must be defined when empty files are not generated"); + } else if (p.minY + p.height < 0) { + throw new IllegalArgumentException("Max Y value overflow"); } } - else - return alternative; + p.bgValue = Integer.valueOf(System.getProperty( "bgValue", "0")); + + return p; } - - + /** * Generate scaled tiles from a range of an existing scale level 0 tile * stack. * - * @param tileFormat format string adfdressing tiles including basePath + * @param tileFormat format string addressing tiles including basePath * @param tileWidth * @param tileHeight * @param minZ the first z-index to be scaled @@ -142,6 +139,7 @@ final static protected BufferedImage open( * @param quality quality for jpg-compression if format is "jpg" * @param type the type of export tiles, e.g. * {@link BufferedImage#TYPE_BYTE_GRAY} + * @param ignoreEmptyTiles - if true don't save empty tiles * * @throws Exception */ @@ -149,116 +147,162 @@ final public static void scale( final String tileFormat, final int tileWidth, final int tileHeight, + final long minX, + final long width, + final long minY, + final long height, final long minZ, final long maxZ, final String format, final float quality, - final int type ) throws Exception + final int type, + final boolean ignoreEmptyTiles, + final int bgValue) throws Exception { final BufferedImage alternative = new BufferedImage( tileWidth, tileHeight, BufferedImage.TYPE_INT_RGB ); - + Graphics2D alternativeG = alternative.createGraphics(); + + Color bgColor = new Color ( bgValue, bgValue, bgValue ); + alternativeG.setPaint( bgColor ); + alternativeG.fillRect ( 0, 0, alternative.getWidth(), alternative.getHeight() ); + final int[] targetPixels = new int[ tileWidth * tileHeight ]; final BufferedImage target = new BufferedImage( tileWidth, tileHeight, BufferedImage.TYPE_INT_RGB ); final BufferedImage sourceImage = new BufferedImage( tileWidth * 2, tileHeight * 2, BufferedImage.TYPE_INT_RGB ); final Graphics2D g = sourceImage.createGraphics(); final int[] sourcePixels = new int[ tileWidth * tileHeight * 4 ]; - + + final long maxY = height > 0 ? minY + height : -1; + final long maxX = width > 0 ? minX + width : -1; Z: for ( long z = minZ; z <= maxZ; ++z ) { System.out.println( "z-index: " + z ); -S: for ( int s = 1; true; ++s ) + boolean proceedY = true; + boolean proceedX = true; +S: for ( int s = 1; proceedX || proceedY; ++s ) { - System.out.println( " scale: " + s ); + System.out.println( "scale: " + s ); final int iScale = 1 << s; final double scale = 1.0 / iScale; - + final int s1 = s - 1; final int iScale1 = 1 << s1; final double scale1 = 1.0 / iScale1; - - boolean proceedY = true; -Y: for ( long y = 0; proceedY; y += tileHeight ) + int nResultTiles = 0; + + proceedY = true; +Y: for ( long y = minY / iScale1; proceedY; y += 2 * tileHeight ) { - final long yt = y / tileHeight; - boolean proceedX = true; - for ( long x = 0; proceedX; x += tileWidth ) + if (maxY > 0 && y >= maxY / iScale1) { + break; + } + proceedX = true; + final long yt = y / (2 * tileHeight); + for ( long x = minX / iScale1; proceedX; x += 2 * tileWidth ) { - final long xt = x / tileWidth; - final Image imp1 = open( + if (maxX > 0 && x >= maxX / iScale1) { + break; + } + nResultTiles++; + final long xt = x / (2 * tileWidth); + final Image imp1 = Util.readTile( String.format( tileFormat, s1, scale1, x * iScale1, y * iScale1, z, tileWidth * iScale1, tileHeight * iScale1, 2 * yt, 2 * xt ), - alternative, - type ); + alternative); - if ( imp1 == alternative ) - if ( x == 0 ) - if ( y == 0 ) - break Z; + if (maxX < 0) { + if (imp1 == alternative) { + if ( x == minX / iScale1 ) + if ( y == minY / iScale1 ) + break Z; + else + continue S; else - continue S; - else - continue Y; - - final Image imp2 = open( + continue Y; + } + } + final Image imp2 = Util.readTile( String.format( tileFormat, s1, scale1, ( x + tileWidth ) * iScale1, y * iScale1, z, tileWidth * iScale1, tileHeight * iScale1, 2 * yt, 2 * xt + 1 ), - alternative, - type ); - - proceedX = imp2 != alternative; - - final Image imp3 = open( + alternative); + + proceedX = maxX >= 0 || imp2 != alternative; + + final Image imp3 = Util.readTile( String.format( tileFormat, s1, scale1, x * iScale1, ( y + tileHeight ) * iScale1, z, tileWidth * iScale1, tileHeight * iScale1, 2 * yt + 1, 2 * xt ), - alternative, - type ); - - proceedY = imp3 != alternative; - - if ( x == 0 && y == 0 && !( proceedX || proceedY) ) + alternative); + + proceedY = maxY >= 0 || imp3 != alternative; + + if (!proceedX && !proceedY && x == minX / iScale1 && y == minY / iScale1) { break S; - - final Image imp4 = open( + } + final Image imp4 = Util.readTile( String.format( tileFormat, s1, scale1, ( x + tileWidth ) * iScale1, ( y + tileHeight ) * iScale1, z, tileWidth * iScale1, tileHeight * iScale1, 2 * yt + 1, 2 * xt + 1 ), - alternative, - type ); - + alternative); + + if (imp1 == alternative && imp2 == alternative && imp3 == alternative && imp4 == alternative) { + continue; + } + g.drawImage( imp1, 0, 0, null ); g.drawImage( imp2, tileWidth, 0, null ); g.drawImage( imp3, 0, tileHeight, null ); g.drawImage( imp4, tileWidth, tileHeight, null ); - + final PixelGrabber pg = new PixelGrabber( sourceImage, 0, 0, tileWidth * 2, tileHeight * 2, sourcePixels, 0, tileWidth * 2 ); pg.grabPixels(); - - Downsampler.downsampleRGB( sourcePixels, targetPixels, tileWidth * 2, tileHeight * 2 ); - - target.getRaster().setDataElements( 0, 0, tileWidth, tileHeight, targetPixels ); - final BufferedImage targetCopy = Util.draw( target, type ); - Util.writeTile( - targetCopy, - String.format( tileFormat, s, scale, x * iScale, y * iScale, z, tileWidth * iScale, tileHeight * iScale, yt, xt ), - format, - quality ); - } + boolean notEmpty = Downsampler.downsampleRGB( sourcePixels, targetPixels, tileWidth * 2, tileHeight * 2, bgColor.getRGB() ); + + if (notEmpty || !ignoreEmptyTiles) { + target.getRaster().setDataElements( 0, 0, tileWidth, tileHeight, targetPixels ); + final BufferedImage targetCopy = Util.draw( target, type ); + Util.writeTile( + targetCopy, + String.format( tileFormat, s, scale, x * iScale, y * iScale, z, tileWidth * iScale, tileHeight * iScale, yt, xt ), + format, + quality ); + } + } // end for x + } // end for y + if (nResultTiles <= 1) { + proceedX = false; + proceedY = false; } - } - } + } // end for s + } // end for z } final static public void scale( final Param p ) throws Exception { - scale( - p.tileFormat, - p.tileWidth, - p.tileHeight, - p.minZ, - p.maxZ, - p.format, - p.quality, - p.type ); + scale(p.tileFormat, + p.tileWidth, + p.tileHeight, + adjustStart(p.minX, p.tileWidth), + adjustSize(p.width, p.tileWidth), + adjustStart(p.minY, p.tileHeight), + adjustSize(p.height, p.tileHeight), + p.minZ, + p.maxZ, + p.format, + p.quality, + p.type, + p.ignoreEmptyTiles, + p.bgValue); + } + + private static final long adjustStart(long index, int tileSize) { + return index / tileSize * tileSize; // make it the start of the corresponding tile + } + + private static final long adjustSize(long size, int tileSize) { + if (size > 0 && size % tileSize > 0) { + return size + tileSize - (size % tileSize); // make it the end of the corresponding tile + } else { + return size; + } } - final static public void main( final String... args ) throws Exception { scale( parseParameters() ); diff --git a/src/main/java/org/catmaid/TileCATMAID.java b/src/main/java/org/catmaid/TileCATMAID.java index 476b45e..d0ecb88 100644 --- a/src/main/java/org/catmaid/TileCATMAID.java +++ b/src/main/java/org/catmaid/TileCATMAID.java @@ -18,7 +18,6 @@ import java.awt.image.BufferedImage; -import net.imglib2.FinalDimensions; import net.imglib2.FinalInterval; import net.imglib2.Interval; import net.imglib2.RandomAccessible; @@ -37,7 +36,6 @@ import org.catmaid.Tiler.Orientation; - /** *

A standalone command line application to export image tiles representing * scale level 0 of the tiled scale pyramids for the CATMAID interface from @@ -112,14 +110,32 @@ *

first z-section index to be exported (long, 0)
*
exportMaxZ
*
last z-section index to be exported (long, depth-1)
+ *
exportMinX
+ *
first X in level 0 pixel coordinates to be exported (long, 0)
+ *
exportMaxX
+ *
last X in level 0 pixel coordinates to be exported (long, width-1)
+ *
exportMinY
+ *
first Y in level 0 pixel coordinates to be exported (long, 0)
+ *
exportMaxY
+ *
last Y in level 0 pixel coordinates to be exported (long, height-1)
*
exportMinR
- *
first row of tiles to be exported (long, 0)
+ *
first row to be exported (long, 0). If defined it takes precedence in defining the target interval otherwise + * it is derived from exportMinY. + *
*
exportMaxR
- *
last row of tiles to be exported (long, depth-1)
+ *
last row to be exported (long, 0). If defined it takes precedence in defining the target interval otherwise + * it is derived from exportMaxY. + *
*
exportMinC
- *
first column of tiles to be exported (long, 0)
+ *
first column to be exported (long, 0). If defined it takes precedence in defining the target interval otherwise + * it is derived from exportMinX. + *
*
exportMaxC
- *
last column of tiles to be exported (long, depth-1)
+ *
last column to be exported (long, 0). If defined it takes precedence in defining the target interval otherwise + * it is derived from exportMaxX. + *
+ * + * *
exportBasePath
*
base path for the stakc to be exported (string, "")
*
tilePattern
@@ -144,61 +160,60 @@ public class TileCATMAID { static public enum Interpolation { NN, NL }; - static protected class Param + static private class Param { /* CATMAID source stack, representing an xyz-orientation */ - public String sourceUrlFormat; - public long sourceWidth; - public long sourceHeight; - public long sourceDepth; - public long sourceScaleLevel; - public int sourceTileWidth; - public int sourceTileHeight; - public double sourceResXY; - public double sourceResZ; - + String sourceUrlFormat; + long sourceWidth; + long sourceHeight; + long sourceDepth; + long sourceScaleLevel; + int sourceTileWidth; + int sourceTileHeight; + double sourceResXY; + double sourceResZ; + /* export */ /* source interval (crop area) in isotropic pixel coordinates */ - public Interval sourceInterval; - public Tiler.Orientation orientation; - public int tileWidth; - public int tileHeight; - public long minZ; - public long maxZ; - public long minR; - public long maxR; - public long minC; - public long maxC; - public String exportPath; - public String tilePattern; - public String format; - public float quality; - public int type; - public TileCATMAID.Interpolation interpolation; + Interval sourceInterval; + Tiler.Orientation orientation; + int tileWidth; + int tileHeight; + long minZ; + long maxZ; + long minR; + long maxR; + long minC; + long maxC; + String exportPath; + String tilePattern; + String format; + float quality; + int type; + boolean ignoreEmptyTiles; + int bgValue; + TileCATMAID.Interpolation interpolation; + int tileCacheSize; } - static protected Param parseParameters() + static private Param parseParameters() { final Param p = new Param(); /* CATMAID source stack */ final String sourceBaseUrl = System.getProperty( "sourceBaseUrl", "" ); p.sourceUrlFormat = System.getProperty( "sourceUrlFormat", sourceBaseUrl + "%5$d/%8$d_%9$d_%1$d.jpg" ); - + p.sourceWidth = Long.parseLong( System.getProperty( "sourceWidth", "0" ) ); p.sourceHeight = Long.parseLong( System.getProperty( "sourceHeight", "0" ) ); p.sourceDepth = Long.parseLong( System.getProperty( "sourceDepth", "0" ) ); p.sourceScaleLevel = Long.parseLong( System.getProperty( "sourceScaleLevel", "0" ) ); - - final int scaleXYDiv = 1 << p.sourceScaleLevel; - + p.sourceTileWidth = Integer.parseInt( System.getProperty( "sourceTileWidth", "256" ) ); p.sourceTileHeight = Integer.parseInt( System.getProperty( "sourceTileHeight", "256" ) ); p.sourceResXY = Double.parseDouble( System.getProperty( "sourceResXY", "1.0" ) ); p.sourceResZ = Double.parseDouble( System.getProperty( "sourceResZ", "1.0" ) ); - - final double scaleZDiv = scaleXYDiv * p.sourceResXY / p.sourceResZ; - + /* export */ final long minX = Long.parseLong( System.getProperty( "minX", "0" ) ); final long minY = Long.parseLong( System.getProperty( "minY", "0" ) ); @@ -206,70 +221,115 @@ static protected Param parseParameters() final long width = Long.parseLong( System.getProperty( "width", "0" ) ); final long height = Long.parseLong( System.getProperty( "height", "0" ) ); final long depth = Long.parseLong( System.getProperty( "depth", "0" ) ); + p.tileWidth = Integer.parseInt( System.getProperty( "tileWidth", "256" ) ); + p.tileHeight = Integer.parseInt( System.getProperty( "tileHeight", "256" ) ); + // sourceInterval is the interval in pixel coordinates at the sourceScaleLevel p.sourceInterval = new FinalInterval( new long[]{ minX, minY, minZ }, new long[]{ minX + width - 1, minY + height - 1, minZ + depth - 1 } ); - final FinalDimensions orientedSourceInterval; final String orientation = System.getProperty( "orientation", "xy" ); - if ( orientation.equalsIgnoreCase( "xz" ) ) + final double scaleXY = (1 << p.sourceScaleLevel); + final double scaleZ = scaleXY * p.sourceResXY / p.sourceResZ; + final long exportMinX = scale(Long.parseLong( System.getProperty( "exportMinX", "0" ) ), scaleXY); + final long exportMinY = scale(Long.parseLong( System.getProperty( "exportMinY", "0" ) ), scaleXY); + final long exportMinZ = scale(Long.parseLong( System.getProperty( "exportMinZ", "0" ) ), scaleZ); + final long exportMaxX = scale(Long.parseLong( System.getProperty( "exportMaxX", + Long.toString(p.sourceInterval.dimension(0)) ) ), scaleXY); + + if (exportMaxX < exportMinX) { + throw new IllegalArgumentException("The end of the X range must be greater than the beginning of the range"); + } + final long exportMaxY = scale(Long.parseLong( System.getProperty( "exportMaxY", + Long.toString(p.sourceInterval.dimension(1)) ) ), scaleXY); + if (exportMaxY < exportMinY) { + throw new IllegalArgumentException("The end of the Y range must be greater than the beginning of the range"); + } + final long exportMaxZ = scale(Long.parseLong( System.getProperty( "exportMaxZ", + Long.toString(p.sourceInterval.dimension(2)) ) ), scaleZ); + if (exportMaxZ < exportMinZ) { + throw new IllegalArgumentException("The end of the Z range must be greater than the beginning of the range"); + } + + final Interval scaledExportInterval = new FinalInterval( + new long[] { exportMinX, exportMinY, exportMinZ }, + new long[] { exportMaxX, exportMaxY, exportMaxZ} + ); + + final Interval orientedScaledInterval; + if ( orientation.equalsIgnoreCase( "xz" ) || orientation.equalsIgnoreCase( "zx" ) ) { p.orientation = Orientation.XZ; - orientedSourceInterval = new FinalDimensions( - p.sourceInterval.dimension( 0 ) / scaleXYDiv, - ( long )( p.sourceInterval.dimension( 2 ) / scaleZDiv ), - p.sourceInterval.dimension( 1 ) / scaleXYDiv ); + orientedScaledInterval = new FinalInterval( + new long[] { + scaledExportInterval.min(0), + scaledExportInterval.min(2), + scaledExportInterval.min(1) + }, + new long[] { + scaledExportInterval.max(0), + scaledExportInterval.max(2), + scaledExportInterval.max(1) + } + ); } - else if ( orientation.equalsIgnoreCase( "zy" ) ) + else if ( orientation.equalsIgnoreCase( "zy" ) || orientation.equalsIgnoreCase( "yz" ) ) { p.orientation = Orientation.ZY; - orientedSourceInterval = new FinalDimensions( - ( long )( p.sourceInterval.dimension( 2 ) / scaleZDiv ), - p.sourceInterval.dimension( 1 ) / scaleXYDiv, - p.sourceInterval.dimension( 0 ) / scaleXYDiv ); + orientedScaledInterval = new FinalInterval( + new long[] { + scaledExportInterval.min(2), + scaledExportInterval.min(1), + scaledExportInterval.min(0) + }, + new long[]{ + scaledExportInterval.max(2), + scaledExportInterval.max(1), + scaledExportInterval.max(0) + } + ); } else { p.orientation = Orientation.XY; - orientedSourceInterval = new FinalDimensions( - p.sourceInterval.dimension( 0 ) / scaleXYDiv, - p.sourceInterval.dimension( 1 ) / scaleXYDiv, - ( long )( p.sourceInterval.dimension( 2 ) / scaleZDiv ) ); + orientedScaledInterval = new FinalInterval(scaledExportInterval); } - - p.tileWidth = Integer.parseInt( System.getProperty( "tileWidth", "256" ) ); - p.tileHeight = Integer.parseInt( System.getProperty( "tileHeight", "256" ) ); - p.minZ = Long.parseLong( System.getProperty( "exportMinZ", "0" ) ); - p.maxZ = Long.parseLong( System.getProperty( - "exportMaxZ", - Long.toString( orientedSourceInterval.dimension( 2 ) - 1 ) ) ); - p.minR = Long.parseLong( System.getProperty( "exportMinR", "0" ) ); - p.maxR = Long.parseLong( System.getProperty( - "exportMaxR", - Long.toString( ( long )Math.ceil( ( double )orientedSourceInterval.dimension( 1 ) / ( double )p.tileHeight ) - 1 ) ) ); - p.minC = Long.parseLong( System.getProperty( "exportMinC", "0" ) ); - p.maxC = Long.parseLong( System.getProperty( - "exportMaxC", - Long.toString( ( long )Math.ceil( ( double )orientedSourceInterval.dimension( 0 ) / ( double )p.tileWidth ) - 1 ) ) ); - + + p.minZ = orientedScaledInterval.min( 2 ); + p.minR = Long.parseLong( System.getProperty( "exportMinR", + Long.toString(( long )(orientedScaledInterval.min( 1 ) / ( double )p.tileHeight )) ) ); + p.minC = Long.parseLong( System.getProperty( "exportMinC", + Long.toString(( long )(orientedScaledInterval.min( 0 ) / ( double )p.tileWidth )) ) ); + p.maxZ = orientedScaledInterval.max( 2 ); + p.maxR = Long.parseLong( System.getProperty( "exportMaxR", + Long.toString(( long )Math.ceil(orientedScaledInterval.max( 1 ) / ( double )p.tileHeight - 1) ) ) ); + p.maxC = Long.parseLong( System.getProperty( "exportMaxC", + Long.toString(( long )Math.ceil(orientedScaledInterval.max( 0 ) / ( double )p.tileWidth - 1) ) ) ); + p.exportPath = System.getProperty( "exportBasePath", "" ); - p.tilePattern = System.getProperty( "tilePattern", "/__" ); p.format = System.getProperty( "format", "jpg" ); + p.tilePattern = System.getProperty( "tilePattern", "%5$d/%8$d_%9$d_%1$d" + "." + p.format); // default is z/row_col_scale.jpg p.quality = Float.parseFloat( System.getProperty( "quality", "0.85" ) ); final String type = System.getProperty( "type", "rgb" ); if ( type.equalsIgnoreCase( "gray" ) || type.equalsIgnoreCase( "grey" ) ) p.type = BufferedImage.TYPE_BYTE_GRAY; else p.type = BufferedImage.TYPE_INT_RGB; - + p.ignoreEmptyTiles = Boolean.valueOf(System.getProperty( "ignoreEmptyTiles")); + p.bgValue = Integer.valueOf(System.getProperty( "bgValue", "0")); + final String interpolation = System.getProperty( "interpolation", "NN" ); - if ( interpolation.equalsIgnoreCase( "nl" ) || interpolation.equalsIgnoreCase( "NL" ) ) + if ( interpolation.equalsIgnoreCase( "NL" ) ) p.interpolation = Interpolation.NL; else p.interpolation = Interpolation.NN; - + p.tileCacheSize = Integer.valueOf(System.getProperty( "tileCacheSize", "0" )); return p; } - + + private static long scale(long val, double scaleFactor) { + return (long) (val / scaleFactor); + } + /** * Create a {@link Tiler} from a CATMAID stack. * @@ -278,20 +338,20 @@ else if ( orientation.equalsIgnoreCase( "zy" ) ) * @param height of scale level 0 in pixels * @param depth of scale level 0 in pixels * @param s scale level to be used, using anything >0 will create an - * accordingly scaled source stack + * accordingly scaled source stack * @param tileWidth * @param tileHeight * @param resXY x,y-resolution - * @param real valued offset in CATMAID scale level 0 pixels + * @param offset in CATMAID scale level 0 pixels * @param resZ z-resolution, what matters is only the ratio * between z- and x,y-resolution to scale the source * to isotropic resolution (if that is desired, you will want to do * it when exporting re-sliced stacks, not when extracting at the * original orientation). - * + * * @return */ - static public Tiler fromCATMAID( + static private Tiler fromCATMAID( final String urlFormat, final long width, final long height, @@ -302,7 +362,8 @@ static public Tiler fromCATMAID( final double resXY, final double resZ, final RealLocalizable offset, - final Interpolation interpolation ) + final Interpolation interpolation, + final int cacheSize ) { final CATMAIDRandomAccessibleInterval catmaidStack = new CATMAIDRandomAccessibleInterval( @@ -312,7 +373,8 @@ static public Tiler fromCATMAID( depth, s, tileWidth, - tileHeight ); + tileHeight, + cacheSize ); /* scale and re-raster */ final double scaleXY = 1.0 / ( 1 << s ); @@ -321,53 +383,49 @@ static public Tiler fromCATMAID( final double offsetX = offset.getDoublePosition( 0 ) * scaleXY; final double offsetY = offset.getDoublePosition( 1 ) * scaleXY; final double offsetZ = offset.getDoublePosition( 2 ) * scaleZ; - + final AffineTransform3D transform = new AffineTransform3D(); transform.set( 1, 0, 0, -offsetX, 0, 1, 0, -offsetY, 0, 0, scaleZ, -offsetZ ); - final RealRandomAccessible< ARGBType > interpolant; + final RealRandomAccessible< ARGBType > interpolatedStack; switch ( interpolation ) { case NL: - interpolant = Views.interpolate( catmaidStack, new NLinearInterpolatorARGBFactory() ); + interpolatedStack = Views.interpolate( catmaidStack, new NLinearInterpolatorARGBFactory() ); break; default: - interpolant = Views.interpolate( catmaidStack, new NearestNeighborInterpolatorFactory< ARGBType >() ); + interpolatedStack = Views.interpolate( catmaidStack, new NearestNeighborInterpolatorFactory< ARGBType >() ); } - final RandomAccessible< ARGBType > scaledInterpolant = RealViews.affine( interpolant, transform ); - final RandomAccessibleInterval< ARGBType > scaled = + final RandomAccessible< ARGBType > scaledInterpolatedStack = RealViews.affine( interpolatedStack, transform ); + final RandomAccessibleInterval< ARGBType > croppedView = Views.interval( - scaledInterpolant, + scaledInterpolatedStack, new FinalInterval( ( long )( scaleXY * width - offsetX ), ( long )( scaleXY * height - offsetY ), ( long )( scaleZ * depth - offsetZ ) ) ); - - return new Tiler( scaled ); + return new Tiler( croppedView ); } - - - - - final static public void main( final String[] args ) throws Exception + + public static void main( final String[] args ) throws Exception { final Param p = parseParameters(); - + System.out.println( "sourceInterval: " + Util.printInterval( p.sourceInterval ) ); - + final RealPoint min = new RealPoint( 3 ); p.sourceInterval.min( min ); - + final int scaleXYDiv = 1 << p.sourceScaleLevel; final double scaleZDiv = scaleXYDiv * p.sourceResXY / p.sourceResZ; - final FinalInterval cropDimensions = new FinalInterval( + final FinalInterval croppedDimensions = new FinalInterval( p.sourceInterval.dimension( 0 ) / scaleXYDiv, p.sourceInterval.dimension( 1 ) / scaleXYDiv, ( long )( p.sourceInterval.dimension( 2 ) / scaleZDiv ) ); - + fromCATMAID( p.sourceUrlFormat, p.sourceWidth, @@ -379,8 +437,9 @@ final static public void main( final String[] args ) throws Exception p.sourceResXY, p.sourceResZ, min, - p.interpolation ).tile( - cropDimensions, + p.interpolation, + p.tileCacheSize ).tile( + croppedDimensions, p.orientation, p.tileWidth, p.tileHeight, @@ -394,6 +453,8 @@ final static public void main( final String[] args ) throws Exception p.tilePattern, p.format, p.quality, - p.type ); + p.type, + p.ignoreEmptyTiles, + p.bgValue); } } diff --git a/src/main/java/org/catmaid/Tiler.java b/src/main/java/org/catmaid/Tiler.java index e8b94f2..fadfac5 100644 --- a/src/main/java/org/catmaid/Tiler.java +++ b/src/main/java/org/catmaid/Tiler.java @@ -26,7 +26,6 @@ import net.imglib2.img.array.ArrayImg; import net.imglib2.img.array.ArrayImgs; import net.imglib2.img.basictypeaccess.array.IntArray; -import net.imglib2.type.Type; import net.imglib2.type.numeric.ARGBType; import net.imglib2.util.Intervals; import net.imglib2.view.Views; @@ -38,7 +37,6 @@ */ public class Tiler { -// final static protected Toolkit toolkit = Toolkit.getDefaultToolkit(); final protected RandomAccessibleInterval< ARGBType > source; @@ -62,15 +60,23 @@ static public enum Orientation * @param sourceTile * @param targetTile */ - final static protected < T extends Type< T > > void copyTile( - final RandomAccessibleInterval< T > sourceTile, - final RandomAccessibleInterval< T > targetTile ) + final static private boolean copyTile( + final RandomAccessibleInterval< ARGBType > sourceTile, + final RandomAccessibleInterval< ARGBType > targetTile, + final ARGBType bg ) { - final Cursor< T > src = Views.flatIterable( sourceTile ).cursor(); - final Cursor< T > dst = Views.flatIterable( targetTile ).cursor(); + final Cursor< ARGBType > src = Views.flatIterable( sourceTile ).cursor(); + final Cursor< ARGBType > dst = Views.flatIterable( targetTile ).cursor(); - while ( src.hasNext() ) - dst.next().set( src.next() ); + boolean hasInfo = false; + while ( src.hasNext() ) { + ARGBType spixel = src.next(); + if ((spixel.get() & 0xFFFFFF) != bg.get()) { + hasInfo = true; + } + dst.next().set(spixel); + } + return hasInfo; } @@ -86,7 +92,7 @@ final static protected < T extends Type< T > > void copyTile( * @param yx * @param bg background value */ - final static protected void copyTile( + final static private boolean copyTile( final RandomAccessibleInterval< ARGBType > sourceTile, final RandomAccessibleInterval< ARGBType > targetTile, final boolean yx, @@ -103,7 +109,7 @@ final static protected void copyTile( for ( final ARGBType p : Views.iterable( targetTile ) ) p.set( bg ); - raiTarget = Views.interval( targetTile, sourceTile ); + raiTarget = Views.interval( targetTile, sourceTile ); } final RandomAccessibleInterval< ARGBType > raiSource; @@ -115,35 +121,9 @@ final static protected void copyTile( else raiSource = sourceTile; - copyTile( raiSource, raiTarget ); + return copyTile( raiSource, raiTarget, bg ); } - - - /** - * Replace the ctile coordinates in a pattern string. - * - * @param template - * @param s scale-index (scale = 1/2s) - * @param z z-index - * @param r row - * @param c column - * @return - */ - final static protected String tileName( - final String template, - final long s, - final long z, - final long r, - final long c ) - { - return template. - replace( "", Long.toString( s ) ). - replace( "", Long.toString( z ) ). - replace( "", Long.toString( r ) ). - replace( "", Long.toString( c ) ); - } - - + /** * Generate a subset of a CATMAID tile stack of an {@link Interval} of the * source {@link RandomAccessibleInterval}. That is you can choose the @@ -166,6 +146,7 @@ final static protected String tileName( * @param format * @param quality * @param type + * @param bgValue background pixel value * @throws IOException */ public void tile( @@ -183,7 +164,9 @@ public void tile( final String tilePattern, final String format, final float quality, - final int type ) throws IOException + final int type, + final boolean ignoreEmptyTiles, + final int bgValue ) throws IOException { /* orientation */ final RandomAccessibleInterval< ARGBType > view; @@ -206,19 +189,16 @@ public void tile( view = source; viewInterval = sourceInterval; } - -// final long maxC = -// final long maxR = ( long ) Math.ceil( ( double ) viewInterval.dimension( 1 ) / ( double ) tileHeight ) - 1; -// final long maxZ = viewInterval.dimension( 2 ) - 1; -// + final long[] min = new long[ 3 ]; final long[] size = new long[ 3 ]; size[ 2 ] = 1; - + final int[] tilePixels = new int[ tileWidth * tileHeight ]; final ArrayImg< ARGBType, IntArray > tile = ArrayImgs.argbs( tilePixels, tileWidth, tileHeight ); final BufferedImage img = new BufferedImage( tileWidth, tileHeight, BufferedImage.TYPE_INT_RGB ); - + final ARGBType bg = new ARGBType( ARGBType.rgba(bgValue, bgValue, bgValue, 0) ); + for ( long z = minZ; z <= maxZ; ++z ) { min[ 2 ] = z + viewInterval.min( 2 ); @@ -235,20 +215,18 @@ public void tile( final RandomAccessibleInterval< ARGBType > sourceTile = Views.hyperSlice( Views.offsetInterval( view, min, size ), 2, 0 ); - copyTile( sourceTile, tile, orientation == Orientation.ZY, new ARGBType( 0 ) ); - img.getRaster().setDataElements( 0, 0, tileWidth, tileHeight, tilePixels ); - final BufferedImage imgCopy = Util.draw( img, type ); - - final String tilePath = - new StringBuffer( exportPath ). - append( "/" ). - append( tileName( tilePattern, 0, z, r, c ) ). - append( "." ). - append( format ). - toString(); - - Util.writeTile( imgCopy, tilePath, format, quality ); -// writePngTile( img, sectionPath + "/" + r + "_" + c + "_0.png" ); + boolean tileIsNotEmpty = copyTile( sourceTile, tile, orientation == Orientation.ZY, bg ); + if (tileIsNotEmpty || !ignoreEmptyTiles) { + img.getRaster().setDataElements(0, 0, tileWidth, tileHeight, tilePixels); + final BufferedImage imgCopy = Util.draw(img, type); + final String tilePath = + new StringBuffer(exportPath != null && exportPath.trim().length() > 0 ? exportPath : "") + .append(exportPath != null && exportPath.trim().length() > 0 ? "/" : "") + .append(String.format(tilePattern, 0, 1.0, min[0], min[1], z, tileWidth, tileHeight, r, c)) + .toString(); + + Util.writeTile(imgCopy, tilePath, format, quality); + } } } } @@ -260,7 +238,7 @@ public void tile( * {@link RandomAccessibleInterval}. That is you can choose the * window to be exported. * - * @param sourceInterval the interval of the source to be exported + * @param sourceInterval the interval of the source to be exported * @param orientation the export orientation * @param tileWidth * @param tileHeight @@ -271,6 +249,7 @@ public void tile( * @param format * @param quality * @param type + * @param bgValue background pixel value * @throws IOException */ public void tile( @@ -282,12 +261,14 @@ public void tile( final String tilePattern, final String format, final float quality, - final int type ) throws IOException + final int type, + final boolean ignoreEmptyTiles, + final int bgValue ) throws IOException { final long maxC; final long maxR; final long maxZ; - + switch ( orientation ) { case XZ: @@ -306,10 +287,6 @@ public void tile( maxZ = sourceInterval.dimension( 2 ) - 1; } -// System.out.println( "maxZ:" + maxZ ); -// System.out.println( "maxR:" + maxR ); -// System.out.println( "maxC:" + maxC ); - tile( sourceInterval, orientation, @@ -325,7 +302,9 @@ public void tile( tilePattern, format, quality, - type ); + type, + ignoreEmptyTiles, + bgValue ); } @@ -343,6 +322,7 @@ public void tile( * @param format * @param quality * @param type + * @param bgValue background pixel value * @throws IOException */ public void tile( @@ -353,7 +333,9 @@ public void tile( final String tilePattern, final String format, final float quality, - final int type ) throws IOException + final int type, + final boolean ignoreEmptyTiles, + final int bgValue ) throws IOException { tile( source, @@ -364,7 +346,9 @@ public void tile( tilePattern, format, quality, - type ); + type, + ignoreEmptyTiles, + bgValue ); } @@ -389,6 +373,7 @@ public void tile( * @param quality quality for jpg-compression if format is "jpg" * @param type the type of export tiles, e.g. * {@link BufferedImage#TYPE_BYTE_GRAY} + * @param bgValue background pixel value * @throws IOException */ public void tile( @@ -405,7 +390,9 @@ public void tile( final String tilePattern, final String format, final float quality, - final int type ) throws IOException + final int type, + final boolean ignoreEmptyTiles, + final int bgValue ) throws IOException { tile( source, @@ -422,6 +409,8 @@ public void tile( tilePattern, format, quality, - type ); + type, + ignoreEmptyTiles, + bgValue ); } } diff --git a/src/main/java/org/catmaid/Util.java b/src/main/java/org/catmaid/Util.java index d01d092..083e39e 100644 --- a/src/main/java/org/catmaid/Util.java +++ b/src/main/java/org/catmaid/Util.java @@ -18,23 +18,29 @@ import java.awt.Image; import java.awt.image.BufferedImage; +import java.io.BufferedReader; import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; import javax.imageio.IIOImage; import javax.imageio.ImageIO; import javax.imageio.ImageWriteParam; import javax.imageio.ImageWriter; -import javax.imageio.stream.FileImageOutputStream; +import javax.imageio.stream.ImageOutputStream; /** * * * @author Stephan Saalfeld */ -public class Util -{ - final static public String tilePath( +class Util { + final static String tilePath( final String tileFormat, final int scaleLevel, final double scale, @@ -44,30 +50,157 @@ final static public String tilePath( final int tileWidth, final int tileHeight, final long row, - final long column ) - { + final long column) { return String.format( tileFormat, scaleLevel, scale, x, y, z, tileWidth, tileHeight, row, column ); } - - final static public BufferedImage draw( + + final static BufferedImage draw( final Image img, - final int type ) - { + final int type) { final BufferedImage imgCopy = new BufferedImage( img.getWidth( null ), img.getHeight( null ), type ); imgCopy.createGraphics().drawImage( img, 0, 0, null ); return imgCopy; } - - final static public void writeTile( + + final static BufferedImage readTile( + final String url, + final BufferedImage alternative) { + if ( url.startsWith("file://") ) { + return readTileFromFile(url.substring("file://".length()), alternative); + } else if ( url.startsWith("http://") || url.startsWith("https://") ) { + return readTileFromUrl(url, alternative); + } else { + return readTileFromFile(url, alternative); + } + } + + final static BufferedImage readTileFromFile( + final String url, + final BufferedImage alternative) { + File f = new File( url ); + if ( f.exists() ) { + try { + return ImageIO.read( new File( url ) ); + } catch ( IOException e ) { + e.printStackTrace(); + } + } + return alternative; + } + + final static BufferedImage readTileFromUrl( + final String urlString, + final BufferedImage alternative) { + HttpURLConnection conn = null; + InputStream connInputStream = null; + try { + final URL url = new URL( urlString ); + conn = (HttpURLConnection) url.openConnection(); + conn.setDoOutput(false); + conn.setDoInput(true); + conn.setRequestMethod("GET"); + int statusCode = conn.getResponseCode(); + if (statusCode != HttpURLConnection.HTTP_OK) { + return alternative; + } + connInputStream = conn.getInputStream(); + return ImageIO.read(connInputStream); + } catch ( IOException e ) { + e.printStackTrace(); + } finally { + if (connInputStream != null) { + try { + connInputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + if (conn != null) { + conn.disconnect(); + } + } + return alternative; + } + + final static void writeTile( final BufferedImage img, - final String path, + final String url, + final String format, + final float quality ) throws IOException + { + if ( url.startsWith("file://") ) { + writeTileToFile(img, url.substring("file://".length()), format, quality); + } else if ( url.startsWith("http://") || url.startsWith("https://") ) { + writeTileToUrl(img, url, format, quality); + } else { + writeTileToFile(img, url, format, quality); + } + } + + final static private void writeTileToFile( + final BufferedImage img, + final String tileFile, + final String format, + final float quality ) throws IOException + { + new File( tileFile ).getParentFile().mkdirs(); + final FileOutputStream os = new FileOutputStream( new File( tileFile ) ); + try { + writeTile(img, os, format, quality); + } finally { + os.close(); + } + } + + final static private void writeTileToUrl( + final BufferedImage img, + final String httpUrl, + final String format, + final float quality ) throws IOException + { + URL url = new URL(httpUrl); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setDoOutput(true); + conn.setRequestMethod("POST"); + conn.setRequestProperty("Content-Type", "application/octet-stream"); + OutputStream os = conn.getOutputStream(); + InputStream is = null; + try { + writeTile(img, os, format, quality); + int statusCode = conn.getResponseCode(); + if (statusCode >= HttpURLConnection.HTTP_BAD_REQUEST) { + System.err.printf("Error response from %s: %d\n", httpUrl, statusCode); + } + is = conn.getInputStream(); + BufferedReader br = new BufferedReader(new InputStreamReader((is))); + String serverOutput; + while ((serverOutput = br.readLine()) != null) { + System.out.println(serverOutput); + } + } catch (IOException e) { + System.err.printf("Error writing to %s %d\n", httpUrl, conn.getExpiration()); + throw e; + } finally { + if (is != null) { + try { + is.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + conn.disconnect(); + } + } + + final static private void writeTile( + final BufferedImage img, + final OutputStream outputStream, final String format, final float quality ) throws IOException { - new File( path ).getParentFile().mkdirs(); final ImageWriter writer = ImageIO.getImageWritersByFormatName( format ).next(); - final FileImageOutputStream output = new FileImageOutputStream( new File( path ) ); - writer.setOutput( output ); + ImageOutputStream ios = ImageIO.createImageOutputStream(outputStream); + writer.setOutput(ios); if ( format.equalsIgnoreCase( "jpg" ) ) { final ImageWriteParam param = writer.getDefaultWriteParam(); @@ -76,9 +209,12 @@ final static public void writeTile( writer.write( null, new IIOImage( img.getRaster(), null, null ), param ); } else + { writer.write( img ); - + } + ios.flush(); + outputStream.flush(); writer.dispose(); - output.close(); } + }