Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add (basic) support for transfer functions to Images (issue 6931, bug 1149713) #12219

Merged
merged 2 commits into from
Aug 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 51 additions & 2 deletions src/core/evaluator.js
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,51 @@ class PartialEvaluator {
);
}

handleTransferFunction(tr) {
let transferArray;
if (Array.isArray(tr)) {
transferArray = tr;
} else if (isPDFFunction(tr)) {
transferArray = [tr];
} else {
return null; // Not a valid transfer function entry.
}

const transferMaps = [];
let numFns = 0,
numEffectfulFns = 0;
for (const entry of transferArray) {
const transferObj = this.xref.fetchIfRef(entry);
numFns++;

if (isName(transferObj, "Identity")) {
transferMaps.push(null);
continue;
} else if (!isPDFFunction(transferObj)) {
return null; // Not a valid transfer function object.
}

const transferFn = this._pdfFunctionFactory.create(transferObj);
const transferMap = new Uint8Array(256),
tmp = new Float32Array(1);
for (let j = 0; j < 256; j++) {
tmp[0] = j / 255;
transferFn(tmp, 0, tmp, 0);
transferMap[j] = (tmp[0] * 255) | 0;
}
transferMaps.push(transferMap);
numEffectfulFns++;
}

if (!(numFns === 1 || numFns === 4)) {
return null; // Only 1 or 4 functions are supported, by the specification.
}
if (numEffectfulFns === 0) {
return null; // Only /Identity transfer functions found, which are no-ops.
}
return transferMaps;
}

handleTilingType(
fn,
args,
Expand Down Expand Up @@ -846,6 +891,8 @@ class PartialEvaluator {
gStateObj.push([key, value]);
break;
case "Font":
isSimpleGState = false;

promise = promise.then(() => {
return this.handleSetFont(
resources,
Expand Down Expand Up @@ -885,7 +932,10 @@ class PartialEvaluator {
} else {
warn("Unsupported SMask type");
}

break;
case "TR":
const transferMaps = this.handleTransferFunction(value);
gStateObj.push([key, transferMaps]);
break;
// Only generate info log messages for the following since
// they are unlikely to have a big impact on the rendering.
Expand All @@ -896,7 +946,6 @@ class PartialEvaluator {
case "BG2":
case "UCR":
case "UCR2":
case "TR":
case "TR2":
case "HT":
case "SM":
Expand Down
91 changes: 87 additions & 4 deletions src/display/canvas.js
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,7 @@ var CanvasExtraState = (function CanvasExtraStateClosure() {
this.lineWidth = 1;
this.activeSMask = null;
this.resumeSMaskCtx = null; // nonclonable field (see the save method below)
this.transferMaps = null;
}

CanvasExtraState.prototype = {
Expand Down Expand Up @@ -484,7 +485,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
this._cachedGetSinglePixelWidth = null;
}

function putBinaryImageData(ctx, imgData) {
function putBinaryImageData(ctx, imgData, transferMaps = null) {
if (typeof ImageData !== "undefined" && imgData instanceof ImageData) {
ctx.putImageData(imgData, 0, 0);
return;
Expand Down Expand Up @@ -514,6 +515,24 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
var dest = chunkImgData.data;
var i, j, thisChunkHeight, elemsInThisChunk;

let transferMapRed, transferMapGreen, transferMapBlue, transferMapGray;
if (transferMaps) {
switch (transferMaps.length) {
case 1:
transferMapRed = transferMaps[0];
transferMapGreen = transferMaps[0];
transferMapBlue = transferMaps[0];
transferMapGray = transferMaps[0];
break;
case 4:
transferMapRed = transferMaps[0];
transferMapGreen = transferMaps[1];
transferMapBlue = transferMaps[2];
transferMapGray = transferMaps[3];
break;
}
}

// There are multiple forms in which the pixel data can be passed, and
// imgData.kind tells us which one this is.
if (imgData.kind === ImageKind.GRAYSCALE_1BPP) {
Expand All @@ -524,13 +543,20 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
var fullSrcDiff = (width + 7) >> 3;
var white = 0xffffffff;
var black = IsLittleEndianCached.value ? 0xff000000 : 0x000000ff;

if (transferMapGray) {
if (transferMapGray[0] === 0xff && transferMapGray[0xff] === 0) {
[white, black] = [black, white];
}
}

for (i = 0; i < totalChunks; i++) {
thisChunkHeight =
i < fullChunks ? FULL_CHUNK_HEIGHT : partialChunkHeight;
destPos = 0;
for (j = 0; j < thisChunkHeight; j++) {
var srcDiff = srcLength - srcPos;
var k = 0;
let k = 0;
var kEnd = srcDiff > fullSrcDiff ? width : srcDiff * 8 - 7;
var kEndUnrolled = kEnd & ~7;
var mask = 0;
Expand Down Expand Up @@ -565,23 +591,63 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
}
} else if (imgData.kind === ImageKind.RGBA_32BPP) {
// RGBA, 32-bits per pixel.
const hasTransferMaps = !!(
transferMapRed ||
transferMapGreen ||
transferMapBlue
);

j = 0;
elemsInThisChunk = width * FULL_CHUNK_HEIGHT * 4;
for (i = 0; i < fullChunks; i++) {
dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk));
srcPos += elemsInThisChunk;

if (hasTransferMaps) {
for (let k = 0; k < elemsInThisChunk; k += 4) {
if (transferMapRed) {
dest[k + 0] = transferMapRed[dest[k + 0]];
}
if (transferMapGreen) {
dest[k + 1] = transferMapGreen[dest[k + 1]];
}
if (transferMapBlue) {
dest[k + 2] = transferMapBlue[dest[k + 2]];
}
}
}

ctx.putImageData(chunkImgData, 0, j);
j += FULL_CHUNK_HEIGHT;
}
if (i < totalChunks) {
elemsInThisChunk = width * partialChunkHeight * 4;
dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk));

if (hasTransferMaps) {
for (let k = 0; k < elemsInThisChunk; k += 4) {
if (transferMapRed) {
dest[k + 0] = transferMapRed[dest[k + 0]];
}
if (transferMapGreen) {
dest[k + 1] = transferMapGreen[dest[k + 1]];
}
if (transferMapBlue) {
dest[k + 2] = transferMapBlue[dest[k + 2]];
}
}
}

ctx.putImageData(chunkImgData, 0, j);
}
} else if (imgData.kind === ImageKind.RGB_24BPP) {
// RGB, 24-bits per pixel.
const hasTransferMaps = !!(
transferMapRed ||
transferMapGreen ||
transferMapBlue
);

thisChunkHeight = FULL_CHUNK_HEIGHT;
elemsInThisChunk = width * thisChunkHeight;
for (i = 0; i < totalChunks; i++) {
Expand All @@ -597,6 +663,21 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
dest[destPos++] = src[srcPos++];
dest[destPos++] = 255;
}

if (hasTransferMaps) {
for (let k = 0; k < destPos; k += 4) {
if (transferMapRed) {
dest[k + 0] = transferMapRed[dest[k + 0]];
}
if (transferMapGreen) {
dest[k + 1] = transferMapGreen[dest[k + 1]];
}
if (transferMapBlue) {
dest[k + 2] = transferMapBlue[dest[k + 2]];
}
}
}

ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT);
}
} else {
Expand Down Expand Up @@ -1040,6 +1121,8 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
}
this.tempSMask = null;
break;
case "TR":
this.current.transferMaps = value;
}
}
},
Expand Down Expand Up @@ -2362,7 +2445,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
} else {
tmpCanvas = this.cachedCanvases.getCanvas("inlineImage", width, height);
var tmpCtx = tmpCanvas.context;
putBinaryImageData(tmpCtx, imgData);
putBinaryImageData(tmpCtx, imgData, this.current.transferMaps);
imgToPaint = tmpCanvas.canvas;
}

Expand Down Expand Up @@ -2447,7 +2530,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {

var tmpCanvas = this.cachedCanvases.getCanvas("inlineImage", w, h);
var tmpCtx = tmpCanvas.context;
putBinaryImageData(tmpCtx, imgData);
putBinaryImageData(tmpCtx, imgData, this.current.transferMaps);

for (var i = 0, ii = map.length; i < ii; i++) {
var entry = map[i];
Expand Down
1 change: 1 addition & 0 deletions test/pdfs/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
!issue7492.pdf
!issue7544.pdf
!issue7507.pdf
!issue6931_reduced.pdf
!issue7580.pdf
!issue7598.pdf
!issue7665.pdf
Expand Down
Binary file added test/pdfs/issue6931_reduced.pdf
Binary file not shown.
6 changes: 6 additions & 0 deletions test/test_manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -4468,6 +4468,12 @@
"rounds": 1,
"type": "eq"
},
{ "id": "issue6931",
"file": "pdfs/issue6931_reduced.pdf",
"md5": "e61388913821a5e044bf85a5846d6d9a",
"rounds": 1,
"type": "eq"
},
{ "id": "annotation-button-widget-annotations",
"file": "pdfs/annotation-button-widget.pdf",
"md5": "5cf23adfff84256d9cfe261bea96dade",
Expand Down