From 577e45cd979a6ae9115eab628acb95a0e09f8d7e Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Fri, 18 Mar 2016 20:32:14 +0100 Subject: [PATCH] Do not rotate map canvas after composition This requires to draw tile layers to an intermediate canvas again, but only when the view is rotated. --- src/ol/renderer/canvas/canvasmaprenderer.js | 35 ++--------- .../canvas/canvastilelayerrenderer.js | 55 ++++++++++++++---- .../spec/ol/expected/rotate-canvas.png | Bin 753 -> 615 bytes .../spec/ol/layer/expected/render-canvas.png | Bin 0 -> 2483 bytes test_rendering/spec/ol/layer/tile.test.js | 38 ++++++++++++ 5 files changed, 87 insertions(+), 41 deletions(-) create mode 100644 test_rendering/spec/ol/layer/expected/render-canvas.png 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 b46061ae74692dc153b70a1d81bda6b188821eba..e614cf3c0858d3a802822c2595545b11c696150d 100644 GIT binary patch delta 590 zcmV-U0cu(lVHTUV!=6-vi7{j4C>!+$Xr$n7)G?e&^kH zmDgW?YyAJmn0PMJcmb_FL$ODUXP&vp&(16`KTxwzj*lJe34a)9J^09S{ms`23=9l` zXVXnK(9$y$c?6W4SXdaQtPazzM)w5F(Yf1i`<{6GN$*U$iTt4Ti1Caw8}uc3c;@=6 zufpZXN1uLj2CcX<;V&bz;e|BA3$*eKMKNMI=j<_KDc-k}Jykd3bD+trvortx`|o%m z)AYcg^oZH4Gk=H8WcWS|d5;*+JhMQYo6US{v~EAHkY0H4h1%qkPmBMuF$-QuH2g3q zW5i(kc?D*szxM;xMHf`t%N<8|Bq)ExufLxE`RCt5XVXnUr5DWrMR^3|DsV-}^nbpU zEdS;bYuUZ9(hC$53og6}`ug+l*WYZ+wzP~5kUJ>$2!F^B@Ovx&$)`mW;*-34uiyrK2%ImtZErlF=9; zA+YG8bVoEs=n~8%STY(TBm@>+l#a#-U4oeeOGaaagutSU($N^9OE8mQ$!Ls_5Lk3k cI&5MD0I=SoGlY$iq5uE@07*qoM6N<$g2yW!-v9sr delta 729 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 literal 0 HcmV?d00001 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');