Batch polygon and circle fills and strokes
This commit is contained in:
@@ -183,6 +183,16 @@ ol.color.fromStringInternal_ = function(s) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {ol.ColorLike|string} color Color.
|
||||||
|
* @return {boolean} Is rgba.
|
||||||
|
*/
|
||||||
|
ol.color.isRgba = function(color) {
|
||||||
|
return Array.isArray(color) && color.length == 4 ||
|
||||||
|
typeof color == 'string' && ol.color.rgbaColorRe_.test(color);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {ol.Color} color Color.
|
* @param {ol.Color} color Color.
|
||||||
* @return {boolean} Is valid.
|
* @return {boolean} Is valid.
|
||||||
|
|||||||
@@ -71,6 +71,12 @@ ol.render.canvas.Replay = function(tolerance, maxExtent, resolution) {
|
|||||||
*/
|
*/
|
||||||
this.maxExtent = maxExtent;
|
this.maxExtent = maxExtent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @protected
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
this.transparency = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @type {ol.Extent}
|
* @type {ol.Extent}
|
||||||
@@ -257,6 +263,10 @@ ol.render.canvas.Replay.prototype.replay_ = function(
|
|||||||
var localTransform = this.tmpLocalTransform_;
|
var localTransform = this.tmpLocalTransform_;
|
||||||
var localTransformInv = this.tmpLocalTransformInv_;
|
var localTransformInv = this.tmpLocalTransformInv_;
|
||||||
var prevX, prevY, roundX, roundY;
|
var prevX, prevY, roundX, roundY;
|
||||||
|
var pendingFill = 0;
|
||||||
|
var pendingStroke = 0;
|
||||||
|
var batchSize =
|
||||||
|
this.instructions != instructions || this.transparency ? 0 : 200;
|
||||||
while (i < ii) {
|
while (i < ii) {
|
||||||
var instruction = instructions[i];
|
var instruction = instructions[i];
|
||||||
var type = /** @type {ol.render.canvas.Instruction} */ (instruction[0]);
|
var type = /** @type {ol.render.canvas.Instruction} */ (instruction[0]);
|
||||||
@@ -276,7 +286,17 @@ ol.render.canvas.Replay.prototype.replay_ = function(
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ol.render.canvas.Instruction.BEGIN_PATH:
|
case ol.render.canvas.Instruction.BEGIN_PATH:
|
||||||
context.beginPath();
|
if (pendingFill > batchSize) {
|
||||||
|
context.fill();
|
||||||
|
pendingFill = 0;
|
||||||
|
}
|
||||||
|
if (pendingStroke > batchSize) {
|
||||||
|
context.stroke();
|
||||||
|
pendingStroke = 0;
|
||||||
|
}
|
||||||
|
if (!pendingFill && !pendingStroke) {
|
||||||
|
context.beginPath();
|
||||||
|
}
|
||||||
++i;
|
++i;
|
||||||
break;
|
break;
|
||||||
case ol.render.canvas.Instruction.CIRCLE:
|
case ol.render.canvas.Instruction.CIRCLE:
|
||||||
@@ -290,6 +310,7 @@ ol.render.canvas.Replay.prototype.replay_ = function(
|
|||||||
var dx = x2 - x1;
|
var dx = x2 - x1;
|
||||||
var dy = y2 - y1;
|
var dy = y2 - y1;
|
||||||
var r = Math.sqrt(dx * dx + dy * dy);
|
var r = Math.sqrt(dx * dx + dy * dy);
|
||||||
|
context.moveTo(x2, y2);
|
||||||
context.arc(x1, y1, r, 0, 2 * Math.PI, true);
|
context.arc(x1, y1, r, 0, 2 * Math.PI, true);
|
||||||
++i;
|
++i;
|
||||||
break;
|
break;
|
||||||
@@ -438,7 +459,11 @@ ol.render.canvas.Replay.prototype.replay_ = function(
|
|||||||
++i;
|
++i;
|
||||||
break;
|
break;
|
||||||
case ol.render.canvas.Instruction.FILL:
|
case ol.render.canvas.Instruction.FILL:
|
||||||
context.fill();
|
if (batchSize) {
|
||||||
|
pendingFill++;
|
||||||
|
} else {
|
||||||
|
context.fill();
|
||||||
|
}
|
||||||
++i;
|
++i;
|
||||||
break;
|
break;
|
||||||
case ol.render.canvas.Instruction.MOVE_TO_LINE_TO:
|
case ol.render.canvas.Instruction.MOVE_TO_LINE_TO:
|
||||||
@@ -475,6 +500,11 @@ ol.render.canvas.Replay.prototype.replay_ = function(
|
|||||||
ol.colorlike.isColorLike(instruction[1]),
|
ol.colorlike.isColorLike(instruction[1]),
|
||||||
'2nd instruction should be a string, ' +
|
'2nd instruction should be a string, ' +
|
||||||
'CanvasPattern, or CanvasGradient');
|
'CanvasPattern, or CanvasGradient');
|
||||||
|
if (pendingFill) {
|
||||||
|
context.fill();
|
||||||
|
pendingFill = 0;
|
||||||
|
}
|
||||||
|
|
||||||
context.fillStyle = /** @type {ol.ColorLike} */ (instruction[1]);
|
context.fillStyle = /** @type {ol.ColorLike} */ (instruction[1]);
|
||||||
++i;
|
++i;
|
||||||
break;
|
break;
|
||||||
@@ -494,6 +524,10 @@ ol.render.canvas.Replay.prototype.replay_ = function(
|
|||||||
var usePixelRatio = instruction[7] !== undefined ?
|
var usePixelRatio = instruction[7] !== undefined ?
|
||||||
instruction[7] : true;
|
instruction[7] : true;
|
||||||
var lineWidth = /** @type {number} */ (instruction[2]);
|
var lineWidth = /** @type {number} */ (instruction[2]);
|
||||||
|
if (pendingStroke) {
|
||||||
|
context.stroke();
|
||||||
|
pendingStroke = 0;
|
||||||
|
}
|
||||||
context.strokeStyle = /** @type {string} */ (instruction[1]);
|
context.strokeStyle = /** @type {string} */ (instruction[1]);
|
||||||
context.lineWidth = usePixelRatio ? lineWidth * pixelRatio : lineWidth;
|
context.lineWidth = usePixelRatio ? lineWidth * pixelRatio : lineWidth;
|
||||||
context.lineCap = /** @type {string} */ (instruction[3]);
|
context.lineCap = /** @type {string} */ (instruction[3]);
|
||||||
@@ -519,7 +553,11 @@ ol.render.canvas.Replay.prototype.replay_ = function(
|
|||||||
++i;
|
++i;
|
||||||
break;
|
break;
|
||||||
case ol.render.canvas.Instruction.STROKE:
|
case ol.render.canvas.Instruction.STROKE:
|
||||||
context.stroke();
|
if (batchSize) {
|
||||||
|
pendingStroke++;
|
||||||
|
} else {
|
||||||
|
context.stroke();
|
||||||
|
}
|
||||||
++i;
|
++i;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -528,6 +566,12 @@ ol.render.canvas.Replay.prototype.replay_ = function(
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (pendingFill) {
|
||||||
|
context.fill();
|
||||||
|
}
|
||||||
|
if (pendingStroke) {
|
||||||
|
context.stroke();
|
||||||
|
}
|
||||||
// assert that all instructions were consumed
|
// assert that all instructions were consumed
|
||||||
goog.asserts.assert(i == instructions.length,
|
goog.asserts.assert(i == instructions.length,
|
||||||
'all instructions should be consumed');
|
'all instructions should be consumed');
|
||||||
@@ -547,7 +591,7 @@ ol.render.canvas.Replay.prototype.replay = function(
|
|||||||
context, pixelRatio, transform, viewRotation, skippedFeaturesHash) {
|
context, pixelRatio, transform, viewRotation, skippedFeaturesHash) {
|
||||||
var instructions = this.instructions;
|
var instructions = this.instructions;
|
||||||
this.replay_(context, pixelRatio, transform, viewRotation,
|
this.replay_(context, pixelRatio, transform, viewRotation,
|
||||||
skippedFeaturesHash, instructions, undefined);
|
skippedFeaturesHash, instructions, undefined, undefined);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -1413,6 +1457,10 @@ ol.render.canvas.PolygonReplay.prototype.setFillStrokeStyle = function(fillStyle
|
|||||||
var fillStyleColor = fillStyle.getColor();
|
var fillStyleColor = fillStyle.getColor();
|
||||||
state.fillStyle = ol.colorlike.asColorLike(fillStyleColor ?
|
state.fillStyle = ol.colorlike.asColorLike(fillStyleColor ?
|
||||||
fillStyleColor : ol.render.canvas.defaultFillStyle);
|
fillStyleColor : ol.render.canvas.defaultFillStyle);
|
||||||
|
if (!this.transparency && ol.color.isRgba(state.fillStyle)) {
|
||||||
|
this.transparency = ol.color.asArray(
|
||||||
|
/** @type {ol.Color|string} */ (state.fillStyle))[0] != 1;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
state.fillStyle = undefined;
|
state.fillStyle = undefined;
|
||||||
}
|
}
|
||||||
@@ -1420,6 +1468,9 @@ ol.render.canvas.PolygonReplay.prototype.setFillStrokeStyle = function(fillStyle
|
|||||||
var strokeStyleColor = strokeStyle.getColor();
|
var strokeStyleColor = strokeStyle.getColor();
|
||||||
state.strokeStyle = ol.color.asString(strokeStyleColor ?
|
state.strokeStyle = ol.color.asString(strokeStyleColor ?
|
||||||
strokeStyleColor : ol.render.canvas.defaultStrokeStyle);
|
strokeStyleColor : ol.render.canvas.defaultStrokeStyle);
|
||||||
|
if (!this.transparency && ol.color.isRgba(state.strokeStyle)) {
|
||||||
|
this.transparency = ol.color.asArray(state.strokeStyle)[3] != 1;
|
||||||
|
}
|
||||||
var strokeStyleLineCap = strokeStyle.getLineCap();
|
var strokeStyleLineCap = strokeStyle.getLineCap();
|
||||||
state.lineCap = strokeStyleLineCap !== undefined ?
|
state.lineCap = strokeStyleLineCap !== undefined ?
|
||||||
strokeStyleLineCap : ol.render.canvas.defaultLineCap;
|
strokeStyleLineCap : ol.render.canvas.defaultLineCap;
|
||||||
|
|||||||
@@ -103,6 +103,20 @@ describe('ol.color', function() {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('ol.color.isRgba', function() {
|
||||||
|
it('identifies rgba arrays', function() {
|
||||||
|
expect(ol.color.isRgba([255, 255, 255, 1])).to.be(true);
|
||||||
|
expect(ol.color.isRgba([255, 255, 255])).to.be(false);
|
||||||
|
});
|
||||||
|
it('identifies rgba strings', function() {
|
||||||
|
expect(ol.color.isRgba('rgba(255,255,255,1)')).to.be(true);
|
||||||
|
expect(ol.color.isRgba('rgb(255,255,255)')).to.be(false);
|
||||||
|
expect(ol.color.isRgba('#FFF')).to.be(false);
|
||||||
|
expect(ol.color.isRgba('#FFFFFF')).to.be(false);
|
||||||
|
expect(ol.color.isRgba('red')).to.be(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('ol.color.isValid', function() {
|
describe('ol.color.isValid', function() {
|
||||||
|
|
||||||
it('identifies valid colors', function() {
|
it('identifies valid colors', function() {
|
||||||
|
|||||||
@@ -1,5 +1,139 @@
|
|||||||
goog.provide('ol.test.renderer.canvas.Replay');
|
goog.provide('ol.test.renderer.canvas.Replay');
|
||||||
|
|
||||||
|
describe('ol.render.canvas.ReplayGroup', function() {
|
||||||
|
|
||||||
|
describe('#replay', function() {
|
||||||
|
|
||||||
|
var context, replay, fillCount, strokeCount, beginPathCount;
|
||||||
|
var feature1, feature2, feature3, style1, style2, style3, transform;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
transform = goog.vec.Mat4.createNumber();
|
||||||
|
replay = new ol.render.canvas.ReplayGroup(1, [-180, -90, 180, 90], 1);
|
||||||
|
feature1 = new ol.Feature(new ol.geom.Polygon(
|
||||||
|
[[[-90, -45], [-90, 0], [0, 0], [0, -45], [-90, -45]]]));
|
||||||
|
feature2 = new ol.Feature(new ol.geom.Polygon(
|
||||||
|
[[[90, 45], [90, 0], [0, 0], [0, 45], [90, 45]]]));
|
||||||
|
feature3 = new ol.Feature(new ol.geom.Polygon(
|
||||||
|
[[[-90, -45], [-90, 45], [90, 45], [90, -45], [-90, -45]]]));
|
||||||
|
style1 = new ol.style.Style({
|
||||||
|
fill: new ol.style.Fill({color: 'black'}),
|
||||||
|
stroke: new ol.style.Stroke({color: 'white', width: 1})
|
||||||
|
});
|
||||||
|
style2 = new ol.style.Style({
|
||||||
|
fill: new ol.style.Fill({color: 'white'}),
|
||||||
|
stroke: new ol.style.Stroke({color: 'black', width: 1})
|
||||||
|
});
|
||||||
|
style3 = new ol.style.Style({
|
||||||
|
fill: new ol.style.Fill({color: 'rgba(255,255,255,0.8)'}),
|
||||||
|
stroke: new ol.style.Stroke({color: 'rgba(0,0,0,0.8)', width: 1})
|
||||||
|
});
|
||||||
|
fillCount = 0;
|
||||||
|
strokeCount = 0;
|
||||||
|
beginPathCount = 0;
|
||||||
|
context = {
|
||||||
|
fill: function() {
|
||||||
|
fillCount++;
|
||||||
|
},
|
||||||
|
stroke: function() {
|
||||||
|
strokeCount++;
|
||||||
|
},
|
||||||
|
beginPath: function() {
|
||||||
|
beginPathCount++;
|
||||||
|
},
|
||||||
|
clip: function() {
|
||||||
|
beginPathCount--;
|
||||||
|
},
|
||||||
|
save: function() {},
|
||||||
|
moveTo: function() {},
|
||||||
|
lineTo: function() {},
|
||||||
|
closePath: function() {},
|
||||||
|
setLineDash: function() {},
|
||||||
|
restore: function() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
it('batches fill and stroke instructions for same style', function() {
|
||||||
|
ol.renderer.vector.renderFeature(replay, feature1, style1, 1);
|
||||||
|
ol.renderer.vector.renderFeature(replay, feature2, style1, 1);
|
||||||
|
ol.renderer.vector.renderFeature(replay, feature3, style1, 1);
|
||||||
|
replay.replay(context, 1, transform, 0, {});
|
||||||
|
expect(fillCount).to.be(1);
|
||||||
|
expect(strokeCount).to.be(1);
|
||||||
|
expect(beginPathCount).to.be(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('batches fill and stroke instructions for different styles', function() {
|
||||||
|
ol.renderer.vector.renderFeature(replay, feature1, style1, 1);
|
||||||
|
ol.renderer.vector.renderFeature(replay, feature2, style1, 1);
|
||||||
|
ol.renderer.vector.renderFeature(replay, feature3, style2, 1);
|
||||||
|
replay.replay(context, 1, transform, 0, {});
|
||||||
|
expect(fillCount).to.be(2);
|
||||||
|
expect(strokeCount).to.be(2);
|
||||||
|
expect(beginPathCount).to.be(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('batches fill and stroke instructions for changing styles', function() {
|
||||||
|
ol.renderer.vector.renderFeature(replay, feature1, style1, 1);
|
||||||
|
ol.renderer.vector.renderFeature(replay, feature2, style2, 1);
|
||||||
|
ol.renderer.vector.renderFeature(replay, feature3, style1, 1);
|
||||||
|
replay.replay(context, 1, transform, 0, {});
|
||||||
|
expect(fillCount).to.be(3);
|
||||||
|
expect(strokeCount).to.be(3);
|
||||||
|
expect(beginPathCount).to.be(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('batches fill and stroke instructions for skipped feature at the beginning', function() {
|
||||||
|
ol.renderer.vector.renderFeature(replay, feature1, style1, 1);
|
||||||
|
ol.renderer.vector.renderFeature(replay, feature2, style2, 1);
|
||||||
|
ol.renderer.vector.renderFeature(replay, feature3, style2, 1);
|
||||||
|
var skippedUids = {};
|
||||||
|
skippedUids[goog.getUid(feature1)] = true;
|
||||||
|
replay.replay(context, 1, transform, 0, skippedUids);
|
||||||
|
expect(fillCount).to.be(1);
|
||||||
|
expect(strokeCount).to.be(1);
|
||||||
|
expect(beginPathCount).to.be(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('batches fill and stroke instructions for skipped feature at the end', function() {
|
||||||
|
ol.renderer.vector.renderFeature(replay, feature1, style1, 1);
|
||||||
|
ol.renderer.vector.renderFeature(replay, feature2, style1, 1);
|
||||||
|
ol.renderer.vector.renderFeature(replay, feature3, style2, 1);
|
||||||
|
var skippedUids = {};
|
||||||
|
skippedUids[goog.getUid(feature3)] = true;
|
||||||
|
replay.replay(context, 1, transform, 0, skippedUids);
|
||||||
|
expect(fillCount).to.be(1);
|
||||||
|
expect(strokeCount).to.be(1);
|
||||||
|
expect(beginPathCount).to.be(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('batches fill and stroke instructions for skipped features', function() {
|
||||||
|
ol.renderer.vector.renderFeature(replay, feature1, style1, 1);
|
||||||
|
ol.renderer.vector.renderFeature(replay, feature2, style1, 1);
|
||||||
|
ol.renderer.vector.renderFeature(replay, feature3, style2, 1);
|
||||||
|
var skippedUids = {};
|
||||||
|
skippedUids[goog.getUid(feature1)] = true;
|
||||||
|
skippedUids[goog.getUid(feature2)] = true;
|
||||||
|
replay.replay(context, 1, transform, 0, skippedUids);
|
||||||
|
expect(fillCount).to.be(1);
|
||||||
|
expect(strokeCount).to.be(1);
|
||||||
|
expect(beginPathCount).to.be(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not batch when transparent fills/strokes are used', function() {
|
||||||
|
ol.renderer.vector.renderFeature(replay, feature1, style3, 1);
|
||||||
|
ol.renderer.vector.renderFeature(replay, feature2, style3, 1);
|
||||||
|
ol.renderer.vector.renderFeature(replay, feature3, style3, 1);
|
||||||
|
replay.replay(context, 1, transform, 0, {});
|
||||||
|
expect(fillCount).to.be(3);
|
||||||
|
expect(strokeCount).to.be(3);
|
||||||
|
expect(beginPathCount).to.be(3);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
describe('ol.render.canvas.Replay', function() {
|
describe('ol.render.canvas.Replay', function() {
|
||||||
|
|
||||||
describe('constructor', function() {
|
describe('constructor', function() {
|
||||||
@@ -122,7 +256,14 @@ describe('ol.render.canvas.PolygonReplay', function() {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
goog.require('goog.vec.Mat4');
|
||||||
|
goog.require('ol.Feature');
|
||||||
|
goog.require('ol.geom.Polygon');
|
||||||
goog.require('ol.render.canvas.LineStringReplay');
|
goog.require('ol.render.canvas.LineStringReplay');
|
||||||
goog.require('ol.render.canvas.PolygonReplay');
|
goog.require('ol.render.canvas.PolygonReplay');
|
||||||
goog.require('ol.render.canvas.Replay');
|
goog.require('ol.render.canvas.Replay');
|
||||||
|
goog.require('ol.render.canvas.ReplayGroup');
|
||||||
|
goog.require('ol.renderer.vector');
|
||||||
|
goog.require('ol.style.Fill');
|
||||||
goog.require('ol.style.Stroke');
|
goog.require('ol.style.Stroke');
|
||||||
|
goog.require('ol.style.Style');
|
||||||
|
|||||||
BIN
test_rendering/spec/ol/layer/expected/vector-canvas-opaque.png
Normal file
BIN
test_rendering/spec/ol/layer/expected/vector-canvas-opaque.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
@@ -47,7 +47,7 @@ describe('ol.rendering.layer.Vector', function() {
|
|||||||
disposeMap(map);
|
disposeMap(map);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders correctly with the canvas renderer', function(done) {
|
it('renders opacity correctly with the canvas renderer', function(done) {
|
||||||
map = createMap('canvas');
|
map = createMap('canvas');
|
||||||
var smallLine = new ol.Feature(new ol.geom.LineString([
|
var smallLine = new ol.Feature(new ol.geom.LineString([
|
||||||
[center[0], center[1] - 1],
|
[center[0], center[1] - 1],
|
||||||
@@ -73,6 +73,29 @@ describe('ol.rendering.layer.Vector', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('renders fill/stroke batches correctly with the canvas renderer', function(done) {
|
||||||
|
map = createMap('canvas');
|
||||||
|
addPolygon(100);
|
||||||
|
addCircle(200);
|
||||||
|
addPolygon(250);
|
||||||
|
addCircle(500);
|
||||||
|
addPolygon(600);
|
||||||
|
addPolygon(720);
|
||||||
|
map.addLayer(new ol.layer.Vector({
|
||||||
|
source: source,
|
||||||
|
style: new ol.style.Style({
|
||||||
|
stroke: new ol.style.Stroke({
|
||||||
|
color: '#3399CC',
|
||||||
|
width: 1.25
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}));
|
||||||
|
map.once('postrender', function() {
|
||||||
|
expectResemble(map, 'spec/ol/layer/expected/vector-canvas-opaque.png',
|
||||||
|
17, done);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
@@ -84,3 +107,5 @@ goog.require('ol.geom.Circle');
|
|||||||
goog.require('ol.geom.Polygon');
|
goog.require('ol.geom.Polygon');
|
||||||
goog.require('ol.layer.Vector');
|
goog.require('ol.layer.Vector');
|
||||||
goog.require('ol.source.Vector');
|
goog.require('ol.source.Vector');
|
||||||
|
goog.require('ol.style.Stroke');
|
||||||
|
goog.require('ol.style.Style');
|
||||||
|
|||||||
Reference in New Issue
Block a user