New rendering for streets. Uses now redrawing instead of additional Bitmaps. No additional memory use. CPU usage with option shadow = 3 the same or even less than the classical one.

Changed missleading "shadowLayer" to "shadowRadius".
This commit is contained in:
seeebek 2011-10-09 13:28:02 +03:00 committed by Victor Shcherb
parent 4f3f4cc322
commit ed485a2702

View file

@ -47,47 +47,34 @@ import android.view.WindowManager;
public class OsmandRenderer {
private static final Log log = LogUtil.getLog(OsmandRenderer.class);
private final int clFillScreen = Color.rgb(241, 238, 232);
private TextPaint paintText;
private Paint paint;
private Paint paintFillEmpty;
private Paint paintIcon;
public static final int TILE_SIZE = 256;
private Map<String, PathEffect> dashEffect = new LinkedHashMap<String, PathEffect>();
private Map<Integer, Shader> shaders = new LinkedHashMap<Integer, Shader>();
private Map<Integer, Bitmap> cachedIcons = new LinkedHashMap<Integer, Bitmap>();
private final Context context;
//for street shadows
private Canvas streetcv;
private DisplayMetrics dm;
private int[] shadowarray;
private int shadownum;
private static class TextDrawInfo {
public TextDrawInfo(String text){
this.text = text;
}
String text = null;
Path drawOnPath = null;
float vOffset = 0;
float centerX = 0;
float pathRotate = 0;
float centerY = 0;
float textSize = 0;
float minDistance = 0;
int textColor = Color.BLACK;
int textShadow = 0;
int textWrap = 0;
boolean bold = false;
int shieldRes = 0;
int textOrder = 20;
public void fillProperties(RenderingContext rc, float centerX, float centerY){
this.centerX = centerX + rc.textDx;
@ -103,38 +90,53 @@ public class OsmandRenderer {
textOrder = rc.textOrder;
}
}
String text = null;
Path drawOnPath = null;
float vOffset = 0;
float centerX = 0;
float pathRotate = 0;
float centerY = 0;
float textSize = 0;
float minDistance = 0;
int textColor = Color.BLACK;
int textShadow = 0;
int textWrap = 0;
boolean bold = false;
int shieldRes = 0;
int textOrder = 20;
}
private static class IconDrawInfo {
float x = 0;
float y = 0;
int resId;
}
/*package*/ static class RenderingContext {
public boolean interrupted = false;
public boolean nightMode = false;
public boolean highResMode = false;
public float mapTextSize = 1;
List<TextDrawInfo> textToDraw = new ArrayList<TextDrawInfo>();
List<IconDrawInfo> iconsToDraw = new ArrayList<IconDrawInfo>();
float leftX;
float topY;
int width;
int height;
int zoom;
float rotate;
float tileDivisor;
// debug purpose
int pointCount = 0;
int pointInsideCount = 0;
int visible = 0;
int allObjects = 0;
// use to calculate points
PointF tempPoint = new PointF();
float cosRotateTileSize;
@ -155,14 +157,14 @@ public class OsmandRenderer {
int textShield = 0;
int textOrder = -1;
String renderingDebugInfo;
RenderingPaintProperties main = new RenderingPaintProperties();
RenderingPaintProperties second = new RenderingPaintProperties();
RenderingPaintProperties third = new RenderingPaintProperties();
RenderingPaintProperties[] adds = null;
public void clearText() {
showAnotherText = null;
showTextOnPath = false;
@ -177,20 +179,20 @@ public class OsmandRenderer {
textBold = false;
textShield = 0;
}
}
/* package*/ static class RenderingPaintProperties {
int color;
float strokeWidth;
int shadowLayer;
int shadowRadius;
int shadowColor;
boolean fillArea;
PathEffect pathEffect;
Shader shader;
Cap cap;
public void emptyLine(){
color = 0;
strokeWidth = 0;
@ -199,24 +201,24 @@ public class OsmandRenderer {
fillArea = false;
shader = null;
shadowColor = 0;
shadowLayer = 0;
shadowRadius = 0;
}
public void updatePaint(Paint p){
p.setStyle(fillArea ? Style.FILL_AND_STROKE : Style.STROKE);
p.setColor(color);
p.setShader(shader);
if(shadowColor == 0){
shadowLayer = 0;
shadowRadius = 0;
}
p.setShadowLayer(shadowLayer, 0, 0, shadowColor);
p.setShadowLayer(shadowRadius, 0, 0, shadowColor);
p.setStrokeWidth(strokeWidth);
p.setStrokeCap(cap);
if (!fillArea) {
p.setPathEffect(pathEffect);
}
}
public void emptyArea(){
color = 0;
strokeWidth = 0;
@ -225,10 +227,10 @@ public class OsmandRenderer {
shader = null;
pathEffect = null;
shadowColor = 0;
shadowLayer = 0;
shadowRadius = 0;
}
}
public OsmandRenderer(Context context) {
this.context = context;
@ -253,7 +255,7 @@ public class OsmandRenderer {
WindowManager wmgr = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
wmgr.getDefaultDisplay().getMetrics(dm);
}
public PathEffect getDashEffect(String dashes){
if(!dashEffect.containsKey(dashes)){
String[] ds = dashes.split("_"); //$NON-NLS-1$
@ -265,7 +267,7 @@ public class OsmandRenderer {
}
return dashEffect.get(dashes);
}
public Shader getShader(int resId){
if(shaders.get(resId) == null){
Shader sh = new BitmapShader(
@ -274,28 +276,21 @@ public class OsmandRenderer {
}
return shaders.get(resId);
}
private void put(TFloatObjectHashMap<TIntArrayList> map, Float k, int v, int init){
if(!map.containsKey(k)){
map.put(k, new TIntArrayList());
}
map.get(k).add(v);
}
public Bitmap generateNewBitmap(RenderingContext rc, List<BinaryMapDataObject> objects, Bitmap bmp, boolean useEnglishNames,
BaseOsmandRender renderer, List<IMapDownloaderCallback> notifyList) {
long now = System.currentTimeMillis();
//Flag for drawing the streets
boolean streetsdrawn = false;
// fill area
Canvas cv = new Canvas(bmp);
//needed for better street shadows
Bitmap streetbmp = Bitmap.createBitmap(cv.getWidth(), cv.getHeight(), Bitmap.Config.ARGB_8888);
streetcv = new Canvas(streetbmp);
if(renderer != null){
int dc = renderer.getDefaultColor(rc.nightMode);
if(dc != 0){
@ -303,7 +298,7 @@ public class OsmandRenderer {
}
}
cv.drawRect(0, 0, bmp.getWidth(), bmp.getHeight(), paintFillEmpty);
// put in order map
int sz = objects.size();
int init = sz / 4;
@ -342,36 +337,45 @@ public class OsmandRenderer {
}
}
}
if (objects != null && !objects.isEmpty() && rc.width > 0 && rc.height > 0) {
// init rendering context
rc.tileDivisor = (int) (1 << (31 - rc.zoom));
rc.cosRotateTileSize = FloatMath.cos((float) Math.toRadians(rc.rotate)) * TILE_SIZE;
rc.sinRotateTileSize = FloatMath.sin((float) Math.toRadians(rc.rotate)) * TILE_SIZE;
//int shadow = 0; // no shadow (minumum CPU)
//int shadow = 1; // classic shadow (the implementaton in master)
//int shadow = 2; // blur shadow (most CPU, but still reasonable)
int shadow = 3; // solid border (CPU use like classic version or even smaller)
boolean repeat = false;
float[] keys = orderMap.keys();
Arrays.sort(keys);
int objCount = 0;
shadowarray = new int[keys.length];
shadownum = 0;
for (int k = 0; k < keys.length; k++) {
if(repeat == true && shadowarray[shadownum] == k){
shadownum++;
continue;
}
TIntArrayList list = orderMap.get(keys[k]);
for (int j = 0; j < list.size(); j++) {
int i = list.get(j);
int ind = i >> 8;
int l = i & 0xff;
BinaryMapDataObject obj = objects.get(ind);
int l = i & 0xff;
BinaryMapDataObject obj = objects.get(ind);
//draw streets with shadow when all have been drawn
if(keys[k] > 57 && !streetsdrawn){
drawStreetsShadow(cv, streetbmp);
streetsdrawn = true;
cv.drawBitmap(streetbmp, 0, 0, null);
}
// show text only for main type
drawObj(obj, renderer, cv, rc, l, l == 0, keys[k]);
objCount++;
// show text only for main type
drawObj(obj, renderer, cv, rc, l, l == 0, shadow, k);
objCount++;
}
if(objCount > 25){
notifyListeners(notifyList);
@ -380,12 +384,20 @@ public class OsmandRenderer {
if(rc.interrupted){
return null;
}
// order = 57 should be set as limit for shadows
if(keys[k] > 57 && repeat == false && shadow > 1){
shadow = 0;
shadownum = 0;
k = shadowarray[0];
repeat = true;
}
}
notifyListeners(notifyList);
long beforeIconTextTime = System.currentTimeMillis() - now;
int skewConstant = (int) getDensityValue(rc, 16);
int iconsW = rc.width / skewConstant ;
int iconsH = rc.height / skewConstant;
int[] alreadyDrawnIcons = new int[iconsW * iconsH / 32];
@ -418,18 +430,15 @@ public class OsmandRenderer {
}
notifyListeners(notifyList);
//Draw streets here
drawStreetsWithShadow(cv, streetbmp);
drawTextOverCanvas(rc, cv, useEnglishNames);
long time = System.currentTimeMillis() - now;
rc.renderingDebugInfo = String.format("Rendering done in %s (%s text) ms\n" +
"(%s points, %s points inside, %s objects visile from %s)",//$NON-NLS-1$
time, time - beforeIconTextTime,rc.pointCount, rc.pointInsideCount, rc.visible, rc.allObjects);
log.info(rc.renderingDebugInfo);
}
return bmp;
}
@ -445,15 +454,6 @@ public class OsmandRenderer {
cv.drawBitmap(streetbmp, 0, 0, null);
}
// Draw nice shadow for all streets
private void drawStreetsShadow(Canvas cv, Bitmap streetbmp){
Paint shadowpaint = new Paint();
shadowpaint.setColor(Color.BLACK);
shadowpaint.setMaskFilter(new BlurMaskFilter(1, BlurMaskFilter.Blur.SOLID));
Bitmap shadowImage = streetbmp.extractAlpha();
cv.drawBitmap(shadowImage, 0, 0, shadowpaint);
}
private void notifyListeners(List<IMapDownloaderCallback> notifyList) {
if (notifyList != null) {
for (IMapDownloaderCallback c : notifyList) {
@ -462,7 +462,7 @@ public class OsmandRenderer {
}
}
private final static boolean findAllTextIntersections = true;
private float getDensityValue(RenderingContext rc, float val) {
if (rc.highResMode && dm.density > 1) {
return val * dm.density * rc.mapTextSize;
@ -480,7 +480,7 @@ public class OsmandRenderer {
public int compare(RectF object1, RectF object2) {
return Float.compare(object1.left, object2.left);
}
};
// 1. Sort text using text order
@ -503,8 +503,8 @@ public class OsmandRenderer {
if(useEnglishNames){
text.text = Junidecode.unidecode(text.text);
}
// sest text size before finding intersection (it is used there)
float textSize = getDensityValue(rc, text.textSize);
paintText.setTextSize(textSize);
@ -512,14 +512,14 @@ public class OsmandRenderer {
paintText.setColor(text.textColor);
// align center y
text.centerY += (-paintText.ascent());
// calculate if there is intersection
boolean intersects = findTextIntersection(rc, boundsNotPathIntersect, boundsPathIntersect, c, text);
if(intersects){
continue nextText;
}
if(text.drawOnPath != null){
if(text.textShadow > 0){
paintText.setColor(Color.WHITE);
@ -544,7 +544,7 @@ public class OsmandRenderer {
, paintIcon);
}
}
drawWrappedText(cv, text, textSize);
}
}
@ -556,7 +556,7 @@ public class OsmandRenderer {
// set maximum for all text
text.textWrap = 40;
}
if(text.text.length() > text.textWrap){
int start = 0;
int end = text.text.length();
@ -584,13 +584,13 @@ public class OsmandRenderer {
limit += (start - pos) - 1;
}
line++;
}
} else {
drawTextOnCanvas(cv, text.text, text.centerX, text.centerY, paintText, text.textShadow);
}
}
private void drawTextOnCanvas(Canvas cv, String text, float centerX, float centerY, Paint paint, float textShadow){
if(textShadow > 0){
int c = paintText.getColor();
@ -605,18 +605,18 @@ public class OsmandRenderer {
}
cv.drawText(text, centerX, centerY, paint);
}
private boolean findTextIntersection(RenderingContext rc, List<RectF> boundsNotPathIntersect, List<RectF> boundsPathIntersect,
Comparator<RectF> c, TextDrawInfo text) {
boolean horizontalWayDisplay = (text.pathRotate > 45 && text.pathRotate < 135) || (text.pathRotate > 225 && text.pathRotate < 315);
// text.minDistance = 0;
float textWidth = paintText.measureText(text.text) + (!horizontalWayDisplay ? 0 : text.minDistance);
// Paint.ascent is negative, so negate it.
// Paint.ascent is negative, so negate it.
int ascent = (int) Math.ceil(-paintText.ascent());
int descent = (int) Math.ceil(paintText.descent());
float textHeight = ascent + descent + (horizontalWayDisplay ? 0 : text.minDistance) + getDensityValue(rc, 5);
RectF bounds = new RectF();
if(text.drawOnPath == null || horizontalWayDisplay){
bounds.set(text.centerX - textWidth / 2, text.centerY - textHeight / 2 ,
@ -661,9 +661,9 @@ public class OsmandRenderer {
st = 0;
}
// test functionality
// cv.drawRect(bounds, paint);
// cv.drawText(text.text.substring(0, Math.min(5, text.text.length())), bounds.centerX(), bounds.centerY(), paint);
// cv.drawRect(bounds, paint);
// cv.drawText(text.text.substring(0, Math.min(5, text.text.length())), bounds.centerX(), bounds.centerY(), paint);
for (int j = st; j < e; j++) {
RectF b = boundsIntersect.get(j);
float x = Math.min(bounds.right, b.right) - Math.max(b.left, bounds.left);
@ -673,22 +673,23 @@ public class OsmandRenderer {
}
}
// store in list sorted by left boundary
// if(text.minDistance > 0){
// if (verticalText) {
// bounds.set(bounds.left + text.minDistance / 2, bounds.top,
// bounds.right - text.minDistance / 2, bounds.bottom);
// } else {
// bounds.set(bounds.left, bounds.top + text.minDistance / 2, bounds.right,
// bounds.bottom - text.minDistance / 2);
// }
// }
boundsIntersect.add(index, bounds);
// if(text.minDistance > 0){
// if (verticalText) {
// bounds.set(bounds.left + text.minDistance / 2, bounds.top,
// bounds.right - text.minDistance / 2, bounds.bottom);
// } else {
// bounds.set(bounds.left, bounds.top + text.minDistance / 2, bounds.right,
// bounds.bottom - text.minDistance / 2);
// }
// }
boundsIntersect.add(index, bounds);
}
return false;
}
protected void drawObj(BinaryMapDataObject obj, BaseOsmandRender render, Canvas canvas, RenderingContext rc, int l, boolean renderText, float order) {
protected void drawObj(BinaryMapDataObject obj, BaseOsmandRender render, Canvas canvas, RenderingContext rc, int l, boolean renderText
, int shadow, int index) {
rc.allObjects++;
if (obj instanceof MultyPolygon) {
drawMultiPolygon(obj, render,canvas, rc);
@ -702,7 +703,7 @@ public class OsmandRenderer {
drawPoint(obj, render, canvas, rc, pair, renderText);
} else if (t == MapRenderingTypes.POLYLINE_TYPE) {
int layer = MapRenderingTypes.getNegativeWayLayer(mainType);
drawPolyline(obj, render, canvas, rc, pair, layer, order);
drawPolyline(obj, render, canvas, rc, pair, layer, shadow, index);
} else if (t == MapRenderingTypes.POLYGON_TYPE) {
drawPolygon(obj, render, canvas, rc, pair);
} else {
@ -714,8 +715,8 @@ public class OsmandRenderer {
}
}
private PointF calcPoint(BinaryMapDataObject o, int ind, RenderingContext rc){
rc.pointCount ++;
float tx = o.getPoint31XTile(ind) / rc.tileDivisor;
@ -731,7 +732,7 @@ public class OsmandRenderer {
}
return rc.tempPoint;
}
private PointF calcMultiPolygonPoint(MultyPolygon o, int i, int b, RenderingContext rc){
rc.pointCount ++;
float tx = o.getPoint31XTile(i, b)/ rc.tileDivisor;
@ -748,14 +749,16 @@ public class OsmandRenderer {
return rc.tempPoint;
}
public void clearCachedResources(){
cachedIcons.clear();
shaders.clear();
}
private void drawMultiPolygon(BinaryMapDataObject obj, BaseOsmandRender render, Canvas canvas, RenderingContext rc) {
String tag = ((MultyPolygon)obj).getTag();
String value = ((MultyPolygon)obj).getValue();
@ -765,7 +768,7 @@ public class OsmandRenderer {
rc.main.emptyArea();
rc.second.emptyLine();
rc.main.color = Color.rgb(245, 245, 245);
boolean rendered = render.renderPolygon(tag, value, rc.zoom, rc, this, rc.nightMode);
if(!rendered){
return;
@ -796,18 +799,18 @@ public class OsmandRenderer {
rc.main.updatePaint(paint);
canvas.drawPath(path, paint);
// for test purpose
// rc.second.strokeWidth = 1.5f;
// rc.second.color = Color.BLACK;
// rc.second.strokeWidth = 1.5f;
// rc.second.color = Color.BLACK;
if (rc.second.strokeWidth != 0) {
rc.second.updatePaint(paint);
canvas.drawPath(path, paint);
}
}
private void drawPolygon(BinaryMapDataObject obj, BaseOsmandRender render, Canvas canvas, RenderingContext rc, TagValuePair pair) {
if(render == null || pair == null){
return;
}
@ -818,7 +821,7 @@ public class OsmandRenderer {
rc.main.emptyArea();
rc.second.emptyLine();
// rc.main.color = Color.rgb(245, 245, 245);
boolean rendered = render.renderPolygon(pair.tag, pair.value, zoom, rc, this, rc.nightMode);
if(!rendered){
return;
@ -886,13 +889,13 @@ public class OsmandRenderer {
rc.textToDraw.add(info);
}
}
private void drawPoint(BinaryMapDataObject obj, BaseOsmandRender render, Canvas canvas, RenderingContext rc, TagValuePair pair, boolean renderText) {
if(render == null || pair == null){
return;
}
Integer resId = render.getPointIcon(pair.tag, pair.value, rc.zoom, rc.nightMode);
String name = null;
if (renderText) {
@ -913,7 +916,7 @@ public class OsmandRenderer {
ps.x /= len;
ps.y /= len;
}
if(resId != null && resId != 0){
IconDrawInfo ico = new IconDrawInfo();
ico.x = ps.x;
@ -924,13 +927,36 @@ public class OsmandRenderer {
if (name != null) {
drawPointText(render, rc, pair, ps.x, ps.y, name);
}
}
private void drawPolylineWithShadow(Canvas canvas, Path path, int shadow, int shadowRadius, int index){
// option shadow = 1 ,2 don't need any changes in first draw
// option shadow = 0 without any shadows
if(shadow == 0) paint.setShadowLayer(0, 0, 0, 0);
// option shadow = 3 with solid border
if(shadow == 3){
paint.setShadowLayer(0, 0, 0, 0);
paint.setStrokeWidth(paint.getStrokeWidth() + 2);
paint.setColor(0xffbababa);
}
canvas.drawPath(path, paint);
//check for shadow and save index in array
if(shadowRadius > 0 && shadow > 1){
if(shadownum == 0){
shadowarray[shadownum] = index;
shadownum++;
}
if (shadowarray[shadownum-1] != index){
shadowarray[shadownum] = index;
shadownum++;
}
}
}
private void drawPolyline(BinaryMapDataObject obj, BaseOsmandRender render, Canvas canvas, RenderingContext rc, TagValuePair pair, int layer) {
private void drawPolyline(BinaryMapDataObject obj, BaseOsmandRender render, Canvas canvas, RenderingContext rc, TagValuePair pair, int layer,
int shadow, int index) {
if(render == null || pair == null){
return;
}
@ -949,10 +975,10 @@ public class OsmandRenderer {
if(rc.zoom >= 16 && "highway".equals(pair.tag) && MapRenderingTypes.isOneWayWay(obj.getHighwayAttributes())){ //$NON-NLS-1$
rc.adds = getOneWayProperties();
}
rc.visible++;
Path path = null;
float pathRotate = 0;
float roadLength = 0;
@ -963,7 +989,7 @@ public class OsmandRenderer {
float yMid = 0;
PointF middlePoint = new PointF();
int middle = obj.getPointsLength() / 2;
for (int i = 0; i < length ; i++) {
PointF p = calcPoint(obj, i, rc);
if(i == 0 || i == length -1){
@ -994,20 +1020,19 @@ public class OsmandRenderer {
}
if (path != null) {
rc.main.updatePaint(paint);
//changed canvas to the global one for streets
streetcv.drawPath(path, paint);
drawPolylineWithShadow(canvas, path, shadow, rc.main.shadowRadius, index);
if (rc.second.strokeWidth != 0) {
rc.second.updatePaint(paint);
streetcv.drawPath(path, paint);
drawPolylineWithShadow(canvas, path, shadow, rc.second.shadowRadius, index);
if (rc.third.strokeWidth != 0) {
rc.third.updatePaint(paint);
streetcv.drawPath(path, paint);
drawPolylineWithShadow(canvas, path, shadow, rc.third.shadowRadius, index);
}
}
if (rc.adds != null) {
for (int i = 0; i < rc.adds.length; i++) {
rc.adds[i].updatePaint(paint);
streetcv.drawPath(path, paint);
drawPolylineWithShadow(canvas, path, shadow, rc.adds[i].shadowRadius, index);
}
}
if (obj.getName() != null && obj.getName().length() > 0) {
@ -1033,9 +1058,9 @@ public class OsmandRenderer {
text.fillProperties(rc, middlePoint.x, middlePoint.y);
text.pathRotate = pathRotate;
rc.textToDraw.add(text);
}
if(name != null && name.trim().length() > 0){
rc.clearText();
name = render.renderObjectText(name, pair.tag, pair.value, rc, false, rc.nightMode);
@ -1068,12 +1093,12 @@ public class OsmandRenderer {
}
}
}
}
}
}
}
private static RenderingPaintProperties[] oneWay = null;
public static RenderingPaintProperties[] getOneWayProperties(){
if(oneWay == null){
@ -1087,28 +1112,28 @@ public class OsmandRenderer {
oneWay[0].color = 0xff6c70d5;
oneWay[0].strokeWidth = 1;
oneWay[0].pathEffect = arrowDashEffect1;
oneWay[1] = new RenderingPaintProperties();
oneWay[1].emptyLine();
oneWay[1].color = 0xff6c70d5;
oneWay[1].strokeWidth = 2;
oneWay[1].pathEffect = arrowDashEffect2;
oneWay[2] = new RenderingPaintProperties();
oneWay[2].emptyLine();
oneWay[2].color = 0xff6c70d5;
oneWay[2].strokeWidth = 3;
oneWay[2].pathEffect = arrowDashEffect3;
oneWay[3] = new RenderingPaintProperties();
oneWay[3].emptyLine();
oneWay[3].color = 0xff6c70d5;
oneWay[3].strokeWidth = 4;
oneWay[3].pathEffect = arrowDashEffect4;
}
return oneWay;
}
}