diff --git a/src/ol/renderer/canvas/canvasmaprenderer.js b/src/ol/renderer/canvas/canvasmaprenderer.js index 82aeddf79d..2a19ddd023 100644 --- a/src/ol/renderer/canvas/canvasmaprenderer.js +++ b/src/ol/renderer/canvas/canvasmaprenderer.js @@ -19,6 +19,7 @@ goog.require('ol.layer.Vector'); goog.require('ol.layer.VectorTile'); goog.require('ol.render.Event'); goog.require('ol.render.EventType'); +goog.require('ol.render.canvas'); goog.require('ol.render.canvas.Immediate'); goog.require('ol.renderer.Map'); goog.require('ol.renderer.canvas.ImageLayer'); @@ -181,7 +182,7 @@ ol.renderer.canvas.Map.prototype.renderFrame = function(frameState) { return; } - var context; + var context = this.context_; var pixelRatio = frameState.pixelRatio; var width = Math.round(frameState.size[0] * pixelRatio); var height = Math.round(frameState.size[1] * pixelRatio); @@ -189,29 +190,10 @@ ol.renderer.canvas.Map.prototype.renderFrame = function(frameState) { this.canvas_.width = width; this.canvas_.height = height; } else { - this.context_.clearRect(0, 0, width, height); + context.clearRect(0, 0, width, height); } var rotation = frameState.viewState.rotation; - var pixelExtent; - if (rotation) { - context = this.renderContext_; - pixelExtent = ol.extent.getForViewAndSize(this.pixelCenter_, pixelRatio, - rotation, frameState.size, this.pixelExtent_); - var renderWidth = Math.round(ol.extent.getWidth(pixelExtent)); - var renderHeight = Math.round(ol.extent.getHeight(pixelExtent)); - var renderCanvas = this.renderCanvas_; - if (renderCanvas.width != renderWidth || renderCanvas.height != renderHeight) { - renderCanvas.width = renderWidth; - renderCanvas.height = renderHeight; - this.renderContext_.translate(Math.round((renderWidth - width) / 2), - Math.round((renderHeight - height) / 2)); - } else { - this.renderContext_.clearRect(0, 0, renderWidth, renderHeight); - } - } else { - context = this.context_; - } this.calculateMatrices2D(frameState); @@ -220,6 +202,8 @@ ol.renderer.canvas.Map.prototype.renderFrame = function(frameState) { var layerStatesArray = frameState.layerStatesArray; ol.array.stableSort(layerStatesArray, ol.renderer.Map.sortByZIndex); + ol.render.canvas.rotateAtOffset(context, rotation, width / 2, height / 2); + var viewResolution = frameState.viewState.resolution; var i, ii, layer, layerRenderer, layerState; for (i = 0, ii = layerStatesArray.length; i < ii; ++i) { @@ -237,14 +221,7 @@ ol.renderer.canvas.Map.prototype.renderFrame = function(frameState) { } } - if (rotation) { - this.context_.translate(width / 2, height / 2); - this.context_.rotate(rotation); - this.context_.drawImage(this.renderCanvas_, - Math.round(pixelExtent[0]), Math.round(pixelExtent[1])); - this.context_.rotate(-rotation); - this.context_.translate(-width / 2, -height / 2); - } + ol.render.canvas.rotateAtOffset(context, -rotation, width / 2, height / 2); this.dispatchComposeEvent_( ol.render.EventType.POSTCOMPOSE, frameState); diff --git a/src/ol/renderer/canvas/canvastilelayerrenderer.js b/src/ol/renderer/canvas/canvastilelayerrenderer.js index 68ac5a8625..983c61f3bf 100644 --- a/src/ol/renderer/canvas/canvastilelayerrenderer.js +++ b/src/ol/renderer/canvas/canvastilelayerrenderer.js @@ -3,6 +3,7 @@ goog.provide('ol.renderer.canvas.TileLayer'); goog.require('goog.asserts'); +goog.require('goog.vec.Mat4'); goog.require('ol.TileRange'); goog.require('ol.TileState'); goog.require('ol.array'); @@ -12,6 +13,7 @@ goog.require('ol.layer.Tile'); goog.require('ol.render.EventType'); goog.require('ol.renderer.canvas.Layer'); goog.require('ol.source.Tile'); +goog.require('ol.vec.Mat4'); /** @@ -41,6 +43,12 @@ ol.renderer.canvas.TileLayer = function(tileLayer) { */ this.tmpExtent_ = ol.extent.createEmpty(); + /** + * @private + * @type {!goog.vec.Mat4.Number} + */ + this.imageTransform_ = goog.vec.Mat4.createNumber(); + }; goog.inherits(ol.renderer.canvas.TileLayer, ol.renderer.canvas.Layer); @@ -55,7 +63,10 @@ ol.renderer.canvas.TileLayer.prototype.composeFrame = function( var center = viewState.center; var projection = viewState.projection; var resolution = viewState.resolution; + var rotation = viewState.rotation; var size = frameState.size; + var offsetX = Math.round(pixelRatio * size[0] / 2); + var offsetY = Math.round(pixelRatio * size[1] / 2); var pixelScale = pixelRatio / resolution; var layer = this.getLayer(); var source = layer.getSource(); @@ -67,17 +78,29 @@ ol.renderer.canvas.TileLayer.prototype.composeFrame = function( this.dispatchPreComposeEvent(context, frameState, transform); - var renderContext; - if (layer.hasListener(ol.render.EventType.RENDER)) { - // resize and clear - this.context_.canvas.width = context.canvas.width; - this.context_.canvas.height = context.canvas.height; + var renderContext = context; + var hasRenderListeners = layer.hasListener(ol.render.EventType.RENDER); + var drawOffsetX, drawOffsetY, drawScale, drawSize; + if (rotation || hasRenderListeners) { renderContext = this.context_; - } else { - renderContext = context; + var renderCanvas = renderContext.canvas; + var tilePixelRatio = source.getTilePixelRatio(pixelRatio); + drawScale = tilePixelRatio / pixelRatio; + var width = context.canvas.width * drawScale; + var height = context.canvas.height * drawScale; + // Make sure the canvas is big enough for all possible rotation angles + drawSize = Math.round(Math.sqrt(width * width + height * height)); + if (renderCanvas.width != drawSize) { + renderCanvas.width = renderCanvas.height = drawSize; + } else { + renderContext.clearRect(0, 0, drawSize, drawSize); + } + drawOffsetX = (drawSize - width) / 2 / drawScale; + drawOffsetY = (drawSize - height) / 2 / drawScale; + pixelScale *= drawScale; + offsetX = Math.round(drawScale * (offsetX + drawOffsetX)) + offsetY = Math.round(drawScale * (offsetY + drawOffsetY)); } - var offsetX = Math.round(pixelRatio * size[0] / 2); - var offsetY = Math.round(pixelRatio * size[1] / 2); // for performance reasons, context.save / context.restore is not used // to save and restore the transformation matrix and the opacity. @@ -142,9 +165,17 @@ ol.renderer.canvas.TileLayer.prototype.composeFrame = function( } } - if (renderContext != context) { - this.dispatchRenderEvent(renderContext, frameState, transform); - context.drawImage(renderContext.canvas, 0, 0); + if (hasRenderListeners) { + var dX = drawOffsetX - offsetX / drawScale + offsetX; + var dY = drawOffsetY - offsetY / drawScale + offsetY; + var imageTransform = ol.vec.Mat4.makeTransform2D(this.imageTransform_, + drawSize / 2 - dX, drawSize / 2 - dY, pixelScale, -pixelScale, + -rotation, -center[0] + dX / pixelScale, -center[1] - dY / pixelScale); + this.dispatchRenderEvent(renderContext, frameState, imageTransform); + } + if (rotation || hasRenderListeners) { + context.drawImage(renderContext.canvas, -Math.round(drawOffsetX), + -Math.round(drawOffsetY), drawSize / drawScale, drawSize / drawScale); } renderContext.globalAlpha = alpha; diff --git a/test_rendering/spec/ol/expected/rotate-canvas.png b/test_rendering/spec/ol/expected/rotate-canvas.png index b46061ae74..e614cf3c08 100644 Binary files a/test_rendering/spec/ol/expected/rotate-canvas.png and b/test_rendering/spec/ol/expected/rotate-canvas.png differ diff --git a/test_rendering/spec/ol/layer/expected/render-canvas.png b/test_rendering/spec/ol/layer/expected/render-canvas.png new file mode 100644 index 0000000000..eb8723647b Binary files /dev/null and b/test_rendering/spec/ol/layer/expected/render-canvas.png differ diff --git a/test_rendering/spec/ol/layer/tile.test.js b/test_rendering/spec/ol/layer/tile.test.js index d41d6cf560..6113f07db1 100644 --- a/test_rendering/spec/ol/layer/tile.test.js +++ b/test_rendering/spec/ol/layer/tile.test.js @@ -188,13 +188,51 @@ describe('ol.rendering.layer.Tile', function() { }); + describe('tile layer with render listener', function() { + var source, onAddLayer; + + beforeEach(function() { + source = new ol.source.XYZ({ + url: 'spec/ol/data/tiles/osm/{z}/{x}/{y}.png' + }); + onAddLayer = function(evt) { + evt.element.on('render', function(e) { + e.vectorContext.setImageStyle(new ol.style.Circle({ + radius: 5, + snapToPixel: false, + fill: new ol.style.Fill({color: 'yellow'}), + stroke: new ol.style.Stroke({color: 'red', width: 1}) + })); + e.vectorContext.drawPointGeometry(new ol.geom.Point( + ol.proj.transform([-123, 38], 'EPSG:4326', 'EPSG:3857'))); + }); + } + }); + + afterEach(function() { + disposeMap(map); + }); + + it('works with the canvas renderer', function(done) { + map = createMap('canvas'); + map.getLayers().on('add', onAddLayer); + waitForTiles([source], {}, function() { + expectResemble(map, 'spec/ol/layer/expected/render-canvas.png', + 2.6, done); + }); + }); + }); }); goog.require('ol.Map'); goog.require('ol.View'); +goog.require('ol.geom.Point'); goog.require('ol.layer.Tile'); goog.require('ol.object'); goog.require('ol.proj'); goog.require('ol.source.TileImage'); goog.require('ol.source.XYZ'); +goog.require('ol.style.Circle'); +goog.require('ol.style.Fill'); +goog.require('ol.style.Stroke'); goog.require('ol.tilegrid.TileGrid');