Skip to content

Commit

Permalink
Hillshading (#922) #923
Browse files Browse the repository at this point in the history
  • Loading branch information
usrusr authored and devemux86 committed Feb 26, 2017
1 parent f63fe32 commit 7dd483c
Show file tree
Hide file tree
Showing 28 changed files with 1,074 additions and 28 deletions.
Expand Up @@ -16,7 +16,11 @@
*/
package org.mapsforge.core.graphics;

import org.mapsforge.core.model.Rectangle;

public interface GraphicContext {
void shadeBitmap(Bitmap bitmap, Rectangle shadeRect, Rectangle tileRect, float magnitude);

void drawBitmap(Bitmap bitmap, int left, int top);

void drawBitmap(Bitmap bitmap, int left, int top, Filter filter);
Expand Down
Expand Up @@ -25,6 +25,11 @@
public interface GraphicFactory {
Bitmap createBitmap(int width, int height);

/** create a single channel bitmap for hillshading
* @param buffer
*/
Bitmap createMonoBitmap(int width, int height, byte[] buffer);

Bitmap createBitmap(int width, int height, boolean isTransparent);

Canvas createCanvas();
Expand Down
Expand Up @@ -20,6 +20,7 @@
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.Region;

import org.mapsforge.core.graphics.Bitmap;
Expand All @@ -30,6 +31,7 @@
import org.mapsforge.core.graphics.Paint;
import org.mapsforge.core.graphics.Path;
import org.mapsforge.core.model.Dimension;
import org.mapsforge.core.model.Rectangle;

class AndroidCanvas implements Canvas {
private static final float[] INVERT_MATRIX = {
Expand All @@ -41,6 +43,7 @@ class AndroidCanvas implements Canvas {

android.graphics.Canvas canvas;
private final android.graphics.Paint bitmapPaint = new android.graphics.Paint();
private final android.graphics.Paint shadePaint = new android.graphics.Paint();
private ColorFilter grayscaleFilter, grayscaleInvertFilter, invertFilter;

AndroidCanvas() {
Expand All @@ -49,6 +52,9 @@ class AndroidCanvas implements Canvas {
this.bitmapPaint.setAntiAlias(true);
this.bitmapPaint.setFilterBitmap(true);

this.shadePaint.setAntiAlias(true);
this.shadePaint.setFilterBitmap(true);

createFilters();
}

Expand Down Expand Up @@ -112,6 +118,14 @@ public void drawBitmap(Bitmap bitmap, Matrix matrix) {
this.canvas.drawBitmap(AndroidGraphicFactory.getBitmap(bitmap), AndroidGraphicFactory.getMatrix(matrix), bitmapPaint);
}

@Override
public void shadeBitmap(Bitmap bitmap, Rectangle hillRect, Rectangle tileRect, float magnitude) {
shadePaint.setAlpha((int) (255 * magnitude));
Rect atr = new Rect((int)hillRect.left, (int)hillRect.top, (int)hillRect.right, (int)hillRect.bottom);
Rect asr = new Rect((int)tileRect.left, (int)tileRect.top, (int)tileRect.right, (int)tileRect.bottom);
this.canvas.drawBitmap(AndroidGraphicFactory.getBitmap(bitmap), atr, asr, shadePaint);
}

@Override
public void drawBitmap(Bitmap bitmap, Matrix matrix, Filter filter) {
applyFilter(filter);
Expand Down
Expand Up @@ -48,6 +48,8 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.Buffer;
import java.nio.ByteBuffer;

public final class AndroidGraphicFactory implements GraphicFactory {

Expand All @@ -69,6 +71,9 @@ public final class AndroidGraphicFactory implements GraphicFactory {
// passes through unoptimized path in the skia library.
public static final Config TRANSPARENT_BITMAP = Config.ARGB_8888;


public static final Config MONO_ALPHA_BITMAP = Config.ALPHA_8;

public static android.graphics.Bitmap convertToAndroidBitmap(Drawable drawable) {
android.graphics.Bitmap bitmap;
if (drawable instanceof BitmapDrawable) {
Expand Down Expand Up @@ -188,6 +193,16 @@ public static void clearResourceMemoryCache() {
public Bitmap createBitmap(int width, int height) {
return new AndroidBitmap(width, height, TRANSPARENT_BITMAP);
}
@Override
public Bitmap createMonoBitmap(int width, int height, byte[] buffer) {
AndroidBitmap androidBitmap = new AndroidBitmap(width, height, MONO_ALPHA_BITMAP);
if(buffer!=null) {
Buffer b = ByteBuffer.wrap(buffer);
androidBitmap.bitmap.copyPixelsFromBuffer(b);
}
return androidBitmap;
}


@Override
public Bitmap createBitmap(int width, int height, boolean isTransparent) {
Expand Down
Expand Up @@ -36,6 +36,7 @@
import org.mapsforge.map.layer.cache.InMemoryTileCache;
import org.mapsforge.map.layer.cache.TileCache;
import org.mapsforge.map.layer.cache.TwoLevelTileCache;
import org.mapsforge.map.layer.hills.HillsRenderConfig;
import org.mapsforge.map.layer.renderer.TileRendererLayer;
import org.mapsforge.map.model.MapViewPosition;
import org.mapsforge.map.rendertheme.XmlRenderTheme;
Expand Down Expand Up @@ -234,7 +235,7 @@ public static TileRendererLayer createTileRendererLayer(
TileCache tileCache, MapViewPosition mapViewPosition,
MapDataStore mapFile, XmlRenderTheme renderTheme) {
TileRendererLayer tileRendererLayer = new TileRendererLayer(tileCache, mapFile,
mapViewPosition, AndroidGraphicFactory.INSTANCE);
mapViewPosition, AndroidGraphicFactory.INSTANCE, null);
tileRendererLayer.setXmlRenderTheme(renderTheme);
return tileRendererLayer;
}
Expand All @@ -261,6 +262,28 @@ public static TileRendererLayer createTileRendererLayer(
return tileRendererLayer;
}

/**
* Utility method to create a tile renderer layer with hillshading.
*
* @param tileCache the cache
* @param mapViewPosition the position
* @param mapFile the map file
* @param renderTheme the render theme to use
* @param hasAlpha if the layer is transparent (more memory)
* @param renderLabels should usually be true
* @param cacheLabels should usually be false
* @param hillsRenderConfig may be null to omit hillshading
* @return the layer
*/
public static TileRendererLayer createTileRendererLayerWithHillshading(
TileCache tileCache, MapViewPosition mapViewPosition,
MapDataStore mapFile, XmlRenderTheme renderTheme, boolean hasAlpha,
boolean renderLabels, boolean cacheLabels, HillsRenderConfig hillsRenderConfig) {
TileRendererLayer tileRendererLayer = new TileRendererLayer(tileCache, mapFile,
mapViewPosition, hasAlpha, renderLabels, cacheLabels, AndroidGraphicFactory.INSTANCE, hillsRenderConfig);
tileRendererLayer.setXmlRenderTheme(renderTheme);
return tileRendererLayer;
}
/**
* @return true if the current thread is the UI thread, false otherwise.
*/
Expand Down Expand Up @@ -432,4 +455,5 @@ private AndroidUtil() {
// no-op, for privacy
}


}
Expand Up @@ -26,6 +26,7 @@
import org.mapsforge.map.android.view.MapView;
import org.mapsforge.map.datastore.MapDataStore;
import org.mapsforge.map.layer.cache.TileCache;
import org.mapsforge.map.layer.hills.HillsRenderConfig;
import org.mapsforge.map.model.MapViewPosition;
import org.mapsforge.map.model.common.PreferencesFacade;
import org.mapsforge.map.reader.MapFile;
Expand Down Expand Up @@ -154,7 +155,7 @@ protected void createSharedPreferences() {
*
* @return the fallback initial position of the mapview.
*/
protected MapPosition getDefaultInitialPosition() {
protected org.mapsforge.core.model.MapPosition getDefaultInitialPosition() {
return new MapPosition(new LatLong(0, 0), getZoomLevelDefault());
}

Expand Down Expand Up @@ -336,4 +337,12 @@ protected MapView getMapView() {
setContentView(getLayoutId());
return (MapView) findViewById(getMapViewId());
}

/**
* override to enable hill shading
* @return null or the HillsRenderConfig to use (defining height model path and algorithm)
*/
protected HillsRenderConfig getHillsRenderConfig() {
return null;
}
}
Expand Up @@ -24,6 +24,7 @@
import org.mapsforge.core.graphics.Path;
import org.mapsforge.core.graphics.Style;
import org.mapsforge.core.model.Dimension;
import org.mapsforge.core.model.Rectangle;

import java.awt.AlphaComposite;
import java.awt.Composite;
Expand All @@ -40,6 +41,8 @@
import java.awt.image.IndexColorModel;
import java.awt.image.LookupOp;
import java.awt.image.ShortLookupTable;
import java.util.AbstractMap;
import java.util.Map;

class AwtCanvas implements Canvas {
private static final String UNKNOWN_STYLE = "unknown style: ";
Expand All @@ -48,6 +51,35 @@ class AwtCanvas implements Canvas {
private Graphics2D graphics2D;
private BufferedImageOp grayscaleOp, invertOp, invertOp4;

private static Map.Entry<Float, Composite> sizeOneShadingCompositeCache = null;
private static Composite getHillshadingComposite(float magnitude){
Map.Entry<Float, Composite> existing = sizeOneShadingCompositeCache;
if(existing!=null && existing.getKey()==magnitude){
// JMM says: "A thread-safe immutable object is seen as immutable by all threads, even if a data race is used to pass references to the immutable object between threads"
// worst case we construct more than strictly needed
return existing.getValue();
}

Composite selected = selectHillShadingComposite(magnitude);

if(sizeOneShadingCompositeCache==null) {
// only cache the first magnitude value, in the rare instance that more than one magnitude value would be used
// in a process lifecycle it would be better to create new Composite instances than create new instances _and_ new cache entries
sizeOneShadingCompositeCache = new AbstractMap.SimpleImmutableEntry(magnitude, selected);
}

return selected;
}
/** composite selection,
* select between {@link AlphaComposite} (fast, squashes saturation at high magnitude)
* and {@link AwtLuminanceShadingComposite} (per-pixel rgb->hsv->rgb conversion to keep saturation at high magnitude, might be a bit slow and/or inconsistent with the android implementation) */
private static Composite selectHillShadingComposite(float magnitude) {
return new AwtLuminanceShadingComposite(magnitude);
// return AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, magnitude);
}



AwtCanvas() {
createFilters();
}
Expand Down Expand Up @@ -137,6 +169,24 @@ public void drawBitmap(Bitmap bitmap, Matrix matrix) {
AwtGraphicFactory.getAffineTransform(matrix));
}

@Override
public void shadeBitmap(Bitmap bitmap, Rectangle shadeRect, Rectangle tileRect, float magnitude) {
Composite oldComposite = this.graphics2D.getComposite();
Composite composite = getHillshadingComposite(magnitude);

this.graphics2D.setComposite(composite);
BufferedImage bufferedImage = AwtGraphicFactory.getBufferedImage(bitmap);

this.graphics2D.drawImage(bufferedImage
, (int)tileRect.left, (int)tileRect.top, (int)tileRect.right, (int)tileRect.bottom
, (int)shadeRect.left, (int)shadeRect.top, (int)shadeRect.right, (int)shadeRect.bottom
, null);


this.graphics2D.setComposite(oldComposite);
}


@Override
public void drawBitmap(Bitmap bitmap, Matrix matrix, Filter filter) {
this.graphics2D.drawRenderedImage(applyFilter(AwtGraphicFactory.getBufferedImage(bitmap), filter), AwtGraphicFactory.getAffineTransform(matrix));
Expand Down Expand Up @@ -329,4 +379,5 @@ public void setColorAndStroke(AwtPaint awtPaint) {
this.graphics2D.setStroke(awtPaint.stroke);
}
}

}
Expand Up @@ -40,13 +40,33 @@
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.IndexColorModel;
import java.awt.image.Raster;
import java.awt.image.SampleModel;
import java.awt.image.WritableRaster;
import java.io.IOException;
import java.io.InputStream;

public class AwtGraphicFactory implements GraphicFactory {
public static final GraphicFactory INSTANCE = new AwtGraphicFactory();
private static final java.awt.Color TRANSPARENT = new java.awt.Color(0, 0, 0, 0);

private static final ColorModel monoColorModel;
static {
/** use an inversing lookup color model on the AWT side so that the android implementation can take the bytes without any twiddling
* (the only 8 bit bitmaps android knows are alpha masks, so we have to define our mono bitmap bytes in a way that are easy for android to understand
**/
byte[] linear = new byte[256];
for(int i=0;i<256;i++){
linear[i]= (byte) (i+Byte.MIN_VALUE);
linear[i]= (byte) (255-i);
}
monoColorModel = new IndexColorModel(8, 256, linear, linear, linear);
}

public static GraphicContext createGraphicContext(Graphics graphics) {
return new org.mapsforge.map.awt.graphics.AwtCanvas((Graphics2D) graphics);
}
Expand Down Expand Up @@ -99,6 +119,19 @@ public Bitmap createBitmap(int width, int height) {
return new AwtBitmap(width, height);
}


@Override
public Bitmap createMonoBitmap(int width, int height, byte[] buffer) {
BufferedImage bufferedImage;
DataBuffer dataBuffer = new DataBufferByte(buffer, buffer.length);

SampleModel singleByteSampleModel = monoColorModel.createCompatibleSampleModel(width, height);
WritableRaster writableRaster = Raster.createWritableRaster(singleByteSampleModel, dataBuffer, null);
bufferedImage = new BufferedImage(monoColorModel, writableRaster, false, null);

return new AwtBitmap(bufferedImage);
}

@Override
public Bitmap createBitmap(int width, int height, boolean isTransparent) {
if (isTransparent) {
Expand Down

0 comments on commit 7dd483c

Please sign in to comment.