Add (basic) support for transfer functions to Images (issue 6931, bug 1149713)
This is *similar* to the existing transfer function support for SMasks, but extended to simple image data. Please note that the extra amount of data now being sent to the worker-thread, for affected /ExtGState entries, is limited to *at most* 4 `Uint8Array`s each with a length of 256 elements. Refer to https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/PDF32000_2008.pdf#G9.1658137 for additional details.
This commit is contained in:
parent
9d3e046a4f
commit
1058f16605
@ -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,
|
||||
@ -887,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.
|
||||
@ -898,7 +946,6 @@ class PartialEvaluator {
|
||||
case "BG2":
|
||||
case "UCR":
|
||||
case "UCR2":
|
||||
case "TR":
|
||||
case "TR2":
|
||||
case "HT":
|
||||
case "SM":
|
||||
|
@ -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 = {
|
||||
@ -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;
|
||||
@ -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) {
|
||||
@ -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;
|
||||
@ -565,6 +591,11 @@ 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;
|
||||
@ -572,16 +603,51 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
|
||||
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++) {
|
||||
@ -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 {
|
||||
@ -1040,6 +1121,8 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
|
||||
}
|
||||
this.tempSMask = null;
|
||||
break;
|
||||
case "TR":
|
||||
this.current.transferMaps = value;
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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];
|
||||
|
1
test/pdfs/.gitignore
vendored
1
test/pdfs/.gitignore
vendored
@ -45,6 +45,7 @@
|
||||
!issue7492.pdf
|
||||
!issue7544.pdf
|
||||
!issue7507.pdf
|
||||
!issue6931_reduced.pdf
|
||||
!issue7580.pdf
|
||||
!issue7598.pdf
|
||||
!issue7665.pdf
|
||||
|
BIN
test/pdfs/issue6931_reduced.pdf
Normal file
BIN
test/pdfs/issue6931_reduced.pdf
Normal file
Binary file not shown.
@ -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",
|
||||
|
Loading…
Reference in New Issue
Block a user