Merge pull request #14545 from brendandahl/output-scale

Generate test images at different output scales.
This commit is contained in:
Jonas Jenwald 2022-02-24 21:56:54 +01:00 committed by GitHub
commit 889b761f22
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 101 additions and 34 deletions

View File

@ -61,7 +61,7 @@ function loadStyles(styles) {
return Promise.all(promises); return Promise.all(promises);
} }
function writeSVG(svgElement, ctx) { function writeSVG(svgElement, ctx, outputScale) {
// We need to have UTF-8 encoded XML. // We need to have UTF-8 encoded XML.
const svg_xml = unescape( const svg_xml = unescape(
encodeURIComponent(new XMLSerializer().serializeToString(svgElement)) encodeURIComponent(new XMLSerializer().serializeToString(svgElement))
@ -104,7 +104,7 @@ function inlineImages(images) {
return Promise.all(imagePromises); return Promise.all(imagePromises);
} }
async function convertCanvasesToImages(annotationCanvasMap) { async function convertCanvasesToImages(annotationCanvasMap, outputScale) {
const results = new Map(); const results = new Map();
const promises = []; const promises = [];
for (const [key, canvas] of annotationCanvasMap) { for (const [key, canvas] of annotationCanvasMap) {
@ -112,7 +112,10 @@ async function convertCanvasesToImages(annotationCanvasMap) {
new Promise(resolve => { new Promise(resolve => {
canvas.toBlob(blob => { canvas.toBlob(blob => {
const image = document.createElement("img"); const image = document.createElement("img");
image.onload = resolve; image.onload = function () {
image.style.width = Math.floor(image.width / outputScale) + "px";
resolve();
};
results.set(key, image); results.set(key, image);
image.src = URL.createObjectURL(blob); image.src = URL.createObjectURL(blob);
}); });
@ -200,6 +203,7 @@ class Rasterize {
static async annotationLayer( static async annotationLayer(
ctx, ctx,
viewport, viewport,
outputScale,
annotations, annotations,
annotationCanvasMap, annotationCanvasMap,
page, page,
@ -215,7 +219,8 @@ class Rasterize {
const annotationViewport = viewport.clone({ dontFlip: true }); const annotationViewport = viewport.clone({ dontFlip: true });
const annotationImageMap = await convertCanvasesToImages( const annotationImageMap = await convertCanvasesToImages(
annotationCanvasMap annotationCanvasMap,
outputScale
); );
// Rendering annotation layer as HTML. // Rendering annotation layer as HTML.
@ -608,13 +613,38 @@ class Driver {
ctx = this.canvas.getContext("2d", { alpha: false }); ctx = this.canvas.getContext("2d", { alpha: false });
task.pdfDoc.getPage(task.pageNum).then( task.pdfDoc.getPage(task.pageNum).then(
page => { page => {
const viewport = page.getViewport({ // Default to creating the test images at the devices pixel ratio,
// unless the test explicitly specifies an output scale.
const outputScale = task.outputScale || window.devicePixelRatio;
let viewport = page.getViewport({
scale: PixelsPerInch.PDF_TO_CSS_UNITS, scale: PixelsPerInch.PDF_TO_CSS_UNITS,
}); });
this.canvas.width = viewport.width; // Restrict the test from creating a canvas that is too big.
this.canvas.height = viewport.height; const MAX_CANVAS_PIXEL_DIMENSION = 4096;
const largestDimension = Math.max(viewport.width, viewport.height);
if (
Math.floor(largestDimension * outputScale) >
MAX_CANVAS_PIXEL_DIMENSION
) {
const rescale = MAX_CANVAS_PIXEL_DIMENSION / largestDimension;
viewport = viewport.clone({
scale: PixelsPerInch.PDF_TO_CSS_UNITS * rescale,
});
}
const pixelWidth = Math.floor(viewport.width * outputScale);
const pixelHeight = Math.floor(viewport.height * outputScale);
task.viewportWidth = Math.floor(viewport.width);
task.viewportHeight = Math.floor(viewport.height);
task.outputScale = outputScale;
this.canvas.width = pixelWidth;
this.canvas.height = pixelHeight;
this.canvas.style.width = Math.floor(viewport.width) + "px";
this.canvas.style.height = Math.floor(viewport.height) + "px";
this._clearCanvas(); this._clearCanvas();
const transform =
outputScale !== 1 ? [outputScale, 0, 0, outputScale, 0, 0] : null;
// Initialize various `eq` test subtypes, see comment below. // Initialize various `eq` test subtypes, see comment below.
let renderAnnotations = false, let renderAnnotations = false,
renderForms = false, renderForms = false,
@ -639,8 +669,8 @@ class Driver {
textLayerCanvas = document.createElement("canvas"); textLayerCanvas = document.createElement("canvas");
this.textLayerCanvas = textLayerCanvas; this.textLayerCanvas = textLayerCanvas;
} }
textLayerCanvas.width = viewport.width; textLayerCanvas.width = pixelWidth;
textLayerCanvas.height = viewport.height; textLayerCanvas.height = pixelHeight;
const textLayerContext = textLayerCanvas.getContext("2d"); const textLayerContext = textLayerCanvas.getContext("2d");
textLayerContext.clearRect( textLayerContext.clearRect(
0, 0,
@ -648,6 +678,7 @@ class Driver {
textLayerCanvas.width, textLayerCanvas.width,
textLayerCanvas.height textLayerCanvas.height
); );
textLayerContext.scale(outputScale, outputScale);
const enhanceText = !!task.enhance; const enhanceText = !!task.enhance;
// The text builder will draw its content on the test canvas // The text builder will draw its content on the test canvas
initPromise = page initPromise = page
@ -679,8 +710,8 @@ class Driver {
annotationLayerCanvas = document.createElement("canvas"); annotationLayerCanvas = document.createElement("canvas");
this.annotationLayerCanvas = annotationLayerCanvas; this.annotationLayerCanvas = annotationLayerCanvas;
} }
annotationLayerCanvas.width = viewport.width; annotationLayerCanvas.width = pixelWidth;
annotationLayerCanvas.height = viewport.height; annotationLayerCanvas.height = pixelHeight;
annotationLayerContext = annotationLayerCanvas.getContext("2d"); annotationLayerContext = annotationLayerCanvas.getContext("2d");
annotationLayerContext.clearRect( annotationLayerContext.clearRect(
0, 0,
@ -688,6 +719,7 @@ class Driver {
annotationLayerCanvas.width, annotationLayerCanvas.width,
annotationLayerCanvas.height annotationLayerCanvas.height
); );
annotationLayerContext.scale(outputScale, outputScale);
if (!renderXfa) { if (!renderXfa) {
// The annotation builder will draw its content // The annotation builder will draw its content
@ -716,6 +748,7 @@ class Driver {
viewport, viewport,
optionalContentConfigPromise: task.optionalContentConfigPromise, optionalContentConfigPromise: task.optionalContentConfigPromise,
annotationCanvasMap, annotationCanvasMap,
transform,
}; };
if (renderForms) { if (renderForms) {
renderContext.annotationMode = AnnotationMode.ENABLE_FORMS; renderContext.annotationMode = AnnotationMode.ENABLE_FORMS;
@ -732,7 +765,7 @@ class Driver {
ctx.save(); ctx.save();
ctx.globalCompositeOperation = "screen"; ctx.globalCompositeOperation = "screen";
ctx.fillStyle = "rgb(128, 255, 128)"; // making it green ctx.fillStyle = "rgb(128, 255, 128)"; // making it green
ctx.fillRect(0, 0, viewport.width, viewport.height); ctx.fillRect(0, 0, pixelWidth, pixelHeight);
ctx.restore(); ctx.restore();
ctx.drawImage(textLayerCanvas, 0, 0); ctx.drawImage(textLayerCanvas, 0, 0);
} }
@ -762,6 +795,7 @@ class Driver {
Rasterize.annotationLayer( Rasterize.annotationLayer(
annotationLayerContext, annotationLayerContext,
viewport, viewport,
outputScale,
data, data,
annotationCanvasMap, annotationCanvasMap,
page, page,
@ -871,6 +905,9 @@ class Driver {
page: task.pageNum, page: task.pageNum,
snapshot, snapshot,
stats: task.stats.times, stats: task.stats.times,
viewportWidth: task.viewportWidth,
viewportHeight: task.viewportHeight,
outputScale: task.outputScale,
}); });
this._send("/submit_task_results", result, callback); this._send("/submit_task_results", result, callback);
} }

View File

@ -165,8 +165,8 @@ Original author: L. David Baron <dbaron@dbaron.org>
</filter> </filter>
</defs> </defs>
<g id="magnify"> <g id="magnify">
<image x="0" y="0" width="100%" height="100%" id="image1" /> <image x="0" y="0" id="image1" />
<image x="0" y="0" width="100%" height="100%" id="image2" /> <image x="0" y="0" id="image2" />
</g> </g>
<rect id="diffrect" filter="url(#showDifferences)" pointer-events="none" x="0" y="0" width="100%" height="100%" /> <rect id="diffrect" filter="url(#showDifferences)" pointer-events="none" x="0" y="0" width="100%" height="100%" />
</svg> </svg>

View File

@ -228,10 +228,15 @@ window.onload = function () {
}); });
continue; continue;
} }
match = line.match(/^ {2}IMAGE[^:]*: (.*)$/); match = line.match(/^ {2}IMAGE[^:]*\((\d+)x(\d+)x(\d+)\): (.*)$/);
if (match) { if (match) {
const item = gTestItems[gTestItems.length - 1]; const item = gTestItems[gTestItems.length - 1];
item.images.push(match[1]); item.images.push({
width: parseFloat(match[1]),
height: parseFloat(match[2]),
outputScale: parseFloat(match[3]),
file: match[4],
});
} }
} }
buildViewer(); buildViewer();
@ -335,16 +340,31 @@ window.onload = function () {
const cell = ID("images"); const cell = ID("images");
ID("image1").style.display = ""; ID("image1").style.display = "";
const scale = item.images[0].outputScale / window.devicePixelRatio;
ID("image1").setAttribute("width", item.images[0].width * scale);
ID("image1").setAttribute("height", item.images[0].height * scale);
ID("svg").setAttribute("width", item.images[0].width * scale);
ID("svg").setAttribute("height", item.images[0].height * scale);
ID("image2").style.display = "none"; ID("image2").style.display = "none";
if (item.images[1]) {
ID("image2").setAttribute("width", item.images[1].width * scale);
ID("image2").setAttribute("height", item.images[1].height * scale);
}
ID("diffrect").style.display = "none"; ID("diffrect").style.display = "none";
ID("imgcontrols").reset(); ID("imgcontrols").reset();
ID("image1").setAttributeNS(XLINK_NS, "xlink:href", gPath + item.images[0]); ID("image1").setAttributeNS(
XLINK_NS,
"xlink:href",
gPath + item.images[0].file
);
// Making the href be #image1 doesn't seem to work // Making the href be #image1 doesn't seem to work
ID("feimage1").setAttributeNS( ID("feimage1").setAttributeNS(
XLINK_NS, XLINK_NS,
"xlink:href", "xlink:href",
gPath + item.images[0] gPath + item.images[0].file
); );
if (item.images.length === 1) { if (item.images.length === 1) {
ID("imgcontrols").style.display = "none"; ID("imgcontrols").style.display = "none";
@ -353,30 +373,24 @@ window.onload = function () {
ID("image2").setAttributeNS( ID("image2").setAttributeNS(
XLINK_NS, XLINK_NS,
"xlink:href", "xlink:href",
gPath + item.images[1] gPath + item.images[1].file
); );
// Making the href be #image2 doesn't seem to work // Making the href be #image2 doesn't seem to work
ID("feimage2").setAttributeNS( ID("feimage2").setAttributeNS(
XLINK_NS, XLINK_NS,
"xlink:href", "xlink:href",
gPath + item.images[1] gPath + item.images[1].file
); );
} }
cell.style.display = ""; cell.style.display = "";
getImageData(item.images[0], function (data) { getImageData(item.images[0].file, function (data) {
gImage1Data = data; gImage1Data = data;
syncSVGSize(gImage1Data);
}); });
getImageData(item.images[1], function (data) { getImageData(item.images[1].file, function (data) {
gImage2Data = data; gImage2Data = data;
}); });
} }
function syncSVGSize(imageData) {
ID("svg").setAttribute("width", imageData.width);
ID("svg").setAttribute("height", imageData.height);
}
function showImage(i) { function showImage(i) {
if (i === 1) { if (i === 1) {
ID("image1").style.display = ""; ID("image1").style.display = "";
@ -414,7 +428,7 @@ window.onload = function () {
} }
function canvasPixelAsHex(data, x, y) { function canvasPixelAsHex(data, x, y) {
const offset = (y * data.width + x) * 4; const offset = (y * data.width + x) * 4 * window.devicePixelRatio;
const r = data.data[offset]; const r = data.data[offset];
const g = data.data[offset + 1]; const g = data.data[offset + 1];
const b = data.data[offset + 2]; const b = data.data[offset + 2];

View File

@ -451,7 +451,8 @@ function checkEq(task, results, browser, masterMode) {
if (!pageResults[page]) { if (!pageResults[page]) {
continue; continue;
} }
var testSnapshot = pageResults[page].snapshot; const pageResult = pageResults[page];
let testSnapshot = pageResult.snapshot;
if (testSnapshot && testSnapshot.startsWith("data:image/png;base64,")) { if (testSnapshot && testSnapshot.startsWith("data:image/png;base64,")) {
testSnapshot = Buffer.from(testSnapshot.substring(22), "base64"); testSnapshot = Buffer.from(testSnapshot.substring(22), "base64");
} else { } else {
@ -492,8 +493,8 @@ function checkEq(task, results, browser, masterMode) {
refSnapshot refSnapshot
); );
// NB: this follows the format of Mozilla reftest output so that // This no longer follows the format of Mozilla reftest output.
// we can reuse its reftest-analyzer script const viewportString = `(${pageResult.viewportWidth}x${pageResult.viewportHeight}x${pageResult.outputScale})`;
fs.appendFileSync( fs.appendFileSync(
eqLog, eqLog,
"REFTEST TEST-UNEXPECTED-FAIL | " + "REFTEST TEST-UNEXPECTED-FAIL | " +
@ -503,10 +504,10 @@ function checkEq(task, results, browser, masterMode) {
"-page" + "-page" +
(page + 1) + (page + 1) +
" | image comparison (==)\n" + " | image comparison (==)\n" +
"REFTEST IMAGE 1 (TEST): " + `REFTEST IMAGE 1 (TEST)${viewportString}: ` +
path.join(testSnapshotDir, page + 1 + ".png") + path.join(testSnapshotDir, page + 1 + ".png") +
"\n" + "\n" +
"REFTEST IMAGE 2 (REFERENCE): " + `REFTEST IMAGE 2 (REFERENCE)${viewportString}: ` +
path.join(testSnapshotDir, page + 1 + "_ref.png") + path.join(testSnapshotDir, page + 1 + "_ref.png") +
"\n" "\n"
); );
@ -735,6 +736,9 @@ function refTestPostHandler(req, res) {
taskResults[round][page] = { taskResults[round][page] = {
failure, failure,
snapshot, snapshot,
viewportWidth: data.viewportWidth,
viewportHeight: data.viewportHeight,
outputScale: data.outputScale,
}; };
if (stats) { if (stats) {
stats.push({ stats.push({

View File

@ -6142,6 +6142,18 @@
"forms": true, "forms": true,
"lastPage": 1 "lastPage": 1
}, },
{
"id": "issue12716-hidpi",
"file": "pdfs/issue12716.pdf",
"md5": "9bdc9c552bcfccd629f5f97385e79ca5",
"rounds": 1,
"link": true,
"type": "eq",
"forms": true,
"lastPage": 1,
"outputScale": 2,
"about": "This tests draws to another canvas for the button, so it's a good test to ensure output scale is working."
},
{ "id": "xfa_issue13500", { "id": "xfa_issue13500",
"file": "pdfs/xfa_issue13500.pdf", "file": "pdfs/xfa_issue13500.pdf",
"md5": "b81274a19f5a95c1466db3648f1be491", "md5": "b81274a19f5a95c1466db3648f1be491",