Render themes: line symbol and stroke dash array #124 #131

This commit is contained in:
Emux 2017-09-03 16:03:25 +03:00
parent f4f8eb8d1c
commit 76e73368ab
2 changed files with 93 additions and 15 deletions

View File

@ -217,8 +217,6 @@
<xs:attribute name="symbol-width" type="xs:positiveInteger" use="optional" />
<xs:attribute name="symbol-height" type="xs:positiveInteger" use="optional" />
<xs:attribute name="symbol-percent" type="xs:positiveInteger" use="optional" />
<xs:attribute name="align-center" default="false" type="xs:boolean" use="optional" />
<xs:attribute name="repeat" default="false" type="xs:boolean" use="optional" />
</xs:complexType>
<xs:complexType name="text">

View File

@ -23,6 +23,7 @@ package org.oscim.theme;
import org.oscim.backend.CanvasAdapter;
import org.oscim.backend.XMLReaderAdapter;
import org.oscim.backend.canvas.Bitmap;
import org.oscim.backend.canvas.Canvas;
import org.oscim.backend.canvas.Color;
import org.oscim.backend.canvas.Paint.Cap;
import org.oscim.backend.canvas.Paint.FontFamily;
@ -30,6 +31,7 @@ import org.oscim.backend.canvas.Paint.FontStyle;
import org.oscim.renderer.atlas.TextureAtlas;
import org.oscim.renderer.atlas.TextureAtlas.Rect;
import org.oscim.renderer.atlas.TextureRegion;
import org.oscim.renderer.bucket.TextureItem;
import org.oscim.theme.IRenderTheme.ThemeException;
import org.oscim.theme.rule.Rule;
import org.oscim.theme.rule.Rule.Closed;
@ -85,6 +87,9 @@ public class XmlThemeBuilder extends DefaultHandler {
private static final String OUTLINE_STYLE = "O";
private static final String AREA_STYLE = "A";
private static final float REPEAT_GAP_DEFAULT = 200f;
private static final float REPEAT_START_DEFAULT = 30f;
/**
* @param theme an input theme containing valid render theme XML data.
* @return a new RenderTheme which is created by parsing the XML data from the input theme.
@ -249,11 +254,11 @@ public class XmlThemeBuilder extends DefaultHandler {
} else if ("style-line".equals(localName)) {
checkState(localName, Element.STYLE);
handleLineElement(localName, attributes, true);
handleLineElement(localName, attributes, true, false);
} else if ("outline-layer".equals(localName)) {
checkState(localName, Element.RENDERING_INSTRUCTION);
LineStyle line = createLine(null, localName, attributes, mLevels++, true);
LineStyle line = createLine(null, localName, attributes, mLevels++, true, false);
mStyles.put(OUTLINE_STYLE + line.style, line);
} else if ("area".equals(localName)) {
@ -272,7 +277,7 @@ public class XmlThemeBuilder extends DefaultHandler {
} else if ("line".equals(localName)) {
checkState(localName, Element.RENDERING_INSTRUCTION);
handleLineElement(localName, attributes, false);
handleLineElement(localName, attributes, false, false);
} else if ("text".equals(localName)) {
checkState(localName, Element.RENDERING_INSTRUCTION);
@ -298,7 +303,7 @@ public class XmlThemeBuilder extends DefaultHandler {
} else if ("lineSymbol".equals(localName)) {
checkState(localName, Element.RENDERING_INSTRUCTION);
log.error("unknown element: {}", localName);
handleLineElement(localName, attributes, false, true);
} else if ("atlas".equals(localName)) {
checkState(localName, Element.ATLAS);
@ -439,7 +444,7 @@ public class XmlThemeBuilder extends DefaultHandler {
return texture;
}
private void handleLineElement(String localName, Attributes attributes, boolean isStyle)
private void handleLineElement(String localName, Attributes attributes, boolean isStyle, boolean hasSymbol)
throws SAXException {
String use = attributes.getValue("use");
@ -453,7 +458,7 @@ public class XmlThemeBuilder extends DefaultHandler {
}
}
LineStyle line = createLine(style, localName, attributes, mLevels++, false);
LineStyle line = createLine(style, localName, attributes, mLevels++, false, hasSymbol);
if (isStyle) {
mStyles.put(LINE_STYLE + line.style, line);
@ -479,7 +484,7 @@ public class XmlThemeBuilder extends DefaultHandler {
* @return a new Line with the given rendering attributes.
*/
private LineStyle createLine(LineStyle line, String elementName, Attributes attributes,
int level, boolean isOutline) {
int level, boolean isOutline, boolean hasSymbol) {
LineBuilder<?> b = mLineBuilder.set(line);
b.isOutline(isOutline);
b.level(level);
@ -546,10 +551,13 @@ public class XmlThemeBuilder extends DefaultHandler {
else if ("style".equals(name))
; // ignore
else if ("dasharray".equals(name))
; // TBD
else if ("dasharray".equals(name)) {
b.dashArray = parseFloatArray(value);
for (int j = 0; j < b.dashArray.length; ++j) {
b.dashArray[j] = b.dashArray[j] * mScale;
}
else if ("symbol-width".equals(name))
} else if ("symbol-width".equals(name))
b.symbolWidth = (int) (Integer.parseInt(value) * mScale);
else if ("symbol-height".equals(name))
@ -565,9 +573,72 @@ public class XmlThemeBuilder extends DefaultHandler {
logUnknownAttribute(elementName, name, value, i);
}
b.texture = Utils.loadTexture(mTheme.getRelativePathPrefix(), src, b.symbolWidth, b.symbolHeight, b.symbolPercent);
/*if (b.texture != null)
b.texture.mipmap = true;*/
if (b.dashArray != null) {
// Create a dashed texture
int bmpWidth = 0;
int bmpHeight = (int) (b.strokeWidth);
if (bmpHeight < 1)
bmpHeight = 2;
for (float f : b.dashArray) {
if (f < 1)
f = 1;
bmpWidth += f;
}
int factor = 10;
Bitmap bmp = CanvasAdapter.newBitmap(bmpWidth * factor, bmpHeight * factor, 0);
Canvas canvas = CanvasAdapter.newCanvas();
canvas.setBitmap(bmp);
boolean bw = false;
int x = 0;
for (float f : b.dashArray) {
if (f < 1)
f = 1;
canvas.fillRectangle(x * factor, 0, f * factor, bmpHeight * factor, (bw ? Color.TRANSPARENT : Color.WHITE));
x += f;
bw = !bw;
}
b.texture = new TextureItem(bmp);
b.texture.mipmap = false;
b.stipple = (int) (bmpWidth * 1.2f);
b.stippleWidth = bmpWidth;
b.fixed = false;
b.randomOffset = false;
b.stippleColor = b.fillColor;
b.fillColor = Color.TRANSPARENT;
b.strokeColor = Color.TRANSPARENT;
} else {
b.texture = Utils.loadTexture(mTheme.getRelativePathPrefix(), src, b.symbolWidth, b.symbolHeight, b.symbolPercent);
if (hasSymbol) {
// We have no way to set a repeat gap for the renderer,
// so we create a texture that already contains this repeat gap.
float repeatGap = REPEAT_GAP_DEFAULT * mScale;
float repeatStart = REPEAT_START_DEFAULT * mScale;
int width = (int) (b.texture.width + repeatGap);
int height = b.texture.height;
Bitmap bmp = CanvasAdapter.newBitmap(width, height, 0);
Canvas canvas = CanvasAdapter.newCanvas();
canvas.setBitmap(bmp);
canvas.drawBitmap(b.texture.bitmap, repeatStart, 0);
b.texture = new TextureItem(bmp);
// We must set stipple values
// The multipliers are determined empirically to
// correspond to the representation at Mapsforge.
b.stipple = b.texture.width * 3;
b.strokeWidth *= 2 * mScale;
// Use texture color
b.stippleColor = Color.WHITE;
b.fillColor = Color.TRANSPARENT;
b.strokeColor = Color.TRANSPARENT;
b.fixed = false;
}
}
return b.build();
}
@ -1111,6 +1182,15 @@ public class XmlThemeBuilder extends DefaultHandler {
return mCategories == null || rule.cat == null || mCategories.contains(rule.cat);
}
private static float[] parseFloatArray(String dashString) {
String[] dashEntries = dashString.split(",");
float[] dashIntervals = new float[dashEntries.length];
for (int i = 0; i < dashEntries.length; ++i) {
dashIntervals[i] = Float.parseFloat(dashEntries[i]);
}
return dashIntervals;
}
private static void validateNonNegative(String name, float value) {
if (value < 0)
throw new ThemeException(name + " must not be negative: "