From 624d8a8e3e7a8580bdd813b2c8ef5d9c6446b78d Mon Sep 17 00:00:00 2001 From: Calixte Denizet Date: Thu, 28 Apr 2022 16:25:47 +0200 Subject: [PATCH] Use integer coordinates when drawing images (bug 1264608, issue #3351) - it aims to fix https://bugzilla.mozilla.org/show_bug.cgi?id=1264608; - it's only a partial fix for #3351; - some tiled images have some spurious white lines between the tiles. When the current transform is applyed the corners of an image can have some non-integer coordinates leading to some extra transparency added to handle that. So with this patch the current transform is applied on the point and on the dimensions in order to have at the end only integer values. --- src/display/canvas.js | 104 ++++++++++++++++++++++++++++++--- test/pdfs/issue3351.1.pdf.link | 2 + test/pdfs/issue3351.2.pdf.link | 2 + test/pdfs/issue3351.3.pdf.link | 2 + test/test_manifest.json | 24 ++++++++ 5 files changed, 126 insertions(+), 8 deletions(-) create mode 100644 test/pdfs/issue3351.1.pdf.link create mode 100644 test/pdfs/issue3351.2.pdf.link create mode 100644 test/pdfs/issue3351.3.pdf.link diff --git a/src/display/canvas.js b/src/display/canvas.js index aa076e63f..6b141ee5f 100644 --- a/src/display/canvas.js +++ b/src/display/canvas.js @@ -377,6 +377,78 @@ class CachedCanvases { } } +function drawImageAtIntegerCoords( + ctx, + srcImg, + srcX, + srcY, + srcW, + srcH, + destX, + destY, + destW, + destH +) { + const [a, b, c, d, tx, ty] = ctx.mozCurrentTransform; + if (b === 0 && c === 0) { + // top-left corner is at (X, Y) and + // bottom-right one is at (X + width, Y + height). + + // If leftX is 4.321 then it's rounded to 4. + // If width is 10.432 then it's rounded to 11 because + // rightX = leftX + width = 14.753 which is rounded to 15 + // so after rounding the total width is 11 (15 - 4). + // It's why we can't just floor/ceil uniformly, it just depends + // on the values we've. + + const tlX = destX * a + tx; + const rTlX = Math.round(tlX); + const tlY = destY * d + ty; + const rTlY = Math.round(tlY); + const brX = (destX + destW) * a + tx; + + // Some pdf contains images with 1x1 images so in case of 0-width after + // scaling we must fallback on 1 to be sure there is something. + const rWidth = Math.abs(Math.round(brX) - rTlX) || 1; + const brY = (destY + destH) * d + ty; + const rHeight = Math.abs(Math.round(brY) - rTlY) || 1; + + // We must apply a transformation in order to apply it on the image itself. + // For example if a == 1 && d == -1, it means that the image itself is + // mirrored w.r.t. the x-axis. + ctx.setTransform(Math.sign(a), 0, 0, Math.sign(d), rTlX, rTlY); + ctx.drawImage(srcImg, srcX, srcY, srcW, srcH, 0, 0, rWidth, rHeight); + ctx.setTransform(a, b, c, d, tx, ty); + + return [rWidth, rHeight]; + } + + if (a === 0 && d === 0) { + // This path is taken in issue9462.pdf (page 3). + const tlX = destY * c + tx; + const rTlX = Math.round(tlX); + const tlY = destX * b + ty; + const rTlY = Math.round(tlY); + const brX = (destY + destH) * c + tx; + const rWidth = Math.abs(Math.round(brX) - rTlX) || 1; + const brY = (destX + destW) * b + ty; + const rHeight = Math.abs(Math.round(brY) - rTlY) || 1; + + ctx.setTransform(0, Math.sign(b), Math.sign(c), 0, rTlX, rTlY); + ctx.drawImage(srcImg, srcX, srcY, srcW, srcH, 0, 0, rHeight, rWidth); + ctx.setTransform(a, b, c, d, tx, ty); + + return [rHeight, rWidth]; + } + + // Not a scale matrix so let the render handle the case without rounding. + ctx.drawImage(srcImg, srcX, srcY, srcW, srcH, destX, destY, destW, destH); + + const scaleX = Math.hypot(a, b); + const scaleY = Math.hypot(c, d); + return [scaleX * destW, scaleY * destH]; +} + function compileType3Glyph(imgData) { const POINT_TO_PROCESS_LIMIT = 1000; const POINT_TYPES = new Uint8Array([ @@ -1461,8 +1533,8 @@ class CanvasGraphics { const cord1 = Util.applyTransform([0, 0], maskToCanvas); const cord2 = Util.applyTransform([width, height], maskToCanvas); const rect = Util.normalizeRect([cord1[0], cord1[1], cord2[0], cord2[1]]); - const drawnWidth = Math.ceil(rect[2] - rect[0]); - const drawnHeight = Math.ceil(rect[3] - rect[1]); + const drawnWidth = Math.round(rect[2] - rect[0]) || 1; + const drawnHeight = Math.round(rect[3] - rect[1]) || 1; const fillCanvas = this.cachedCanvases.getCanvas( "fillCanvas", drawnWidth, @@ -1496,7 +1568,9 @@ class CanvasGraphics { fillCtx.mozCurrentTransform, img.interpolate ); - fillCtx.drawImage( + + drawImageAtIntegerCoords( + fillCtx, scaled, 0, 0, @@ -3005,7 +3079,18 @@ class CanvasGraphics { ctx.save(); ctx.transform.apply(ctx, image.transform); ctx.scale(1, -1); - ctx.drawImage(maskCanvas.canvas, 0, 0, width, height, 0, -1, 1, 1); + drawImageAtIntegerCoords( + ctx, + maskCanvas.canvas, + 0, + 0, + width, + height, + 0, + -1, + 1, + 1 + ); ctx.restore(); } this.compose(); @@ -3085,7 +3170,9 @@ class CanvasGraphics { ctx.mozCurrentTransform, imgData.interpolate ); - ctx.drawImage( + + const [rWidth, rHeight] = drawImageAtIntegerCoords( + ctx, scaled.img, 0, 0, @@ -3103,8 +3190,8 @@ class CanvasGraphics { imgData, left: position[0], top: position[1], - width: width / ctx.mozCurrentTransformInverse[0], - height: height / ctx.mozCurrentTransformInverse[3], + width: rWidth, + height: rHeight, }); } this.compose(); @@ -3133,7 +3220,8 @@ class CanvasGraphics { ctx.save(); ctx.transform.apply(ctx, entry.transform); ctx.scale(1, -1); - ctx.drawImage( + drawImageAtIntegerCoords( + ctx, tmpCanvas.canvas, entry.x, entry.y, diff --git a/test/pdfs/issue3351.1.pdf.link b/test/pdfs/issue3351.1.pdf.link new file mode 100644 index 000000000..9c5232e26 --- /dev/null +++ b/test/pdfs/issue3351.1.pdf.link @@ -0,0 +1,2 @@ +https://github.com/mozilla/pdf.js/files/8582209/Doc2.pdf + diff --git a/test/pdfs/issue3351.2.pdf.link b/test/pdfs/issue3351.2.pdf.link new file mode 100644 index 000000000..701a90a58 --- /dev/null +++ b/test/pdfs/issue3351.2.pdf.link @@ -0,0 +1,2 @@ +https://bugzilla.mozilla.org/attachment.cgi?id=8741330 + diff --git a/test/pdfs/issue3351.3.pdf.link b/test/pdfs/issue3351.3.pdf.link new file mode 100644 index 000000000..02665a4f2 --- /dev/null +++ b/test/pdfs/issue3351.3.pdf.link @@ -0,0 +1,2 @@ +https://bugzilla.mozilla.org/attachment.cgi?id=8741334 + diff --git a/test/test_manifest.json b/test/test_manifest.json index 6082ee0f1..540776747 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -6403,5 +6403,29 @@ "link": true, "lastPage": 1, "type": "eq" + }, + { "id": "issue3351.1", + "file": "pdfs/issue3351.1.pdf", + "md5": "4216245a5f18bb3eac80575ccf0b272d", + "rounds": 1, + "link": true, + "lastPage": 1, + "type": "eq" + }, + { "id": "issue3351.2", + "file": "pdfs/issue3351.2.pdf", + "md5": "fa3fd1659c409c7824ef8838c3071efc", + "rounds": 1, + "link": true, + "lastPage": 1, + "type": "eq" + }, + { "id": "issue3351.3", + "file": "pdfs/issue3351.3.pdf", + "md5": "60e2f2c480b6bc0e7f726743c6896520", + "rounds": 1, + "link": true, + "lastPage": 1, + "type": "eq" } ]