From b71b87d7bbf4ab1c397af77a1a34f09be5c54179 Mon Sep 17 00:00:00 2001 From: philip Date: Sat, 28 Dec 2019 03:40:10 +0000 Subject: [PATCH 01/13] Fix issue with reprojection and double drawing pixels. --- src/ol/reproj.js | 31 +++++-------------------------- src/ol/reproj/Triangulation.js | 2 +- 2 files changed, 6 insertions(+), 27 deletions(-) diff --git a/src/ol/reproj.js b/src/ol/reproj.js index 7593c6bc3b..bb54d07f01 100644 --- a/src/ol/reproj.js +++ b/src/ol/reproj.js @@ -53,24 +53,6 @@ export function calculateSourceResolution(sourceProj, targetProj, } -/** - * Enlarge the clipping triangle point by 1 pixel to ensure the edges overlap - * in order to mask gaps caused by antialiasing. - * - * @param {number} centroidX Centroid of the triangle (x coordinate in pixels). - * @param {number} centroidY Centroid of the triangle (y coordinate in pixels). - * @param {number} x X coordinate of the point (in pixels). - * @param {number} y Y coordinate of the point (in pixels). - * @return {import("./coordinate.js").Coordinate} New point 1 px farther from the centroid. - */ -function enlargeClipPoint(centroidX, centroidY, x, y) { - const dX = x - centroidX; - const dY = y - centroidY; - const distance = Math.sqrt(dX * dX + dY * dY); - return [Math.round(x + dX / distance), Math.round(y + dY / distance)]; -} - - /** * Renders the source data into new canvas based on the triangulation. * @@ -103,6 +85,8 @@ export function render(width, height, pixelRatio, context.scale(pixelRatio, pixelRatio); + context.globalCompositeOperation = 'lighter'; + const sourceDataExtent = createEmpty(); sources.forEach(function(src, i, arr) { extend(sourceDataExtent, src.extent); @@ -190,15 +174,10 @@ export function render(width, height, pixelRatio, context.save(); context.beginPath(); - const centroidX = (u0 + u1 + u2) / 3; - const centroidY = (v0 + v1 + v2) / 3; - const p0 = enlargeClipPoint(centroidX, centroidY, u0, v0); - const p1 = enlargeClipPoint(centroidX, centroidY, u1, v1); - const p2 = enlargeClipPoint(centroidX, centroidY, u2, v2); - context.moveTo(p1[0], p1[1]); - context.lineTo(p0[0], p0[1]); - context.lineTo(p2[0], p2[1]); + context.moveTo(u1, v1); + context.lineTo(u0, v0); + context.lineTo(u2, v2); context.clip(); context.transform( diff --git a/src/ol/reproj/Triangulation.js b/src/ol/reproj/Triangulation.js index ebdab668da..9fe7270abd 100644 --- a/src/ol/reproj/Triangulation.js +++ b/src/ol/reproj/Triangulation.js @@ -321,7 +321,7 @@ class Triangulation { } this.addTriangle_(a, c, d, aSrc, cSrc, dSrc); - this.addTriangle_(a, b, c, aSrc, bSrc, cSrc); + this.addTriangle_(a, c, b, aSrc, cSrc, bSrc); } /** From a6b1df3574bb8f5ea520200a942fbdd7d0caba50 Mon Sep 17 00:00:00 2001 From: philip Date: Sat, 28 Dec 2019 17:47:53 +0000 Subject: [PATCH 02/13] Fix missing corners of the world --- src/ol/reproj.js | 31 ++++++++++++++++++++++++++++++- src/ol/reproj/Tile.js | 9 ++++----- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/src/ol/reproj.js b/src/ol/reproj.js index bb54d07f01..e669a4ec82 100644 --- a/src/ol/reproj.js +++ b/src/ol/reproj.js @@ -2,7 +2,7 @@ * @module ol/reproj */ import {createCanvasContext2D} from './dom.js'; -import {containsCoordinate, createEmpty, extend, getHeight, getTopLeft, getWidth} from './extent.js'; +import {containsCoordinate, createEmpty, extend, forEachCorner, getCenter, getHeight, getTopLeft, getWidth} from './extent.js'; import {solveLinearSystem} from './math.js'; import {getPointResolution, transform} from './proj.js'; @@ -53,6 +53,35 @@ export function calculateSourceResolution(sourceProj, targetProj, } +/** + * Calculates ideal resolution to use from the source in order to achieve + * pixel mapping as close as possible to 1:1 during reprojection. + * The resolution is calculated regardless of what resolutions + * are actually available in the dataset (TileGrid, Image, ...). + * + * @param {import("./proj/Projection.js").default} sourceProj Source projection. + * @param {import("./proj/Projection.js").default} targetProj Target projection. + * @param {import("./extent.js").Extent} targetExtent Target extent + * @param {number} targetResolution Target resolution. + * @return {number} The best resolution to use. Can be +-Infinity, NaN or 0. + */ +export function calculateSourceExtentResolution(sourceProj, targetProj, + targetExtent, targetResolution) { + + const targetCenter = getCenter(targetExtent); + let sourceResolution = calculateSourceResolution(sourceProj, targetProj, targetCenter, targetResolution); + + if (!isFinite(sourceResolution) || sourceResolution <= 0) { + forEachCorner(targetExtent, function(corner) { + sourceResolution = calculateSourceResolution(sourceProj, targetProj, corner, targetResolution); + return isFinite(sourceResolution) && sourceResolution > 0; + }); + } + + return sourceResolution; +} + + /** * Renders the source data into new canvas based on the triangulation. * diff --git a/src/ol/reproj/Tile.js b/src/ol/reproj/Tile.js index 909b125e7e..648431ac7f 100644 --- a/src/ol/reproj/Tile.js +++ b/src/ol/reproj/Tile.js @@ -7,9 +7,9 @@ import Tile from '../Tile.js'; import TileState from '../TileState.js'; import {listen, unlistenByKey} from '../events.js'; import EventType from '../events/EventType.js'; -import {getArea, getCenter, getIntersection} from '../extent.js'; +import {getArea, getIntersection} from '../extent.js'; import {clamp} from '../math.js'; -import {calculateSourceResolution, render as renderReprojected} from '../reproj.js'; +import {calculateSourceExtentResolution, render as renderReprojected} from '../reproj.js'; import Triangulation from './Triangulation.js'; @@ -140,9 +140,8 @@ class ReprojTile extends Tile { const targetResolution = targetTileGrid.getResolution( this.wrappedTileCoord_[0]); - const targetCenter = getCenter(limitedTargetExtent); - const sourceResolution = calculateSourceResolution( - sourceProj, targetProj, targetCenter, targetResolution); + const sourceResolution = calculateSourceExtentResolution( + sourceProj, targetProj, limitedTargetExtent, targetResolution); if (!isFinite(sourceResolution) || sourceResolution <= 0) { // invalid sourceResolution -> EMPTY From 4040d03ae6ef8054671fda169d239e4dba810b12 Mon Sep 17 00:00:00 2001 From: philip Date: Sat, 28 Dec 2019 18:09:07 +0000 Subject: [PATCH 03/13] Fix problem with zero size canvas drawing --- src/ol/reproj.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/ol/reproj.js b/src/ol/reproj.js index e669a4ec82..32ded4f1a5 100644 --- a/src/ol/reproj.js +++ b/src/ol/reproj.js @@ -135,12 +135,15 @@ export function render(width, height, pixelRatio, const srcWidth = getWidth(src.extent); const srcHeight = getHeight(src.extent); - stitchContext.drawImage( - src.image, - gutter, gutter, - src.image.width - 2 * gutter, src.image.height - 2 * gutter, - xPos * stitchScale, yPos * stitchScale, - srcWidth * stitchScale, srcHeight * stitchScale); + // This test should never fail -- but it does. Need to find a fix the upstream condition + if (src.image.width > 0 && src.image.height > 0) { + stitchContext.drawImage( + src.image, + gutter, gutter, + src.image.width - 2 * gutter, src.image.height - 2 * gutter, + xPos * stitchScale, yPos * stitchScale, + srcWidth * stitchScale, srcHeight * stitchScale); + } }); const targetTopLeft = getTopLeft(targetExtent); From f457093baf8650edf638c577c6e0cbf033d095b5 Mon Sep 17 00:00:00 2001 From: philip Date: Sat, 28 Dec 2019 22:31:30 +0000 Subject: [PATCH 04/13] Handle the zoomed out case where the source extent is infinite. This does raise the question of whether an Infinite extent intersects a finite extent. It appears not to, but maybe it should. --- src/ol/reproj/Triangulation.js | 42 +++++++++++++++++++++++++++++----- 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/src/ol/reproj/Triangulation.js b/src/ol/reproj/Triangulation.js index 9fe7270abd..d5c7580584 100644 --- a/src/ol/reproj/Triangulation.js +++ b/src/ol/reproj/Triangulation.js @@ -247,12 +247,19 @@ class Triangulation { } if (!needsSubdivision && this.maxSourceExtent_) { - if (!intersects(sourceQuadExtent, this.maxSourceExtent_)) { - // whole quad outside source projection extent -> ignore - return; + if (isFinite(sourceQuadExtent[0]) && + isFinite(sourceQuadExtent[1]) && + isFinite(sourceQuadExtent[2]) && + isFinite(sourceQuadExtent[3])) { + if (!intersects(sourceQuadExtent, this.maxSourceExtent_)) { + // whole quad outside source projection extent -> ignore + return; + } } } + let isNotFinite = 0; + if (!needsSubdivision) { if (!isFinite(aSrc[0]) || !isFinite(aSrc[1]) || !isFinite(bSrc[0]) || !isFinite(bSrc[1]) || @@ -261,7 +268,16 @@ class Triangulation { if (maxSubdivision > 0) { needsSubdivision = true; } else { - return; + // It might be the case that only 1 of the points is infinite. In this case + // we can draw a single triangle with the other three points + isNotFinite = + (((!isFinite(aSrc[0]) || !isFinite(aSrc[1])) | 0) << 3) + + (((!isFinite(bSrc[0]) || !isFinite(bSrc[1])) | 0) << 2) + + (((!isFinite(cSrc[0]) || !isFinite(cSrc[1])) | 0) << 1) + + ((!isFinite(dSrc[0]) || !isFinite(dSrc[1])) | 0); + if (isNotFinite != 1 && isNotFinite != 2 && isNotFinite != 4 && isNotFinite != 8) { + return; + } } } } @@ -320,8 +336,22 @@ class Triangulation { this.wrapsXInSource_ = true; } - this.addTriangle_(a, c, d, aSrc, cSrc, dSrc); - this.addTriangle_(a, c, b, aSrc, cSrc, bSrc); + // Exactly zero or one of *Src is not finite + if ((isNotFinite & 0xb) == 0) { + this.addTriangle_(a, c, d, aSrc, cSrc, dSrc); + } + if ((isNotFinite & 0xe) == 0) { + this.addTriangle_(a, c, b, aSrc, cSrc, bSrc); + } + if (isNotFinite) { + // Try the other two triangles + if ((isNotFinite & 0xd) == 0) { + this.addTriangle_(b, d, a, bSrc, dSrc, aSrc); + } + if ((isNotFinite & 0x7) == 0) { + this.addTriangle_(b, d, c, bSrc, dSrc, cSrc); + } + } } /** From 89ed757273b596eff023552dd9e8723df09e9be0 Mon Sep 17 00:00:00 2001 From: philip Date: Mon, 30 Dec 2019 16:22:44 +0000 Subject: [PATCH 05/13] Fix indentation --- src/ol/reproj/Triangulation.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ol/reproj/Triangulation.js b/src/ol/reproj/Triangulation.js index d5c7580584..bc3f24e522 100644 --- a/src/ol/reproj/Triangulation.js +++ b/src/ol/reproj/Triangulation.js @@ -251,10 +251,10 @@ class Triangulation { isFinite(sourceQuadExtent[1]) && isFinite(sourceQuadExtent[2]) && isFinite(sourceQuadExtent[3])) { - if (!intersects(sourceQuadExtent, this.maxSourceExtent_)) { - // whole quad outside source projection extent -> ignore - return; - } + if (!intersects(sourceQuadExtent, this.maxSourceExtent_)) { + // whole quad outside source projection extent -> ignore + return; + } } } From e35795c5a38d4d216128e633dbb2db5b84beef1d Mon Sep 17 00:00:00 2001 From: philip Date: Mon, 30 Dec 2019 16:46:20 +0000 Subject: [PATCH 06/13] Rework code to pass eslint --- src/ol/reproj/Triangulation.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ol/reproj/Triangulation.js b/src/ol/reproj/Triangulation.js index bc3f24e522..1f56a7a82c 100644 --- a/src/ol/reproj/Triangulation.js +++ b/src/ol/reproj/Triangulation.js @@ -271,10 +271,10 @@ class Triangulation { // It might be the case that only 1 of the points is infinite. In this case // we can draw a single triangle with the other three points isNotFinite = - (((!isFinite(aSrc[0]) || !isFinite(aSrc[1])) | 0) << 3) + - (((!isFinite(bSrc[0]) || !isFinite(bSrc[1])) | 0) << 2) + - (((!isFinite(cSrc[0]) || !isFinite(cSrc[1])) | 0) << 1) + - ((!isFinite(dSrc[0]) || !isFinite(dSrc[1])) | 0); + ((!isFinite(aSrc[0]) || !isFinite(aSrc[1])) ? 8 : 0) + + ((!isFinite(bSrc[0]) || !isFinite(bSrc[1])) ? 4 : 0) + + ((!isFinite(cSrc[0]) || !isFinite(cSrc[1])) ? 2 : 0) + + ((!isFinite(dSrc[0]) || !isFinite(dSrc[1])) ? 1 : 0); if (isNotFinite != 1 && isNotFinite != 2 && isNotFinite != 4 && isNotFinite != 8) { return; } From 902ed53999bb398e101c824029bee601bffdf7bb Mon Sep 17 00:00:00 2001 From: philip Date: Mon, 6 Jan 2020 02:23:58 +0000 Subject: [PATCH 07/13] Add detection of browsers which cannot render correctly with diagonal clipping regions. In this case, modify the shape of the clip region so that it has (a number) of horizontal and vertical edges. --- src/ol/TileCache.js | 3 +- src/ol/reproj.js | 95 ++++++++++++++++++++++++++++++++-- src/ol/reproj/Triangulation.js | 3 ++ 3 files changed, 97 insertions(+), 4 deletions(-) diff --git a/src/ol/TileCache.js b/src/ol/TileCache.js index e71cfd4bc3..5d02d2a2b0 100644 --- a/src/ol/TileCache.js +++ b/src/ol/TileCache.js @@ -24,7 +24,8 @@ class TileCache extends LRUCache { if (tile.getKey() in usedTiles) { break; } else { - this.pop().dispose(); + // This lets the GC clean up the object + this.pop(); } } } diff --git a/src/ol/reproj.js b/src/ol/reproj.js index 32ded4f1a5..2e3fd7f1ec 100644 --- a/src/ol/reproj.js +++ b/src/ol/reproj.js @@ -6,6 +6,68 @@ import {containsCoordinate, createEmpty, extend, forEachCorner, getCenter, getHe import {solveLinearSystem} from './math.js'; import {getPointResolution, transform} from './proj.js'; +let brokenDiagonalRendering_; + +/** + * This draws a small triangle into a canvas by setting the triangle as the clip region + * and then drawing a (too large) rectangle + * + * @param {CanvasRenderingContext2D} ctx The context in which to draw the triangle + * @param {number} u1 The x-coordinate of the second point. The first point is 0,0. + * @param {number} v1 The y-coordinate of the second point. + * @param {number} u2 The x-coordinate of the third point. + * @param {number} v2 The y-coordinate of the third point. + */ +function drawTestTriangle(ctx, u1, v1, u2, v2) { + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(u1, v1); + ctx.lineTo(u2, v2); + ctx.closePath(); + ctx.save(); + ctx.clip(); + ctx.fillRect(0, 0, Math.max(u1, u2) + 1, Math.max(v1, v2)); + ctx.restore(); +} + +/** + * Given the data from getImageData, see if the right values appear at the provided offset. + * Returns true if either the color or transparency is off + * + * @param {Uint8ClampedArray} data The data returned from getImageData + * @param {number} offset The pixel offset from the start of data. + * @return {boolean} true if the diagonal rendering is broken + */ +function verifyBrokenDiagonalRendering(data, offset) { + // the values ought to be close to the rgba(210, 0, 0, 0.75) + return Math.abs(data[offset * 4] - 210) > 2 || Math.abs(data[offset * 4 + 3] - 0.75 * 255) > 2; +} + +/** + * Determines if the current browser configuration can render triangular clip regions correctly. + * This value is cached so the function is only expensive the first time called. + * Firefox on Windows (as of now) does not if HWA is enabled. See https://bugzilla.mozilla.org/show_bug.cgi?id=1606976 + * IE also doesn't. Chrome works, and everything seems to work on OSX and Android. This function caches the + * result. I suppose that it is conceivably possible that a browser might flip modes while the app is + * running, but lets hope not. + * + * @return {boolean} true if the Diagonal Rendering is broken. + */ +function isBrokenDiagonalRendering() { + if (brokenDiagonalRendering_ === undefined) { + const ctx = document.createElement('canvas').getContext('2d'); + ctx.globalCompositeOperation = 'lighter'; + ctx.fillStyle = 'rgba(210, 0, 0, 0.75)'; + drawTestTriangle(ctx, 4, 5, 4, 0); + drawTestTriangle(ctx, 4, 5, 0, 5); + const data = ctx.getImageData(0, 0, 3, 3).data; + brokenDiagonalRendering_ = verifyBrokenDiagonalRendering(data, 0) || + verifyBrokenDiagonalRendering(data, 4) || + verifyBrokenDiagonalRendering(data, 8); + } + + return brokenDiagonalRendering_; +} /** * Calculates ideal resolution to use from the source in order to achieve @@ -207,9 +269,36 @@ export function render(width, height, pixelRatio, context.save(); context.beginPath(); - context.moveTo(u1, v1); - context.lineTo(u0, v0); - context.lineTo(u2, v2); + if (isBrokenDiagonalRendering()) { + // Make sure that everything is on pixel boundaries + const u0r = Math.round(u0); + const v0r = Math.round(v0); + const u1r = Math.round(u1); + const v1r = Math.round(v1); + const u2r = Math.round(u2); + const v2r = Math.round(v2); + // Make sure that all lines are horizontal or vertical + context.moveTo(u1r, v1r); + // This is the diagonal line. Do it in 4 steps + const steps = 4; + const ud = u0r - u1r; + const vd = v0r - v1r; + for (let step = 0; step < steps; step++) { + // Go horizontally + context.lineTo(u1r + Math.round((step + 1) * ud / steps), v1r + Math.round(step * vd / (steps - 1))); + // Go vertically + if (step != (steps - 1)) { + context.lineTo(u1r + Math.round((step + 1) * ud / steps), v1r + Math.round((step + 1) * vd / (steps - 1))); + } + } + // We are almost at u0r, v0r + context.lineTo(u2r, v2r); + } else { + context.moveTo(u1, v1); + context.lineTo(u0, v0); + context.lineTo(u2, v2); + } + context.clip(); context.transform( diff --git a/src/ol/reproj/Triangulation.js b/src/ol/reproj/Triangulation.js index 1f56a7a82c..5375947ead 100644 --- a/src/ol/reproj/Triangulation.js +++ b/src/ol/reproj/Triangulation.js @@ -337,6 +337,9 @@ class Triangulation { } // Exactly zero or one of *Src is not finite + // The triangles must have the diagonal line as the first side + // This is to allow easy code in reproj.s to make it straight for broken + // browsers that can't handle diagonal clipping if ((isNotFinite & 0xb) == 0) { this.addTriangle_(a, c, d, aSrc, cSrc, dSrc); } From 852f6552c7dae58996893ba271c672d082f71fca Mon Sep 17 00:00:00 2001 From: philip Date: Sat, 11 Jan 2020 17:26:52 +0000 Subject: [PATCH 08/13] Fix pixel rounding --- src/ol/reproj.js | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/ol/reproj.js b/src/ol/reproj.js index 2e3fd7f1ec..518e815572 100644 --- a/src/ol/reproj.js +++ b/src/ol/reproj.js @@ -176,6 +176,10 @@ export function render(width, height, pixelRatio, context.scale(pixelRatio, pixelRatio); + function pixelRound(value) { + return Math.round(value * pixelRatio) / pixelRatio; + } + context.globalCompositeOperation = 'lighter'; const sourceDataExtent = createEmpty(); @@ -271,12 +275,12 @@ export function render(width, height, pixelRatio, if (isBrokenDiagonalRendering()) { // Make sure that everything is on pixel boundaries - const u0r = Math.round(u0); - const v0r = Math.round(v0); - const u1r = Math.round(u1); - const v1r = Math.round(v1); - const u2r = Math.round(u2); - const v2r = Math.round(v2); + const u0r = pixelRound(u0); + const v0r = pixelRound(v0); + const u1r = pixelRound(u1); + const v1r = pixelRound(v1); + const u2r = pixelRound(u2); + const v2r = pixelRound(v2); // Make sure that all lines are horizontal or vertical context.moveTo(u1r, v1r); // This is the diagonal line. Do it in 4 steps @@ -285,10 +289,10 @@ export function render(width, height, pixelRatio, const vd = v0r - v1r; for (let step = 0; step < steps; step++) { // Go horizontally - context.lineTo(u1r + Math.round((step + 1) * ud / steps), v1r + Math.round(step * vd / (steps - 1))); + context.lineTo(u1r + pixelRound((step + 1) * ud / steps), v1r + pixelRound(step * vd / (steps - 1))); // Go vertically if (step != (steps - 1)) { - context.lineTo(u1r + Math.round((step + 1) * ud / steps), v1r + Math.round((step + 1) * vd / (steps - 1))); + context.lineTo(u1r + pixelRound((step + 1) * ud / steps), v1r + pixelRound((step + 1) * vd / (steps - 1))); } } // We are almost at u0r, v0r From 1772c8198b52f8d7576527ef267c7a7e234be542 Mon Sep 17 00:00:00 2001 From: philip Date: Sat, 11 Jan 2020 18:07:09 +0000 Subject: [PATCH 09/13] Added comment pointing to an area of improvement --- src/ol/reproj.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ol/reproj.js b/src/ol/reproj.js index 518e815572..cca5d98ffa 100644 --- a/src/ol/reproj.js +++ b/src/ol/reproj.js @@ -314,6 +314,10 @@ export function render(width, height, pixelRatio, context.scale(sourceResolution / pixelRatio, -sourceResolution / pixelRatio); + // This is rather inefficient as we draw the *entire* source canvas into the destination and + // rely on the clipping to eliminate nearly all the pixels. It seems smarter to only + // draw the relevant region of the source canvas. However, I'm having difficulty figuring + // out what the parameters ought to be. context.drawImage(stitchContext.canvas, 0, 0); context.restore(); }); From 76b926420c2bc39ffe383b17fe583452963aedd2 Mon Sep 17 00:00:00 2001 From: philip Date: Tue, 14 Jan 2020 23:52:54 +0000 Subject: [PATCH 10/13] Only draw the piece of the source canvas that is required. I.e. take the clip region into account. --- src/ol/reproj.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/ol/reproj.js b/src/ol/reproj.js index cca5d98ffa..e0420e7a8c 100644 --- a/src/ol/reproj.js +++ b/src/ol/reproj.js @@ -2,7 +2,7 @@ * @module ol/reproj */ import {createCanvasContext2D} from './dom.js'; -import {containsCoordinate, createEmpty, extend, forEachCorner, getCenter, getHeight, getTopLeft, getWidth} from './extent.js'; +import {boundingExtent, containsCoordinate, createEmpty, extend, forEachCorner, getCenter, getHeight, getTopLeft, getWidth} from './extent.js'; import {solveLinearSystem} from './math.js'; import {getPointResolution, transform} from './proj.js'; @@ -314,11 +314,13 @@ export function render(width, height, pixelRatio, context.scale(sourceResolution / pixelRatio, -sourceResolution / pixelRatio); - // This is rather inefficient as we draw the *entire* source canvas into the destination and - // rely on the clipping to eliminate nearly all the pixels. It seems smarter to only - // draw the relevant region of the source canvas. However, I'm having difficulty figuring - // out what the parameters ought to be. - context.drawImage(stitchContext.canvas, 0, 0); + const sourceTriangleExtent = boundingExtent(source); + const topLeftX = Math.floor((sourceTriangleExtent[0] - sourceDataExtent[0]) * stitchScale); + const topLeftY = Math.floor((sourceDataExtent[3] - sourceTriangleExtent[3]) * stitchScale); + const width = Math.ceil((sourceTriangleExtent[2] - sourceTriangleExtent[0]) * stitchScale) + 2; + const height = Math.ceil((sourceTriangleExtent[3] - sourceTriangleExtent[1]) * stitchScale) + 2; + + context.drawImage(stitchContext.canvas, topLeftX, topLeftY, width, height, topLeftX, topLeftY, width, height); context.restore(); }); From 6e2d8cc26682583e7f0b568fb9df330f76ff5d14 Mon Sep 17 00:00:00 2001 From: philip Date: Fri, 3 Apr 2020 01:46:03 +0000 Subject: [PATCH 11/13] Add test for the reprojection being able to handle translucent layers --- test/spec/ol/reproj/image.test.js | 36 +++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/test/spec/ol/reproj/image.test.js b/test/spec/ol/reproj/image.test.js index cc5d05d12b..d040891460 100644 --- a/test/spec/ol/reproj/image.test.js +++ b/test/spec/ol/reproj/image.test.js @@ -19,6 +19,20 @@ describe('ol.reproj.Image', function() { }); } + function createTranslucentImage(pixelRatio) { + return new ReprojImage( + getProjection('EPSG:3857'), getProjection('EPSG:4326'), + [-180, -85, 180, 85], 10, pixelRatio, + function(extent, resolution, pixelRatio) { + return new ImageWrapper(extent, resolution, pixelRatio, + 'data:image/png;base64,' + + 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8depePQAIiwMjFXlnJQAAAABJRU5ErkJggg==', null, + function(image, src) { + image.getImage().src = src; + }); + }); + } + it('changes state as expected', function(done) { const image = createImage(1); expect(image.getState()).to.be(0); // IDLE @@ -55,4 +69,26 @@ describe('ol.reproj.Image', function() { }); image.load(); }); + + it('has uniform color', function(done) { + const image = createTranslucentImage(1); + listen(image, 'change', function() { + if (image.getState() == 2) { // LOADED + const canvas = image.getImage(); + expect(canvas.width).to.be(36); + expect(canvas.height).to.be(17); + const pixels = canvas.getContext('2d').getImageData(0, 0, canvas.width, canvas.height).data; + + for (let i = 0; i < canvas.width * canvas.height * 4; i += 4) { + expect(pixels[i + 0]).to.be.within(pixels[0] - 2, pixels[0] + 2); + expect(pixels[i + 1]).to.be.within(pixels[1] - 2, pixels[1] + 2); + expect(pixels[i + 2]).to.be.within(pixels[2] - 2, pixels[2] + 2); + expect(pixels[i + 3]).to.be.within(pixels[3] - 2, pixels[3] + 2); + } + done(); + } + }); + image.load(); + }); + }); From 665a8275f6f0421931a6a9f1bfbb84e1f02ccea8 Mon Sep 17 00:00:00 2001 From: philip Date: Fri, 3 Apr 2020 21:54:42 +0000 Subject: [PATCH 12/13] It appears that Safari and Chrome behave differently when doing a drawImage with coordinates that are outside the source. Chrome appears to draw the piece of the image that is within the area specified on the drawImage and within the source. Safari bails and draws nothing if (I think) any of the corners are outside the source. --- src/ol/reproj.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/ol/reproj.js b/src/ol/reproj.js index e0420e7a8c..7e286d0715 100644 --- a/src/ol/reproj.js +++ b/src/ol/reproj.js @@ -314,13 +314,7 @@ export function render(width, height, pixelRatio, context.scale(sourceResolution / pixelRatio, -sourceResolution / pixelRatio); - const sourceTriangleExtent = boundingExtent(source); - const topLeftX = Math.floor((sourceTriangleExtent[0] - sourceDataExtent[0]) * stitchScale); - const topLeftY = Math.floor((sourceDataExtent[3] - sourceTriangleExtent[3]) * stitchScale); - const width = Math.ceil((sourceTriangleExtent[2] - sourceTriangleExtent[0]) * stitchScale) + 2; - const height = Math.ceil((sourceTriangleExtent[3] - sourceTriangleExtent[1]) * stitchScale) + 2; - - context.drawImage(stitchContext.canvas, topLeftX, topLeftY, width, height, topLeftX, topLeftY, width, height); + context.drawImage(stitchContext.canvas, 0, 0); context.restore(); }); From 4e81cf02ee1e14485c81a6ca6ce88f6dcf8ccaea Mon Sep 17 00:00:00 2001 From: philip Date: Fri, 3 Apr 2020 21:58:51 +0000 Subject: [PATCH 13/13] Remove the unused import --- src/ol/reproj.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ol/reproj.js b/src/ol/reproj.js index 7e286d0715..518e815572 100644 --- a/src/ol/reproj.js +++ b/src/ol/reproj.js @@ -2,7 +2,7 @@ * @module ol/reproj */ import {createCanvasContext2D} from './dom.js'; -import {boundingExtent, containsCoordinate, createEmpty, extend, forEachCorner, getCenter, getHeight, getTopLeft, getWidth} from './extent.js'; +import {containsCoordinate, createEmpty, extend, forEachCorner, getCenter, getHeight, getTopLeft, getWidth} from './extent.js'; import {solveLinearSystem} from './math.js'; import {getPointResolution, transform} from './proj.js';