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(
|
handleTilingType(
|
||||||
fn,
|
fn,
|
||||||
args,
|
args,
|
||||||
@ -887,7 +932,10 @@ class PartialEvaluator {
|
|||||||
} else {
|
} else {
|
||||||
warn("Unsupported SMask type");
|
warn("Unsupported SMask type");
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
case "TR":
|
||||||
|
const transferMaps = this.handleTransferFunction(value);
|
||||||
|
gStateObj.push([key, transferMaps]);
|
||||||
break;
|
break;
|
||||||
// Only generate info log messages for the following since
|
// Only generate info log messages for the following since
|
||||||
// they are unlikely to have a big impact on the rendering.
|
// they are unlikely to have a big impact on the rendering.
|
||||||
@ -898,7 +946,6 @@ class PartialEvaluator {
|
|||||||
case "BG2":
|
case "BG2":
|
||||||
case "UCR":
|
case "UCR":
|
||||||
case "UCR2":
|
case "UCR2":
|
||||||
case "TR":
|
|
||||||
case "TR2":
|
case "TR2":
|
||||||
case "HT":
|
case "HT":
|
||||||
case "SM":
|
case "SM":
|
||||||
|
@ -416,6 +416,7 @@ var CanvasExtraState = (function CanvasExtraStateClosure() {
|
|||||||
this.lineWidth = 1;
|
this.lineWidth = 1;
|
||||||
this.activeSMask = null;
|
this.activeSMask = null;
|
||||||
this.resumeSMaskCtx = null; // nonclonable field (see the save method below)
|
this.resumeSMaskCtx = null; // nonclonable field (see the save method below)
|
||||||
|
this.transferMaps = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
CanvasExtraState.prototype = {
|
CanvasExtraState.prototype = {
|
||||||
@ -484,7 +485,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||||||
this._cachedGetSinglePixelWidth = null;
|
this._cachedGetSinglePixelWidth = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function putBinaryImageData(ctx, imgData) {
|
function putBinaryImageData(ctx, imgData, transferMaps = null) {
|
||||||
if (typeof ImageData !== "undefined" && imgData instanceof ImageData) {
|
if (typeof ImageData !== "undefined" && imgData instanceof ImageData) {
|
||||||
ctx.putImageData(imgData, 0, 0);
|
ctx.putImageData(imgData, 0, 0);
|
||||||
return;
|
return;
|
||||||
@ -514,6 +515,24 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||||||
var dest = chunkImgData.data;
|
var dest = chunkImgData.data;
|
||||||
var i, j, thisChunkHeight, elemsInThisChunk;
|
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
|
// There are multiple forms in which the pixel data can be passed, and
|
||||||
// imgData.kind tells us which one this is.
|
// imgData.kind tells us which one this is.
|
||||||
if (imgData.kind === ImageKind.GRAYSCALE_1BPP) {
|
if (imgData.kind === ImageKind.GRAYSCALE_1BPP) {
|
||||||
@ -524,13 +543,20 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||||||
var fullSrcDiff = (width + 7) >> 3;
|
var fullSrcDiff = (width + 7) >> 3;
|
||||||
var white = 0xffffffff;
|
var white = 0xffffffff;
|
||||||
var black = IsLittleEndianCached.value ? 0xff000000 : 0x000000ff;
|
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++) {
|
for (i = 0; i < totalChunks; i++) {
|
||||||
thisChunkHeight =
|
thisChunkHeight =
|
||||||
i < fullChunks ? FULL_CHUNK_HEIGHT : partialChunkHeight;
|
i < fullChunks ? FULL_CHUNK_HEIGHT : partialChunkHeight;
|
||||||
destPos = 0;
|
destPos = 0;
|
||||||
for (j = 0; j < thisChunkHeight; j++) {
|
for (j = 0; j < thisChunkHeight; j++) {
|
||||||
var srcDiff = srcLength - srcPos;
|
var srcDiff = srcLength - srcPos;
|
||||||
var k = 0;
|
let k = 0;
|
||||||
var kEnd = srcDiff > fullSrcDiff ? width : srcDiff * 8 - 7;
|
var kEnd = srcDiff > fullSrcDiff ? width : srcDiff * 8 - 7;
|
||||||
var kEndUnrolled = kEnd & ~7;
|
var kEndUnrolled = kEnd & ~7;
|
||||||
var mask = 0;
|
var mask = 0;
|
||||||
@ -565,6 +591,11 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||||||
}
|
}
|
||||||
} else if (imgData.kind === ImageKind.RGBA_32BPP) {
|
} else if (imgData.kind === ImageKind.RGBA_32BPP) {
|
||||||
// RGBA, 32-bits per pixel.
|
// RGBA, 32-bits per pixel.
|
||||||
|
const hasTransferMaps = !!(
|
||||||
|
transferMapRed ||
|
||||||
|
transferMapGreen ||
|
||||||
|
transferMapBlue
|
||||||
|
);
|
||||||
|
|
||||||
j = 0;
|
j = 0;
|
||||||
elemsInThisChunk = width * FULL_CHUNK_HEIGHT * 4;
|
elemsInThisChunk = width * FULL_CHUNK_HEIGHT * 4;
|
||||||
@ -572,16 +603,51 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||||||
dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk));
|
dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk));
|
||||||
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);
|
ctx.putImageData(chunkImgData, 0, j);
|
||||||
j += FULL_CHUNK_HEIGHT;
|
j += FULL_CHUNK_HEIGHT;
|
||||||
}
|
}
|
||||||
if (i < totalChunks) {
|
if (i < totalChunks) {
|
||||||
elemsInThisChunk = width * partialChunkHeight * 4;
|
elemsInThisChunk = width * partialChunkHeight * 4;
|
||||||
dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk));
|
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);
|
ctx.putImageData(chunkImgData, 0, j);
|
||||||
}
|
}
|
||||||
} else if (imgData.kind === ImageKind.RGB_24BPP) {
|
} else if (imgData.kind === ImageKind.RGB_24BPP) {
|
||||||
// RGB, 24-bits per pixel.
|
// RGB, 24-bits per pixel.
|
||||||
|
const hasTransferMaps = !!(
|
||||||
|
transferMapRed ||
|
||||||
|
transferMapGreen ||
|
||||||
|
transferMapBlue
|
||||||
|
);
|
||||||
|
|
||||||
thisChunkHeight = FULL_CHUNK_HEIGHT;
|
thisChunkHeight = FULL_CHUNK_HEIGHT;
|
||||||
elemsInThisChunk = width * thisChunkHeight;
|
elemsInThisChunk = width * thisChunkHeight;
|
||||||
for (i = 0; i < totalChunks; i++) {
|
for (i = 0; i < totalChunks; i++) {
|
||||||
@ -597,6 +663,21 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||||||
dest[destPos++] = src[srcPos++];
|
dest[destPos++] = src[srcPos++];
|
||||||
dest[destPos++] = 255;
|
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);
|
ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -1040,6 +1121,8 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||||||
}
|
}
|
||||||
this.tempSMask = null;
|
this.tempSMask = null;
|
||||||
break;
|
break;
|
||||||
|
case "TR":
|
||||||
|
this.current.transferMaps = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2362,7 +2445,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||||||
} else {
|
} else {
|
||||||
tmpCanvas = this.cachedCanvases.getCanvas("inlineImage", width, height);
|
tmpCanvas = this.cachedCanvases.getCanvas("inlineImage", width, height);
|
||||||
var tmpCtx = tmpCanvas.context;
|
var tmpCtx = tmpCanvas.context;
|
||||||
putBinaryImageData(tmpCtx, imgData);
|
putBinaryImageData(tmpCtx, imgData, this.current.transferMaps);
|
||||||
imgToPaint = tmpCanvas.canvas;
|
imgToPaint = tmpCanvas.canvas;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2447,7 +2530,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||||||
|
|
||||||
var tmpCanvas = this.cachedCanvases.getCanvas("inlineImage", w, h);
|
var tmpCanvas = this.cachedCanvases.getCanvas("inlineImage", w, h);
|
||||||
var tmpCtx = tmpCanvas.context;
|
var tmpCtx = tmpCanvas.context;
|
||||||
putBinaryImageData(tmpCtx, imgData);
|
putBinaryImageData(tmpCtx, imgData, this.current.transferMaps);
|
||||||
|
|
||||||
for (var i = 0, ii = map.length; i < ii; i++) {
|
for (var i = 0, ii = map.length; i < ii; i++) {
|
||||||
var entry = map[i];
|
var entry = map[i];
|
||||||
|
1
test/pdfs/.gitignore
vendored
1
test/pdfs/.gitignore
vendored
@ -45,6 +45,7 @@
|
|||||||
!issue7492.pdf
|
!issue7492.pdf
|
||||||
!issue7544.pdf
|
!issue7544.pdf
|
||||||
!issue7507.pdf
|
!issue7507.pdf
|
||||||
|
!issue6931_reduced.pdf
|
||||||
!issue7580.pdf
|
!issue7580.pdf
|
||||||
!issue7598.pdf
|
!issue7598.pdf
|
||||||
!issue7665.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,
|
"rounds": 1,
|
||||||
"type": "eq"
|
"type": "eq"
|
||||||
},
|
},
|
||||||
|
{ "id": "issue6931",
|
||||||
|
"file": "pdfs/issue6931_reduced.pdf",
|
||||||
|
"md5": "e61388913821a5e044bf85a5846d6d9a",
|
||||||
|
"rounds": 1,
|
||||||
|
"type": "eq"
|
||||||
|
},
|
||||||
{ "id": "annotation-button-widget-annotations",
|
{ "id": "annotation-button-widget-annotations",
|
||||||
"file": "pdfs/annotation-button-widget.pdf",
|
"file": "pdfs/annotation-button-widget.pdf",
|
||||||
"md5": "5cf23adfff84256d9cfe261bea96dade",
|
"md5": "5cf23adfff84256d9cfe261bea96dade",
|
||||||
|
Loading…
Reference in New Issue
Block a user