package org.codemap.tasks; import static org.codemap.CodemapCore.colorScheme; import org.codemap.DigitalElevationModel; import org.codemap.HillShading; import org.codemap.Location; import org.codemap.MapInstance; import org.codemap.MapSetting; import org.codemap.internal.DEMAlgorithm; import org.codemap.util.MColor; import org.codemap.util.MapScheme; import org.codemap.util.StopWatch; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Device; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.ImageData; import org.eclipse.swt.graphics.PaletteData; import org.eclipse.swt.widgets.Display; import ch.akuhn.util.ProgressMonitor; import ch.akuhn.values.Arguments; import ch.akuhn.values.TaskValue; import ch.akuhn.values.Value; public class ComputeBackgroundTask extends TaskValue<Image> { public static final MapSetting<Integer> SHORELINE_HEIGHT = MapSetting.define("SHORELINE_HEIGHT", 2); public static final MapSetting<Integer> HILLLINE_HEIGHT = MapSetting.define("HILLLINE_HEIGHT", 10); public ComputeBackgroundTask( Value<MapInstance> mapInstance, Value<DigitalElevationModel> elevationModel, Value<HillShading> shading, Value<MapScheme<MColor>> colors) { super("Caching background image", mapInstance, elevationModel, shading, colors); this.doesNotRequireAllArguments(); } @Override protected Image computeValue(ProgressMonitor monitor, Arguments arguments) { MapInstance mapInstance = arguments.nextOrNull(); if (mapInstance == null) return null; DigitalElevationModel elevationModel = getCurrentElevationModel(arguments, mapInstance); HillShading hillShading = getCurrentHillShading(arguments, mapInstance); MapScheme<MColor> colors = getCurrentColorScheme(arguments); return computeValue(monitor, mapInstance, elevationModel, hillShading, colors); } private Image computeValue(ProgressMonitor monitor, MapInstance mapInstance, DigitalElevationModel elevationModel, HillShading hillShading, MapScheme<MColor> colors) { int mapSize = mapInstance.getWidth(); Device device = Display.getCurrent(); Image image = new Image(device, mapSize, mapSize); GC gc = new GC(image); this.paintWater(monitor, gc); this.paintBackground(monitor, gc, mapInstance, elevationModel, hillShading, colors); gc.dispose(); return image; } private MapScheme<MColor> getCurrentColorScheme(Arguments arguments) { MapScheme<MColor> colors = arguments.nextOrNull(); if (colors == null) colors = MapScheme.with(MColor.HILLGREEN); return colors; } private DigitalElevationModel getCurrentElevationModel(Arguments arguments, MapInstance mapInstance) { DigitalElevationModel elevationModel = arguments.nextOrNull(); if (elevationModel != null && mapInstance.getWidth() != elevationModel.getSize()) elevationModel = null; return elevationModel; } private HillShading getCurrentHillShading(Arguments arguments, MapInstance mapInstance) { HillShading hillShading = arguments.nextOrNull(); if (hillShading != null && mapInstance.getWidth() != hillShading.getSize()) hillShading = null; return hillShading; } private void paintBackground(ProgressMonitor monitor, GC gc, MapInstance mapInstance, DigitalElevationModel elevationModel, HillShading hillShading, MapScheme<MColor> colors) { if (elevationModel == null) { StopWatch stopWatch = new StopWatch("Background (Draft)").start(); paintDraft(monitor, gc, mapInstance); stopWatch.printStop(); } else { StopWatch stopWatch = new StopWatch("Background").start(); paintHills(monitor, gc, mapInstance, elevationModel, hillShading, colors); stopWatch.printStop(); } } private void paintWater(ProgressMonitor monitor, GC gc) { if (monitor.isCanceled()) return; Color blue = colorScheme().getWaterColor().asSWTColor(gc.getDevice()); gc.setBackground(blue); gc.fillRectangle(gc.getClipping()); blue.dispose(); } private void paintDraft(ProgressMonitor monitor, GC gc, MapInstance map) { if (monitor.isCanceled()) return; if (map == null) return; Color shore = colorScheme().getShoreColor().asSWTColor(gc.getDevice()); gc.setForeground(shore); gc.setLineWidth(2); for (Location each: map.locations()) { if (monitor.isCanceled()) break; int r = (int) (each.getElevation() * 2 * map.getWidth() / DEMAlgorithm.MAGIC_VALUE); gc.drawOval(each.px - r, each.py - r, r * 2, r * 2); } shore.dispose(); } private void paintHills(ProgressMonitor monitor, GC gc, MapInstance mapInstance, DigitalElevationModel elevationModel, HillShading hillShading, MapScheme<MColor> colors) { if (monitor.isCanceled()) return; if (hillShading == null) return; float[][] DEM = elevationModel.asFloatArray(); double[][] shade = hillShading.asDoubleArray(); // set black background so that the transparency works // getting it via SWT.COLOR_BLACK raises invalid Thread access // and it don't want to fire up a runnable here ... Color black = new Color(gc.getDevice(), 0, 0, 0); gc.setBackground(black); gc.fillRectangle(gc.getClipping()); black.dispose(); Image background = new FastBackgroundRenderer(DEM, shade, mapInstance, colors, gc.getDevice()).render(); gc.drawImage(background, 0, 0); } @Override protected void maybeDisposeValue(Image previous, Image newValue) { if (previous != null && !previous.equals(newValue)) previous.dispose(); } public static class FastBackgroundRenderer { private Device device; private int mapSize; private double[][] shade; private float[][] DEM; private MapInstance map; private MapScheme<MColor> colors; public FastBackgroundRenderer(float[][] DEM, double[][] shade, MapInstance mapInstance, MapScheme<MColor> colors, Device device) { this.DEM = DEM; this.shade = shade; this.map = mapInstance; this.mapSize = mapInstance.getWidth(); this.device = device; this.colors = colors; } /** * Performance optimized rendering, * uses byte[] for RGB color information and transparency. */ public Image render() { // 1 byte per color, we fill 24bit per pixel byte[] imageBytes = new byte[mapSize*mapSize*3]; byte[] alphaBytes = new byte[mapSize*mapSize]; // colors needed later byte[] waterColor = colorScheme().getWaterColor().asByte(); byte[] shoreColor = colorScheme().getShoreColor().asByte(); int shorelineHeight = map.get(SHORELINE_HEIGHT); int hillineHeight = map.get(HILLLINE_HEIGHT); StopWatch nnStopWatch = new StopWatch("nearest neighbor (total)"); for(int i=0; i < mapSize*mapSize; i++) { int y = i / mapSize; int x = i % mapSize; if (DEM[x][y] <= shorelineHeight) { // water color System.arraycopy(waterColor, 0, imageBytes, i*3, 3); alphaBytes[i] = (byte) 255; continue; } else if (DEM[x][y] <= hillineHeight) { // shore colors System.arraycopy(shoreColor, 0, imageBytes, i*3, 3); alphaBytes[i] = (byte) 255; continue; } // get color from location a.k.a. hill colors nnStopWatch.start(); Location nearestNeighbor = map.nearestNeighbor(x, y); MColor mcolor; if (nearestNeighbor == null) { mcolor = colorScheme().getHillColor(); } else { mcolor = colors.forLocation(nearestNeighbor.getPoint()); } nnStopWatch.stop(); // make rgb int baseIndex = i*3; imageBytes[baseIndex++] = (byte) mcolor.getRed(); // R imageBytes[baseIndex++] = (byte) mcolor.getGreen(); // G imageBytes[baseIndex++] = (byte) mcolor.getBlue(); // B // make alpha // 0 for fully transparent // 255 for fully opaque double f = shade[x][y]; assert f <=1; // alpha values can get negative somehow so we fix that int alpha = (int) Math.max(0.0, 255*f); alphaBytes[i] = (byte) alpha; } nnStopWatch.print(); // define a direct palette with masks for RGB PaletteData palette = new PaletteData(0xFF0000 , 0xFF00 , 0xFF); ImageData imageData = new ImageData(mapSize, mapSize, 24, palette, mapSize*3, imageBytes); // enable alpha by setting alphabytes ... strange that i can't do that per pixel using 32bit values imageData.alphaData = alphaBytes; return new Image(device, imageData); } } }