From a25fcc0001f7c8e8d13861af74659aeb1e12ceb4 Mon Sep 17 00:00:00 2001 From: Mike Primm Date: Sun, 29 May 2011 01:52:57 -0500 Subject: [PATCH] Create BufferedImage using our own buffer - allows faster pixel writing --- .../java/org/dynmap/CraftChunkSnapshot.java | 3 - src/main/java/org/dynmap/flat/FlatMap.java | 37 +++-- .../dynmap/kzedmap/DefaultTileRenderer.java | 139 ++++++++---------- src/main/java/org/dynmap/kzedmap/KzedMap.java | 62 ++++++-- 4 files changed, 134 insertions(+), 107 deletions(-) diff --git a/src/main/java/org/dynmap/CraftChunkSnapshot.java b/src/main/java/org/dynmap/CraftChunkSnapshot.java index 9d461533..8987eb41 100644 --- a/src/main/java/org/dynmap/CraftChunkSnapshot.java +++ b/src/main/java/org/dynmap/CraftChunkSnapshot.java @@ -20,9 +20,6 @@ public class CraftChunkSnapshot implements ChunkSnapshot { this.z = z; this.buf = buf; this.hmap = hmap; - for(int i = 0; i < 256; i++) - if(hmap[i] < 1) - hmap[i] = 1; } /** diff --git a/src/main/java/org/dynmap/flat/FlatMap.java b/src/main/java/org/dynmap/flat/FlatMap.java index 21011528..d16861ca 100644 --- a/src/main/java/org/dynmap/flat/FlatMap.java +++ b/src/main/java/org/dynmap/flat/FlatMap.java @@ -2,8 +2,10 @@ package org.dynmap.flat; import static org.dynmap.JSONUtils.a; import static org.dynmap.JSONUtils.s; - +import java.awt.image.DataBufferInt; import java.awt.image.BufferedImage; +import java.awt.image.WritableRaster; +import java.awt.image.ColorModel; import java.io.File; import java.io.IOException; @@ -22,6 +24,7 @@ import org.dynmap.MapTile; import org.dynmap.MapType; import org.dynmap.debug.Debug; import org.dynmap.kzedmap.KzedMap; +import org.dynmap.kzedmap.KzedMap.KzedBufferedImage; import org.dynmap.MapChunkCache; import org.json.simple.JSONObject; @@ -109,14 +112,17 @@ public class FlatMap extends MapType { boolean isnether = (w.getEnvironment() == Environment.NETHER) && (maximumHeight == 127); boolean rendered = false; - BufferedImage im = KzedMap.allocateBufferedImage(t.size, t.size); - BufferedImage im_day = null; - if(night_and_day) - im_day = KzedMap.allocateBufferedImage(t.size, t.size); Color rslt = new Color(); int[] pixel = new int[3]; int[] pixel_day = new int[3]; - + KzedBufferedImage im = KzedMap.allocateBufferedImage(t.size, t.size); + int[] argb_buf = im.argb_buf; + KzedBufferedImage im_day = null; + int[] argb_buf_day = null; + if(night_and_day) { + im_day = KzedMap.allocateBufferedImage(t.size, t.size); + argb_buf_day = im_day.argb_buf; + } MapChunkCache.MapIterator mapiter = cache.getIterator(t.x * t.size, 127, t.y * t.size); for (int x = 0; x < t.size; x++) { mapiter.initialize(t.x * t.size + x, 127, t.y * t.size); @@ -144,6 +150,8 @@ public class FlatMap extends MapType { } else { int my = mapiter.getHighestBlockYAt() - 1; + if(my < 0) /* If hole to bottom, all air */ + continue; if(my > maximumHeight) my = maximumHeight; mapiter.setY(my); blockType = mapiter.getBlockTypeID(); @@ -213,38 +221,39 @@ public class FlatMap extends MapType { } rslt.setRGBA(pixel[0], pixel[1], pixel[2], 255); - im.setRGB(t.size-y-1, x, rslt.getARGB()); + argb_buf[(t.size-y-1) + (x*t.size)] = rslt.getARGB(); if(night_and_day) { rslt.setRGBA(pixel_day[0], pixel_day[1], pixel_day[2], 255); - im_day.setRGB(t.size-y-1, x, rslt.getARGB()); + argb_buf_day[(t.size-y-1) + (x*t.size)] = rslt.getARGB(); } rendered = true; } } + /* Wrap buffer as buffered image */ Debug.debug("saving image " + outputFile.getPath()); try { - ImageIO.write(im, "png", outputFile); + ImageIO.write(im.buf_img, "png", outputFile); } catch (IOException e) { Debug.error("Failed to save image: " + outputFile.getPath(), e); } catch (java.lang.NullPointerException e) { Debug.error("Failed to save image (NullPointerException): " + outputFile.getPath(), e); } KzedMap.freeBufferedImage(im); - MapManager.mapman.pushUpdate(tile.getWorld(), - new Client.Tile(tile.getFilename())); + MapManager.mapman.pushUpdate(tile.getWorld(), new Client.Tile(tile.getFilename())); + + /* If day too, handle it */ if(night_and_day) { File dayfile = new File(outputFile.getParent(), tile.getDayFilename()); Debug.debug("saving image " + dayfile.getPath()); try { - ImageIO.write(im_day, "png", dayfile); + ImageIO.write(im_day.buf_img, "png", dayfile); } catch (IOException e) { Debug.error("Failed to save image: " + dayfile.getPath(), e); } catch (java.lang.NullPointerException e) { Debug.error("Failed to save image (NullPointerException): " + dayfile.getPath(), e); } KzedMap.freeBufferedImage(im_day); - MapManager.mapman.pushUpdate(tile.getWorld(), - new Client.Tile(tile.getDayFilename())); + MapManager.mapman.pushUpdate(tile.getWorld(), new Client.Tile(tile.getDayFilename())); } return rendered; diff --git a/src/main/java/org/dynmap/kzedmap/DefaultTileRenderer.java b/src/main/java/org/dynmap/kzedmap/DefaultTileRenderer.java index 209f4761..cf7a5813 100644 --- a/src/main/java/org/dynmap/kzedmap/DefaultTileRenderer.java +++ b/src/main/java/org/dynmap/kzedmap/DefaultTileRenderer.java @@ -18,6 +18,7 @@ import org.dynmap.ConfigurationNode; import org.dynmap.MapManager; import org.dynmap.debug.Debug; import org.dynmap.MapChunkCache; +import org.dynmap.kzedmap.KzedMap.KzedBufferedImage; import org.json.simple.JSONObject; public class DefaultTileRenderer implements MapTileRenderer { @@ -80,12 +81,12 @@ public class DefaultTileRenderer implements MapTileRenderer { public boolean render(MapChunkCache cache, KzedMapTile tile, File outputFile) { World world = tile.getWorld(); boolean isnether = (world.getEnvironment() == Environment.NETHER); - BufferedImage im = KzedMap.allocateBufferedImage(KzedMap.tileWidth, KzedMap.tileHeight); - BufferedImage zim = KzedMap.allocateBufferedImage(KzedMap.tileWidth/2, KzedMap.tileHeight/2); + KzedBufferedImage im = KzedMap.allocateBufferedImage(KzedMap.tileWidth, KzedMap.tileHeight); + KzedBufferedImage zim = KzedMap.allocateBufferedImage(KzedMap.tileWidth/2, KzedMap.tileHeight/2); boolean isempty = true; - BufferedImage im_day = null; - BufferedImage zim_day = null; + KzedBufferedImage im_day = null; + KzedBufferedImage zim_day = null; if(night_and_day) { im_day = KzedMap.allocateBufferedImage(KzedMap.tileWidth, KzedMap.tileHeight); zim_day = KzedMap.allocateBufferedImage(KzedMap.tileWidth/2, KzedMap.tileHeight/2); @@ -107,8 +108,8 @@ public class DefaultTileRenderer implements MapTileRenderer { Color c1 = new Color(); Color c2 = new Color(); - int[] argb = new int[KzedMap.tileWidth]; - int[] zargb = new int[4*KzedMap.tileWidth/2]; + int[] argb = im.argb_buf; + int[] zargb = zim.argb_buf; Color c1_day = null; Color c2_day = null; int[] argb_day = null; @@ -116,9 +117,10 @@ public class DefaultTileRenderer implements MapTileRenderer { if(night_and_day) { c1_day = new Color(); c2_day = new Color(); - argb_day = new int[KzedMap.tileWidth]; - zargb_day = new int[4*KzedMap.tileWidth/2]; + argb_day = im_day.argb_buf; + zargb_day = zim_day.argb_buf; } + int rowoff = 0; /* draw the map */ for (y = 0; y < KzedMap.tileHeight;) { jx = ix; @@ -130,12 +132,12 @@ public class DefaultTileRenderer implements MapTileRenderer { mapiter.initialize(jx, iy, jz); scan(world, 2, isnether, c2, c2_day, mapiter); - argb[x] = c1.getARGB(); - argb[x-1] = c2.getARGB(); + argb[rowoff+x] = c1.getARGB(); + argb[rowoff+x-1] = c2.getARGB(); if(night_and_day) { - argb_day[x] = c1_day.getARGB(); - argb_day[x-1] = c2_day.getARGB(); + argb_day[rowoff+x] = c1_day.getARGB(); + argb_day[rowoff+x-1] = c2_day.getARGB(); } isempty = isempty && c1.isTransparent() && c2.isTransparent(); @@ -144,26 +146,9 @@ public class DefaultTileRenderer implements MapTileRenderer { jz++; } - im.setRGB(0, y, KzedMap.tileWidth, 1, argb, 0, KzedMap.tileWidth); - if(night_and_day) - im_day.setRGB(0, y, KzedMap.tileWidth, 1, argb_day, 0, KzedMap.tileWidth); - /* Sum up zoomed pixels - bilinar filter */ - for(x = 0; x < KzedMap.tileWidth / 2; x++) { - c1.setARGB(argb[2*x]); - c2.setARGB(argb[2*x+1]); - for(int i = 0; i < 4; i++) - zargb[4*x+i] = c1.getComponent(i) + c2.getComponent(i); - } - if(night_and_day) { - for(x = 0; x < KzedMap.tileWidth / 2; x++) { - c1.setARGB(argb_day[2*x]); - c2.setARGB(argb_day[2*x+1]); - for(int i = 0; i < 4; i++) - zargb_day[4*x+i] = c1.getComponent(i) + c2.getComponent(i); - } - } y++; + rowoff += KzedMap.tileWidth; jx = ix; jz = iz - 1; @@ -176,49 +161,27 @@ public class DefaultTileRenderer implements MapTileRenderer { mapiter.initialize(jx, iy, jz); scan(world, 0, isnether, c2, c2_day, mapiter); - argb[x] = c1.getARGB(); - argb[x-1] = c2.getARGB(); + argb[rowoff+x] = c1.getARGB(); + argb[rowoff+x-1] = c2.getARGB(); if(night_and_day) { - argb_day[x] = c1_day.getARGB(); - argb_day[x-1] = c2_day.getARGB(); + argb_day[rowoff+x] = c1_day.getARGB(); + argb_day[rowoff+x-1] = c2_day.getARGB(); } isempty = isempty && c1.isTransparent() && c2.isTransparent(); - } - im.setRGB(0, y, KzedMap.tileWidth, 1, argb, 0, KzedMap.tileWidth); - if(night_and_day) - im_day.setRGB(0, y, KzedMap.tileWidth, 1, argb_day, 0, KzedMap.tileWidth); - - /* Finish summing values for zoomed pixels */ - /* Sum up zoomed pixels - bilinar filter */ - for(x = 0; x < KzedMap.tileWidth / 2; x++) { - c1.setARGB(argb[2*x]); - c2.setARGB(argb[2*x+1]); - for(int i = 0; i < 4; i++) - zargb[4*x+i] = (zargb[4*x+i] + c1.getComponent(i) + c2.getComponent(i)) >> 2; - c1.setRGBA(zargb[4*x+1], zargb[4*x+2], zargb[4*x+3], zargb[4*x]); - zargb[x] = c1.getARGB(); - } - if(night_and_day) { - for(x = 0; x < KzedMap.tileWidth / 2; x++) { - c1.setARGB(argb_day[2*x]); - c2.setARGB(argb_day[2*x+1]); - for(int i = 0; i < 4; i++) - zargb_day[4*x+i] = (zargb_day[4*x+i] + c1.getComponent(i) + c2.getComponent(i)) >> 2; - c1.setRGBA(zargb_day[4*x+1], zargb_day[4*x+2], zargb_day[4*x+3], zargb_day[4*x]); - zargb_day[x] = c1.getARGB(); - } - } - zim.setRGB(0, y/2, KzedMap.tileWidth/2, 1, zargb, 0, KzedMap.tileWidth/2); - if(night_and_day) - zim_day.setRGB(0, y/2, KzedMap.tileWidth/2, 1, zargb_day, 0, KzedMap.tileWidth/2); - + } y++; + rowoff += KzedMap.tileWidth; ix++; iz--; } + /* Now, compute zoomed tile - bilinear filter 2x2 -> 1x1 */ + doScaleWithBilinear(argb, zargb, KzedMap.tileWidth, KzedMap.tileHeight); + if(night_and_day) { + doScaleWithBilinear(argb_day, zargb_day, KzedMap.tileWidth, KzedMap.tileHeight); + } /* Hand encoding and writing file off to MapManager */ KzedZoomedMapTile zmtile = new KzedZoomedMapTile(tile.getWorld(), @@ -230,13 +193,37 @@ public class DefaultTileRenderer implements MapTileRenderer { return !isempty; } + private void doScaleWithBilinear(int[] argb, int[] zargb, int width, int height) { + Color c1 = new Color(); + /* Now, compute zoomed tile - bilinear filter 2x2 -> 1x1 */ + for(int y = 0; y < height; y += 2) { + for(int x = 0; x < width; x += 2) { + int red = 0; + int green = 0; + int blue = 0; + int alpha = 0; + for(int yy = y; yy < y+2; yy++) { + for(int xx = x; xx < x+2; xx++) { + c1.setARGB(argb[(yy*width)+xx]); + red += c1.getRed(); + green += c1.getGreen(); + blue += c1.getBlue(); + alpha += c1.getAlpha(); + } + } + c1.setRGBA(red>>2, green>>2, blue>>2, alpha>>2); + zargb[(y*width/4) + (x/2)] = c1.getARGB(); + } + } + } + private void doFileWrites(final File fname, final KzedMapTile mtile, - final BufferedImage img, final BufferedImage img_day, + final KzedBufferedImage img, final KzedBufferedImage img_day, final KzedZoomedMapTile zmtile, final File zoomFile, - final BufferedImage zimg, final BufferedImage zimg_day) { + final KzedBufferedImage zimg, final KzedBufferedImage zimg_day) { Debug.debug("saving image " + fname.getPath()); try { - ImageIO.write(img, "png", fname); + ImageIO.write(img.buf_img, "png", fname); } catch (IOException e) { Debug.error("Failed to save image: " + fname.getPath(), e); } catch (java.lang.NullPointerException e) { @@ -247,7 +234,7 @@ public class DefaultTileRenderer implements MapTileRenderer { File dfname = new File(fname.getParent(), mtile.getDayFilename()); Debug.debug("saving image " + dfname.getPath()); try { - ImageIO.write(img_day, "png", dfname); + ImageIO.write(img_day.buf_img, "png", dfname); } catch (IOException e) { Debug.error("Failed to save image: " + dfname.getPath(), e); } catch (java.lang.NullPointerException e) { @@ -277,6 +264,7 @@ public class DefaultTileRenderer implements MapTileRenderer { oy = sch; BufferedImage zIm = null; + KzedBufferedImage kzIm = null; try { zIm = ImageIO.read(zoomFile); } catch (IOException e) { @@ -286,7 +274,8 @@ public class DefaultTileRenderer implements MapTileRenderer { boolean zIm_allocated = false; if (zIm == null) { /* create new one */ - zIm = KzedMap.allocateBufferedImage(KzedMap.tileWidth, KzedMap.tileHeight); + kzIm = KzedMap.allocateBufferedImage(KzedMap.tileWidth, KzedMap.tileHeight); + zIm = kzIm.buf_img; zIm_allocated = true; Debug.debug("New zoom-out tile created " + zmtile.getFilename()); } else { @@ -294,8 +283,7 @@ public class DefaultTileRenderer implements MapTileRenderer { } /* blit scaled rendered tile onto zoom-out tile */ - int[] pix = zimg.getRGB(0, 0, KzedMap.tileWidth/2, KzedMap.tileHeight/2, null, 0, KzedMap.tileWidth/2); - zIm.setRGB(ox, oy, KzedMap.tileWidth/2, KzedMap.tileHeight/2, pix, 0, KzedMap.tileWidth/2); + zIm.setRGB(ox, oy, KzedMap.tileWidth/2, KzedMap.tileHeight/2, zimg.argb_buf, 0, KzedMap.tileWidth/2); KzedMap.freeBufferedImage(zimg); /* save zoom-out tile */ @@ -309,7 +297,7 @@ public class DefaultTileRenderer implements MapTileRenderer { Debug.error("Failed to save zoom-out tile (NullPointerException): " + zoomFile.getName(), e); } if(zIm_allocated) - KzedMap.freeBufferedImage(zIm); + KzedMap.freeBufferedImage(kzIm); else zIm.flush(); @@ -317,6 +305,7 @@ public class DefaultTileRenderer implements MapTileRenderer { File zoomFile_day = new File(zoomFile.getParent(), zmtile.getDayFilename()); zIm = null; + kzIm = null; try { zIm = ImageIO.read(zoomFile_day); } catch (IOException e) { @@ -326,7 +315,8 @@ public class DefaultTileRenderer implements MapTileRenderer { zIm_allocated = false; if (zIm == null) { /* create new one */ - zIm = KzedMap.allocateBufferedImage(KzedMap.tileWidth, KzedMap.tileHeight); + kzIm = KzedMap.allocateBufferedImage(KzedMap.tileWidth, KzedMap.tileHeight); + zIm = kzIm.buf_img; zIm_allocated = true; Debug.debug("New zoom-out tile created " + zmtile.getFilename()); } else { @@ -334,8 +324,7 @@ public class DefaultTileRenderer implements MapTileRenderer { } /* blit scaled rendered tile onto zoom-out tile */ - pix = zimg_day.getRGB(0, 0, KzedMap.tileWidth/2, KzedMap.tileHeight/2, null, 0, KzedMap.tileWidth/2); - zIm.setRGB(ox, oy, KzedMap.tileWidth/2, KzedMap.tileHeight/2, pix, 0, KzedMap.tileWidth/2); + zIm.setRGB(ox, oy, KzedMap.tileWidth/2, KzedMap.tileHeight/2, zimg_day.argb_buf, 0, KzedMap.tileWidth/2); KzedMap.freeBufferedImage(zimg_day); /* save zoom-out tile */ @@ -349,7 +338,7 @@ public class DefaultTileRenderer implements MapTileRenderer { Debug.error("Failed to save zoom-out tile (NullPointerException): " + zoomFile_day.getName(), e); } if(zIm_allocated) - KzedMap.freeBufferedImage(zIm); + KzedMap.freeBufferedImage(kzIm); else zIm.flush(); } diff --git a/src/main/java/org/dynmap/kzedmap/KzedMap.java b/src/main/java/org/dynmap/kzedmap/KzedMap.java index 56e90d00..6c4faa94 100644 --- a/src/main/java/org/dynmap/kzedmap/KzedMap.java +++ b/src/main/java/org/dynmap/kzedmap/KzedMap.java @@ -3,6 +3,7 @@ package org.dynmap.kzedmap; import java.awt.image.BufferedImage; import java.io.File; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.LinkedList; import java.util.List; @@ -17,6 +18,11 @@ import org.dynmap.MapTile; import org.dynmap.MapType; import org.dynmap.MapChunkCache; import org.json.simple.JSONObject; +import java.awt.image.DataBufferInt; +import java.awt.image.DataBuffer; +import java.awt.image.WritableRaster; +import java.awt.image.ColorModel; +import java.awt.image.Raster; public class KzedMap extends MapType { protected static final Logger log = Logger.getLogger("Minecraft"); @@ -41,11 +47,18 @@ public class KzedMap extends MapType { MapTileRenderer[] renderers; ZoomedTileRenderer zoomrenderer; + /* BufferedImage with direct access to its ARGB-formatted data buffer */ + public static class KzedBufferedImage { + public BufferedImage buf_img; + public int[] argb_buf; + public int width; + public int height; + } + /* BufferedImage cache - we use the same things a lot... */ private static Object lock = new Object(); - private static HashMap> imgcache = - new HashMap>(); /* Indexed by resolution - X<<32+Y */ - private static int[] zerobuf = new int[128]; + private static HashMap> imgcache = + new HashMap>(); /* Indexed by resolution - X<<32+Y */ private static final int CACHE_LIMIT = 10; public KzedMap(ConfigurationNode configuration) { @@ -263,22 +276,24 @@ public class KzedMap extends MapType { * @param x - x dimension * @param y - y dimension */ - public static BufferedImage allocateBufferedImage(int x, int y) { - BufferedImage img = null; + public static KzedBufferedImage allocateBufferedImage(int x, int y) { + KzedBufferedImage img = null; synchronized(lock) { long k = (x<<16) + y; - LinkedList ll = imgcache.get(k); + LinkedList ll = imgcache.get(k); if(ll != null) { img = ll.poll(); } } if(img != null) { /* Got it - reset it for use */ - if(zerobuf.length < x) - zerobuf = new int[x]; - img.setRGB(0, 0, x, y, zerobuf, 0, 0); + Arrays.fill(img.argb_buf, 0); } else { - img = new BufferedImage(x, y, BufferedImage.TYPE_INT_ARGB); + img = new KzedBufferedImage(); + img.width = x; + img.height = y; + img.argb_buf = new int[x*y]; + img.buf_img = createBufferedImage(img.argb_buf, img.width, img.height); } return img; } @@ -286,13 +301,13 @@ public class KzedMap extends MapType { /** * Return buffered image to pool */ - public static void freeBufferedImage(BufferedImage img) { - img.flush(); + public static void freeBufferedImage(KzedBufferedImage img) { + img.buf_img.flush(); synchronized(lock) { - long k = (img.getWidth()<<16) + img.getHeight(); - LinkedList ll = imgcache.get(k); + long k = (img.width<<16) + img.height; + LinkedList ll = imgcache.get(k); if(ll == null) { - ll = new LinkedList(); + ll = new LinkedList(); imgcache.put(k, ll); } if(ll.size() < CACHE_LIMIT) { @@ -308,4 +323,21 @@ public class KzedMap extends MapType { renderer.buildClientConfiguration(worldObject); } } + + /* ARGB band masks */ + private static final int [] band_masks = {0xFF0000, 0xFF00, 0xff, 0xff000000}; + + /** + * Build BufferedImage from provided ARGB array and dimensions + */ + public static BufferedImage createBufferedImage(int[] argb_buf, int w, int h) { + /* Create integer-base data buffer */ + DataBuffer db = new DataBufferInt (argb_buf, w*h); + /* Create writable raster */ + WritableRaster raster = Raster.createPackedRaster(db, w, h, w, band_masks, null); + /* RGB color model */ + ColorModel color_model = ColorModel.getRGBdefault (); + /* Return buffered image */ + return new BufferedImage (color_model, raster, false, null); + } }