Enable/disable image smoothing based on image interpolate value. (bug 1722191)

While some of the output looks worse to my eye, this behavior more
closely matches what I see when I open the PDFs in Adobe acrobat.

Fixes: #4706, #9713, #8245, #1344
This commit is contained in:
Brendan Dahl 2021-09-08 17:31:10 -07:00
parent 8a79f13e5a
commit f38fb42b42
13 changed files with 74 additions and 9 deletions

View File

@ -589,6 +589,7 @@ class PartialEvaluator {
} }
const imageMask = dict.get("ImageMask", "IM") || false; const imageMask = dict.get("ImageMask", "IM") || false;
const interpolate = dict.get("Interpolate", "I");
let imgData, args; let imgData, args;
if (imageMask) { if (imageMask) {
// This depends on a tmpCanvas being filled with the // This depends on a tmpCanvas being filled with the
@ -612,6 +613,7 @@ class PartialEvaluator {
height, height,
imageIsFromDecodeStream: image instanceof DecodeStream, imageIsFromDecodeStream: image instanceof DecodeStream,
inverseDecode: !!decode && decode[0] > 0, inverseDecode: !!decode && decode[0] > 0,
interpolate,
}); });
imgData.cached = !!cacheKey; imgData.cached = !!cacheKey;
args = [imgData]; args = [imgData];

View File

@ -139,7 +139,7 @@ class PDFImage {
this.width = width; this.width = width;
this.height = height; this.height = height;
this.interpolate = dict.get("Interpolate", "I") || false; this.interpolate = dict.get("Interpolate", "I");
this.imageMask = dict.get("ImageMask", "IM") || false; this.imageMask = dict.get("ImageMask", "IM") || false;
this.matte = dict.get("Matte") || false; this.matte = dict.get("Matte") || false;
@ -294,6 +294,7 @@ class PDFImage {
height, height,
imageIsFromDecodeStream, imageIsFromDecodeStream,
inverseDecode, inverseDecode,
interpolate,
}) { }) {
if ( if (
typeof PDFJSDev === "undefined" || typeof PDFJSDev === "undefined" ||
@ -339,7 +340,7 @@ class PDFImage {
} }
} }
return { data, width, height }; return { data, width, height, interpolate };
} }
get drawWidth() { get drawWidth() {
@ -593,6 +594,7 @@ class PDFImage {
const imgData = { const imgData = {
width: drawWidth, width: drawWidth,
height: drawHeight, height: drawHeight,
interpolate: this.interpolate,
kind: 0, kind: 0,
data: null, data: null,
// Other fields are filled in below. // Other fields are filled in below.

View File

@ -12,7 +12,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import { CSS_PIXELS_PER_INCH, PDF_PIXELS_PER_INCH } from "./display_utils.js";
import { import {
FONT_IDENTITY_MATRIX, FONT_IDENTITY_MATRIX,
IDENTITY_MATRIX, IDENTITY_MATRIX,
@ -871,6 +871,27 @@ function composeSMask(ctx, smask, layerCtx) {
ctx.drawImage(mask, 0, 0); ctx.drawImage(mask, 0, 0);
} }
function getImageSmoothingEnabled(transform, interpolate) {
const scale = Util.singularValueDecompose2dScale(transform);
// Round to a 32bit float so that `<=` check below will pass for numbers that
// are very close, but not exactly the same 64bit floats.
scale[0] = Math.fround(scale[0]);
scale[1] = Math.fround(scale[1]);
const actualScale = Math.fround(
((globalThis.devicePixelRatio || 1) * CSS_PIXELS_PER_INCH) /
PDF_PIXELS_PER_INCH
);
if (interpolate !== undefined) {
// If the value is explicitly set use it.
return interpolate;
} else if (scale[0] <= actualScale || scale[1] <= actualScale) {
// Smooth when downscaling.
return true;
}
// Don't smooth when upscaling.
return false;
}
const LINE_CAP_STYLES = ["butt", "round", "square"]; const LINE_CAP_STYLES = ["butt", "round", "square"];
const LINE_JOIN_STYLES = ["miter", "round", "bevel"]; const LINE_JOIN_STYLES = ["miter", "round", "bevel"];
const NORMAL_CLIP = {}; const NORMAL_CLIP = {};
@ -1183,6 +1204,10 @@ class CanvasGraphics {
maskCanvas.canvas, maskCanvas.canvas,
fillCtx.mozCurrentTransformInverse fillCtx.mozCurrentTransformInverse
); );
fillCtx.imageSmoothingEnabled = getImageSmoothingEnabled(
fillCtx.mozCurrentTransform,
img.interpolate
);
fillCtx.drawImage( fillCtx.drawImage(
scaled.img, scaled.img,
0, 0,
@ -2663,6 +2688,10 @@ class CanvasGraphics {
} }
const scaled = this._scaleImage(imgToPaint, ctx.mozCurrentTransformInverse); const scaled = this._scaleImage(imgToPaint, ctx.mozCurrentTransformInverse);
ctx.imageSmoothingEnabled = getImageSmoothingEnabled(
ctx.mozCurrentTransform,
imgData.interpolate
);
ctx.drawImage( ctx.drawImage(
scaled.img, scaled.img,
0, 0,

View File

@ -32,6 +32,9 @@ import {
const DEFAULT_LINK_REL = "noopener noreferrer nofollow"; const DEFAULT_LINK_REL = "noopener noreferrer nofollow";
const SVG_NS = "http://www.w3.org/2000/svg"; const SVG_NS = "http://www.w3.org/2000/svg";
const CSS_PIXELS_PER_INCH = 96.0;
const PDF_PIXELS_PER_INCH = 72.0;
class DOMCanvasFactory extends BaseCanvasFactory { class DOMCanvasFactory extends BaseCanvasFactory {
constructor({ ownerDocument = globalThis.document } = {}) { constructor({ ownerDocument = globalThis.document } = {}) {
super(); super();
@ -622,6 +625,7 @@ function getXfaPageViewport(xfaPage, { scale = 1, rotation = 0 }) {
export { export {
addLinkAttributes, addLinkAttributes,
CSS_PIXELS_PER_INCH,
DEFAULT_LINK_REL, DEFAULT_LINK_REL,
deprecated, deprecated,
DOMCanvasFactory, DOMCanvasFactory,
@ -637,6 +641,7 @@ export {
LinkTarget, LinkTarget,
loadScript, loadScript,
PageViewport, PageViewport,
PDF_PIXELS_PER_INCH,
PDFDateString, PDFDateString,
RenderingCancelledException, RenderingCancelledException,
StatTimer, StatTimer,

View File

@ -16,6 +16,7 @@
import { import {
addLinkAttributes, addLinkAttributes,
CSS_PIXELS_PER_INCH,
getFilenameFromUrl, getFilenameFromUrl,
getPdfFilenameFromUrl, getPdfFilenameFromUrl,
getXfaPageViewport, getXfaPageViewport,
@ -23,6 +24,7 @@ import {
isValidFetchUrl, isValidFetchUrl,
LinkTarget, LinkTarget,
loadScript, loadScript,
PDF_PIXELS_PER_INCH,
PDFDateString, PDFDateString,
RenderingCancelledException, RenderingCancelledException,
} from "./display/display_utils.js"; } from "./display/display_utils.js";
@ -103,11 +105,13 @@ if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("PRODUCTION")) {
export { export {
// From "./display/display_utils.js": // From "./display/display_utils.js":
addLinkAttributes, addLinkAttributes,
CSS_PIXELS_PER_INCH,
getFilenameFromUrl, getFilenameFromUrl,
getPdfFilenameFromUrl, getPdfFilenameFromUrl,
isPdfFile, isPdfFile,
LinkTarget, LinkTarget,
loadScript, loadScript,
PDF_PIXELS_PER_INCH,
PDFDateString, PDFDateString,
RenderingCancelledException, RenderingCancelledException,
getXfaPageViewport, getXfaPageViewport,

View File

@ -20,15 +20,17 @@
const { const {
AnnotationLayer, AnnotationLayer,
AnnotationMode, AnnotationMode,
CSS_PIXELS_PER_INCH,
getDocument, getDocument,
GlobalWorkerOptions, GlobalWorkerOptions,
PDF_PIXELS_PER_INCH,
renderTextLayer, renderTextLayer,
XfaLayer, XfaLayer,
} = pdfjsLib; } = pdfjsLib;
const { SimpleLinkService } = pdfjsViewer; const { SimpleLinkService } = pdfjsViewer;
const WAITING_TIME = 100; // ms const WAITING_TIME = 100; // ms
const PDF_TO_CSS_UNITS = 96.0 / 72.0; const PDF_TO_CSS_UNITS = CSS_PIXELS_PER_INCH / PDF_PIXELS_PER_INCH;
const CMAP_URL = "/build/generic/web/cmaps/"; const CMAP_URL = "/build/generic/web/cmaps/";
const CMAP_PACKED = true; const CMAP_PACKED = true;
const STANDARD_FONT_DATA_URL = "/build/generic/web/standard_fonts/"; const STANDARD_FONT_DATA_URL = "/build/generic/web/standard_fonts/";

View File

@ -69,6 +69,7 @@
!issue8229.pdf !issue8229.pdf
!issue8276_reduced.pdf !issue8276_reduced.pdf
!issue8372.pdf !issue8372.pdf
!issue9713.pdf
!xfa_filled_imm1344e.pdf !xfa_filled_imm1344e.pdf
!issue8424.pdf !issue8424.pdf
!issue8480.pdf !issue8480.pdf
@ -150,6 +151,7 @@
!complex_ttf_font.pdf !complex_ttf_font.pdf
!issue3694_reduced.pdf !issue3694_reduced.pdf
!extgstate.pdf !extgstate.pdf
!issue4706.pdf
!rotation.pdf !rotation.pdf
!simpletype3font.pdf !simpletype3font.pdf
!sizes.pdf !sizes.pdf

BIN
test/pdfs/issue4706.pdf Normal file

Binary file not shown.

BIN
test/pdfs/issue9713.pdf Normal file

Binary file not shown.

View File

@ -3110,6 +3110,12 @@
"rounds": 1, "rounds": 1,
"type": "eq" "type": "eq"
}, },
{ "id": "issue4706",
"file": "pdfs/issue4706.pdf",
"md5": "f3e90a3cf52550583fa2a07a138b8660",
"rounds": 1,
"type": "eq"
},
{ "id": "issue11242", { "id": "issue11242",
"file": "pdfs/issue11242_reduced.pdf", "file": "pdfs/issue11242_reduced.pdf",
"md5": "ba50b6ee537f3e815ccfe0c99e598e05", "md5": "ba50b6ee537f3e815ccfe0c99e598e05",
@ -4421,6 +4427,12 @@
"link": true, "link": true,
"type": "eq" "type": "eq"
}, },
{ "id": "issue9713",
"file": "pdfs/issue9713.pdf",
"md5": "a62bd42d12271105b26a68c8eae5ea5f",
"rounds": 1,
"type": "eq"
},
{ "id": "issue1936-text", { "id": "issue1936-text",
"file": "pdfs/issue1936.pdf", "file": "pdfs/issue1936.pdf",
"md5": "7302eb9b6a626308e2a933aaed9e1756", "md5": "7302eb9b6a626308e2a933aaed9e1756",

View File

@ -13,7 +13,12 @@
* limitations under the License. * limitations under the License.
*/ */
import { AnnotationMode, RenderingCancelledException, shadow } from "pdfjs-lib"; import {
AnnotationMode,
PDF_PIXELS_PER_INCH,
RenderingCancelledException,
shadow,
} from "pdfjs-lib";
import { getXfaHtmlForPrinting } from "./print_utils.js"; import { getXfaHtmlForPrinting } from "./print_utils.js";
import { PDFPrintServiceFactory } from "./app.js"; import { PDFPrintServiceFactory } from "./app.js";
@ -29,7 +34,7 @@ function composePage(
const canvas = document.createElement("canvas"); const canvas = document.createElement("canvas");
// The size of the canvas in pixels for printing. // The size of the canvas in pixels for printing.
const PRINT_UNITS = printResolution / 72.0; const PRINT_UNITS = printResolution / PDF_PIXELS_PER_INCH;
canvas.width = Math.floor(size.width * PRINT_UNITS); canvas.width = Math.floor(size.width * PRINT_UNITS);
canvas.height = Math.floor(size.height * PRINT_UNITS); canvas.height = Math.floor(size.height * PRINT_UNITS);

View File

@ -13,8 +13,8 @@
* limitations under the License. * limitations under the License.
*/ */
import { AnnotationMode, PDF_PIXELS_PER_INCH } from "pdfjs-lib";
import { PDFPrintServiceFactory, PDFViewerApplication } from "./app.js"; import { PDFPrintServiceFactory, PDFViewerApplication } from "./app.js";
import { AnnotationMode } from "pdfjs-lib";
import { compatibilityParams } from "./app_options.js"; import { compatibilityParams } from "./app_options.js";
import { getXfaHtmlForPrinting } from "./print_utils.js"; import { getXfaHtmlForPrinting } from "./print_utils.js";
@ -34,7 +34,7 @@ function renderPage(
const scratchCanvas = activeService.scratchCanvas; const scratchCanvas = activeService.scratchCanvas;
// The size of the canvas in pixels for printing. // The size of the canvas in pixels for printing.
const PRINT_UNITS = printResolution / 72.0; const PRINT_UNITS = printResolution / PDF_PIXELS_PER_INCH;
scratchCanvas.width = Math.floor(size.width * PRINT_UNITS); scratchCanvas.width = Math.floor(size.width * PRINT_UNITS);
scratchCanvas.height = Math.floor(size.height * PRINT_UNITS); scratchCanvas.height = Math.floor(size.height * PRINT_UNITS);

View File

@ -13,7 +13,9 @@
* limitations under the License. * limitations under the License.
*/ */
const CSS_UNITS = 96.0 / 72.0; import { CSS_PIXELS_PER_INCH, PDF_PIXELS_PER_INCH } from "pdfjs-lib";
const CSS_UNITS = CSS_PIXELS_PER_INCH / PDF_PIXELS_PER_INCH;
const DEFAULT_SCALE_VALUE = "auto"; const DEFAULT_SCALE_VALUE = "auto";
const DEFAULT_SCALE = 1.0; const DEFAULT_SCALE = 1.0;
const MIN_SCALE = 0.1; const MIN_SCALE = 0.1;