Batch polygon and circle fills and strokes

This commit is contained in:
Andreas Hocevar
2016-04-07 18:03:57 +02:00
parent 343d2c032d
commit 091dc9fbf4
6 changed files with 246 additions and 5 deletions

View File

@@ -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.

View File

@@ -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;

View File

@@ -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() {

View File

@@ -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');

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -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');