Merge pull request #21235 from calixteman/bug2033095

Improve soft mask composition performance (bug 2033095)
This commit is contained in:
Tim van der Meij 2026-05-07 21:35:09 +02:00 committed by GitHub
commit a328294713
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 615 additions and 244 deletions

View File

@ -34,6 +34,7 @@ import {
import {
getCurrentTransform,
getCurrentTransformInverse,
getRGBA,
makePathFromDrawOPS,
OutputScale,
PixelsPerInch,
@ -85,120 +86,45 @@ function mirrorContextOperations(ctx, destCtx) {
if (ctx._removeMirroring) {
throw new Error("Context is already forwarding operations.");
}
ctx.__originalSave = ctx.save;
ctx.__originalRestore = ctx.restore;
ctx.__originalRotate = ctx.rotate;
ctx.__originalScale = ctx.scale;
ctx.__originalTranslate = ctx.translate;
ctx.__originalTransform = ctx.transform;
ctx.__originalSetTransform = ctx.setTransform;
ctx.__originalResetTransform = ctx.resetTransform;
ctx.__originalClip = ctx.clip;
ctx.__originalMoveTo = ctx.moveTo;
ctx.__originalLineTo = ctx.lineTo;
ctx.__originalBezierCurveTo = ctx.bezierCurveTo;
ctx.__originalRect = ctx.rect;
ctx.__originalClosePath = ctx.closePath;
ctx.__originalBeginPath = ctx.beginPath;
const originalMethods = new Map();
for (const name of [
"save",
"restore",
"rotate",
"scale",
"translate",
"transform",
"setTransform",
"resetTransform",
"clip",
"moveTo",
"lineTo",
"bezierCurveTo",
"quadraticCurveTo",
"arc",
"arcTo",
"ellipse",
"rect",
"roundRect",
"closePath",
"beginPath",
]) {
const original = ctx[name];
if (typeof original !== "function" || typeof destCtx[name] !== "function") {
continue;
}
originalMethods.set(name, original);
ctx[name] = function (...args) {
destCtx[name](...args);
return original.apply(this, args);
};
}
ctx._removeMirroring = () => {
ctx.save = ctx.__originalSave;
ctx.restore = ctx.__originalRestore;
ctx.rotate = ctx.__originalRotate;
ctx.scale = ctx.__originalScale;
ctx.translate = ctx.__originalTranslate;
ctx.transform = ctx.__originalTransform;
ctx.setTransform = ctx.__originalSetTransform;
ctx.resetTransform = ctx.__originalResetTransform;
ctx.clip = ctx.__originalClip;
ctx.moveTo = ctx.__originalMoveTo;
ctx.lineTo = ctx.__originalLineTo;
ctx.bezierCurveTo = ctx.__originalBezierCurveTo;
ctx.rect = ctx.__originalRect;
ctx.closePath = ctx.__originalClosePath;
ctx.beginPath = ctx.__originalBeginPath;
delete ctx._removeMirroring;
};
ctx.save = function () {
destCtx.save();
this.__originalSave();
};
ctx.restore = function () {
destCtx.restore();
this.__originalRestore();
};
ctx.translate = function (x, y) {
destCtx.translate(x, y);
this.__originalTranslate(x, y);
};
ctx.scale = function (x, y) {
destCtx.scale(x, y);
this.__originalScale(x, y);
};
ctx.transform = function (a, b, c, d, e, f) {
destCtx.transform(a, b, c, d, e, f);
this.__originalTransform(a, b, c, d, e, f);
};
ctx.setTransform = function (a, b, c, d, e, f) {
if (b === undefined) {
destCtx.setTransform(a);
this.__originalSetTransform(a);
} else {
destCtx.setTransform(a, b, c, d, e, f);
this.__originalSetTransform(a, b, c, d, e, f);
for (const [name, original] of originalMethods) {
ctx[name] = original;
}
};
ctx.resetTransform = function () {
destCtx.resetTransform();
this.__originalResetTransform();
};
ctx.rotate = function (angle) {
destCtx.rotate(angle);
this.__originalRotate(angle);
};
ctx.clip = function (rule) {
destCtx.clip(rule);
this.__originalClip(rule);
};
ctx.moveTo = function (x, y) {
destCtx.moveTo(x, y);
this.__originalMoveTo(x, y);
};
ctx.lineTo = function (x, y) {
destCtx.lineTo(x, y);
this.__originalLineTo(x, y);
};
ctx.bezierCurveTo = function (cp1x, cp1y, cp2x, cp2y, x, y) {
destCtx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
this.__originalBezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
};
ctx.rect = function (x, y, width, height) {
destCtx.rect(x, y, width, height);
this.__originalRect(x, y, width, height);
};
ctx.closePath = function () {
destCtx.closePath();
this.__originalClosePath();
};
ctx.beginPath = function () {
destCtx.beginPath();
this.__originalBeginPath();
delete ctx._removeMirroring;
};
}
@ -655,6 +581,18 @@ class CanvasGraphics {
this.smaskPreparedFor = null;
this.smaskPreparedOffsetX = 0;
this.smaskPreparedOffsetY = 0;
// For mask-size prebakes with non-zero OOB alpha, the constant
// alpha applied to OOB pixels (dirty box outside the mask canvas)
// at compose time. Null when no compose-time OOB work is needed:
// - layer-size prebake bakes OOB inline; or
// - OOB alpha is 0 and destination-in's transparent source
// samples clear OOB layer pixels for free.
// Compose-time behavior splits on this:
// null -> clip = full dirty box; OOB cleared or baked.
// 255 -> clip excludes OOB; OOB survives unchanged.
// intermediate -> clip excludes OOB, then a fade pass applies
// this constant alpha.
this.smaskPreparedOOBAlpha = null;
this.suspendedCtx = null;
this.contentVisible = true;
this.markedContentStack = markedContentStack || [];
@ -1270,14 +1208,15 @@ class CanvasGraphics {
this.smaskPreparedFor = null;
this.smaskPreparedOffsetX = 0;
this.smaskPreparedOffsetY = 0;
this.smaskPreparedOOBAlpha = null;
}
_ensurePreparedSMask(smask, width, height) {
_ensurePreparedSMask(smask) {
if (smask === this.smaskPreparedFor) {
return;
}
this._clearPreparedSMask();
this._prepareSMaskCanvas(smask, width, height);
this._prepareSMaskCanvas(smask);
}
checkSMaskState(opIdx) {
@ -1291,94 +1230,145 @@ class CanvasGraphics {
// (e.g. a direct SMask A->B replacement, or a restore() that surfaces
// a different saved mask). _ensurePreparedSMask is a no-op when the
// same mask object is re-encountered.
this._ensurePreparedSMask(
this.current.activeSMask,
this.ctx.canvas.width,
this.ctx.canvas.height
);
this._ensurePreparedSMask(this.current.activeSMask);
}
}
/**
* Backdrop cases use a layer-sized canvas so that the backdrop color
* correctly extends to pixels outside the mask canvas bounds.
* Filter-only cases use a mask-sized canvas to avoid a large allocation when
* the mask is small relative to the page; `composeSMask` then uses
* `smaskPreparedOffsetX/Y` to translate dirty-box coordinates into the
* smaller canvas's coordinate space. Plain-alpha masks with no backdrop or
* transfer map need no canvas at all.
*/
_prepareSMaskCanvas(smask, width, height) {
_prepareSMaskCanvas(smask) {
const { canvas: maskCanvas, subtype, backdrop, transferMap } = smask;
const hasFilter =
subtype === "Luminosity" || (subtype === "Alpha" && transferMap);
if (!backdrop && !hasFilter) {
// No canvas to prepare, but record the mask so that checkSMaskState's
// identity check does not keep re-entering the rebuild path for the same
// plain-alpha mask on every restore()/setGState() call.
// Nothing to amortize unless we have a filter or a Luminosity
// backdrop -- Alpha SMasks ignore /BC for the alpha output, and
// unknown subtypes have no defined backdrop semantics. Record the
// mask so checkSMaskState's identity check skips the rebuild path
// on subsequent restore()/setGState() calls.
if (!hasFilter && !(subtype === "Luminosity" && backdrop)) {
this.smaskPreparedFor = smask;
return;
}
let preparedEntry, offsetX, offsetY;
if (backdrop && hasFilter) {
// Both backdrop and filter: must apply backdrop BEFORE filter (spec
// order). Use a layer-sized intermediate so that pixels outside the
// mask canvas bounds get the backdrop color before filtering.
const srcEntry = this.canvasFactory.create(width, height);
const sCtx = srcEntry.context;
sCtx.drawImage(maskCanvas, smask.offsetX, smask.offsetY);
sCtx.globalCompositeOperation = "destination-atop";
sCtx.fillStyle = backdrop;
sCtx.fillRect(0, 0, width, height);
sCtx.globalCompositeOperation = "source-over";
preparedEntry = this.canvasFactory.create(width, height);
const pCtx = preparedEntry.context;
pCtx.filter =
subtype === "Alpha"
? this.filterFactory.addAlphaFilter(transferMap)
: this.filterFactory.addLuminosityFilter(transferMap);
pCtx.drawImage(srcEntry.canvas, 0, 0);
pCtx.filter = "none";
this.canvasFactory.destroy(srcEntry);
offsetX = offsetY = 0;
} else if (hasFilter) {
// Filter only, no backdrop: use a mask-sized canvas to avoid allocating
// a full width × height page canvas for what may be a small mask. The
// mask is drawn at (0, 0) and composeSMask compensates via
// smaskPreparedOffsetX/Y.
preparedEntry = this.canvasFactory.create(
maskCanvas.width,
maskCanvas.height
);
const pCtx = preparedEntry.context;
pCtx.filter =
subtype === "Alpha"
? this.filterFactory.addAlphaFilter(transferMap)
: this.filterFactory.addLuminosityFilter(transferMap);
pCtx.drawImage(maskCanvas, 0, 0);
pCtx.filter = "none";
({ offsetX, offsetY } = smask);
// Constant alpha OOB pixels receive after the spec backdrop+filter
// chain (see smaskPreparedOOBAlpha field doc for the compose-time
// table). /BC only feeds the alpha output for Luminosity (its
// color enters the luminance computation). Alpha SMasks treat /BC
// as a pure color-space backdrop and must not bake it into the
// alpha output.
let filteredOOBAlpha;
if (subtype === "Luminosity" && backdrop) {
// backdrop is "#RRGGBB" (see Evaluator#handleSMask).
const [r, g, b] = getRGBA(backdrop);
const inputAlpha = Math.round(0.3 * r + 0.59 * g + 0.11 * b);
filteredOOBAlpha = transferMap?.[inputAlpha] ?? inputAlpha;
} else {
// Backdrop only (no filter): layer-sized canvas. destination-atop on
// the full width × height fills every transparent pixel — including those
// outside the mask canvas bounds — with the backdrop color.
preparedEntry = this.canvasFactory.create(width, height);
const pCtx = preparedEntry.context;
pCtx.drawImage(maskCanvas, smask.offsetX, smask.offsetY);
pCtx.globalCompositeOperation = "destination-atop";
pCtx.fillStyle = backdrop;
pCtx.fillRect(0, 0, width, height);
pCtx.globalCompositeOperation = "source-over";
offsetX = offsetY = 0;
// Alpha, or Luminosity with no backdrop: OOB input is transparent,
// and both filters map alpha=0 to alpha=0; only transferMap[0] can
// produce a non-zero result.
filteredOOBAlpha = transferMap?.[0] ?? 0;
}
// Use a layer-size prebake when the layer is at most this many
// times bigger than the mask: layer-size avoids compose-time OOB
// work and hits the same-size drawImage GPU fast path, but the
// alloc cost grows with the layer. The crossover is empirical;
// tuned against the bug-2033095 corpus.
const SMASK_LAYER_TO_MASK_AREA_RATIO = 4;
const { width: layerW, height: layerH } = this.ctx.canvas;
const maskArea = maskCanvas.width * maskCanvas.height;
const useLayerSize =
layerW * layerH < SMASK_LAYER_TO_MASK_AREA_RATIO * maskArea;
let filterUrl = null;
if (hasFilter) {
filterUrl =
subtype === "Alpha"
? this.filterFactory.addAlphaFilter(transferMap)
: this.filterFactory.addLuminosityFilter(transferMap);
}
// Alpha SMasks must not bake /BC into the prepared canvas (see
// filteredOOBAlpha comment above).
const bakedBackdrop = subtype === "Luminosity" ? backdrop : null;
let preparedEntry, offsetX, offsetY;
if (useLayerSize) {
preparedEntry = this._bakeSMaskCanvas(
maskCanvas,
smask.offsetX,
smask.offsetY,
layerW,
layerH,
bakedBackdrop,
filterUrl
);
offsetX = 0;
offsetY = 0;
} else {
preparedEntry = this._bakeSMaskCanvas(
maskCanvas,
0,
0,
maskCanvas.width,
maskCanvas.height,
bakedBackdrop,
filterUrl
);
offsetX = smask.offsetX;
offsetY = smask.offsetY;
}
this.smaskPreparedEntry = preparedEntry;
this.smaskPreparedFor = smask;
this.smaskPreparedOffsetX = offsetX;
this.smaskPreparedOffsetY = offsetY;
// Only mask-size prebakes with non-zero OOB alpha need compose-time
// OOB work (see field doc).
this.smaskPreparedOOBAlpha =
!useLayerSize && filteredOOBAlpha !== 0 ? filteredOOBAlpha : null;
}
/**
* Bake the mask plus optional backdrop into a (w x h) canvas with the
* mask drawn at (drawX, drawY), then optionally pipe through
* `filterUrl`. Returns the prepared canvas-factory entry.
*
* The backdrop fill uses destination-atop so transparent / partial-
* alpha pixels inside the mask see the backdrop *before* filtering
* (per PDF spec). Filtering the raw mask would yield filter(0)
* instead of filter(backdrop) -- wrong for "keep" Luminosity and for
* Alpha masks whose transferMap[255] differs from transferMap[0].
*
* In the no-backdrop layer-size case the OOB region of srcEntry
* stays transparent and the filter outputs filter(transparent) =
* transferMap[0], matching the spec's transparent extension of the
* mask group. No-backdrop mask-size prebakes have no OOB region;
* destination-in handles OOB at compose time.
*/
_bakeSMaskCanvas(maskCanvas, drawX, drawY, w, h, backdrop, filterUrl) {
if (!backdrop && !filterUrl) {
// Caller (_prepareSMaskCanvas) gates on this; without either,
// the prebake would just be a wasted copy of the mask.
unreachable("_bakeSMaskCanvas with neither backdrop nor filter");
}
const srcEntry = this.canvasFactory.create(w, h);
const sCtx = srcEntry.context;
sCtx.drawImage(maskCanvas, drawX, drawY);
if (backdrop) {
sCtx.globalCompositeOperation = "destination-atop";
sCtx.fillStyle = backdrop;
sCtx.fillRect(0, 0, w, h);
}
if (!filterUrl) {
return srcEntry;
}
const preparedEntry = this.canvasFactory.create(w, h);
const pCtx = preparedEntry.context;
pCtx.filter = filterUrl;
pCtx.drawImage(srcEntry.canvas, 0, 0);
pCtx.filter = "none";
this.canvasFactory.destroy(srcEntry);
return preparedEntry;
}
/**
@ -1402,11 +1392,7 @@ class CanvasGraphics {
copyCtxState(this.suspendedCtx, ctx);
mirrorContextOperations(ctx, this.suspendedCtx);
this._ensurePreparedSMask(
this.current.activeSMask,
drawnWidth,
drawnHeight
);
this._ensurePreparedSMask(this.current.activeSMask);
this.setGState(opIdx, [["BM", "source-over"]]);
}
@ -1432,14 +1418,16 @@ class CanvasGraphics {
return;
}
if (!dirtyBox) {
dirtyBox = [0, 0, this.ctx.canvas.width, this.ctx.canvas.height];
} else {
dirtyBox[0] = Math.floor(dirtyBox[0]);
dirtyBox[1] = Math.floor(dirtyBox[1]);
dirtyBox[2] = Math.ceil(dirtyBox[2]);
dirtyBox[3] = Math.ceil(dirtyBox[3]);
}
// Don't mutate the caller's box -- callers (e.g. consumePath) may
// hold on to it.
dirtyBox = dirtyBox
? [
Math.floor(dirtyBox[0]),
Math.floor(dirtyBox[1]),
Math.ceil(dirtyBox[2]),
Math.ceil(dirtyBox[3]),
]
: [0, 0, this.ctx.canvas.width, this.ctx.canvas.height];
const smask = this.current.activeSMask;
const suspendedCtx = this.suspendedCtx;
@ -1469,44 +1457,73 @@ class CanvasGraphics {
const preparedEntry = this.smaskPreparedEntry;
if (preparedEntry) {
// Fast path: backdrop and/or filter pre-applied. For layer-sized entries
// (backdrop cases) smaskPreparedOffsetX/Y are 0 so source and destination
// coordinates are identical. For mask-sized entries (filter-only) we
// subtract the mask's layer offset to convert the dirty-box position into
// the smaller canvas's coordinate space.
// Out-of-bounds source pixels are treated as transparent by the specs,
// which is correct for a no-backdrop mask.
const srcX = layerOffsetX - this.smaskPreparedOffsetX;
const srcY = layerOffsetY - this.smaskPreparedOffsetY;
layerCtx.save();
layerCtx.globalAlpha = 1;
layerCtx.setTransform(1, 0, 0, 1, 0, 0);
const clip = new Path2D();
clip.rect(layerOffsetX, layerOffsetY, layerWidth, layerHeight);
layerCtx.clip(clip);
layerCtx.globalCompositeOperation = "destination-in";
layerCtx.drawImage(
preparedEntry.canvas,
srcX,
srcY,
layerWidth,
layerHeight,
layerOffsetX,
layerOffsetY,
layerWidth,
layerHeight
);
layerCtx.restore();
// Fast path: prepared-mask destination-in drawImage. See
// smaskPreparedOOBAlpha field doc for the OOB handling table.
let clipX = layerOffsetX;
let clipY = layerOffsetY;
let clipW = layerWidth;
let clipH = layerHeight;
const oobAlpha = this.smaskPreparedOOBAlpha;
const hasOOBAlpha = oobAlpha !== null;
if (hasOOBAlpha) {
clipX = Math.max(layerOffsetX, smask.offsetX);
clipY = Math.max(layerOffsetY, smask.offsetY);
const x1 = Math.min(
layerOffsetX + layerWidth,
smask.offsetX + smask.canvas.width
);
const y1 = Math.min(
layerOffsetY + layerHeight,
smask.offsetY + smask.canvas.height
);
clipW = x1 - clipX;
clipH = y1 - clipY;
}
if (clipW > 0 && clipH > 0) {
const srcX = clipX - this.smaskPreparedOffsetX;
const srcY = clipY - this.smaskPreparedOffsetY;
layerCtx.save();
layerCtx.globalAlpha = 1;
layerCtx.setTransform(1, 0, 0, 1, 0, 0);
const clip = new Path2D();
clip.rect(clipX, clipY, clipW, clipH);
layerCtx.clip(clip);
layerCtx.globalCompositeOperation = "destination-in";
layerCtx.drawImage(
preparedEntry.canvas,
srcX,
srcY,
clipW,
clipH,
clipX,
clipY,
clipW,
clipH
);
layerCtx.restore();
}
if (hasOOBAlpha && oobAlpha < 255) {
this._applySMaskOOBAlpha(
layerCtx,
layerOffsetX,
layerOffsetY,
layerWidth,
layerHeight,
clipX,
clipY,
clipX + clipW,
clipY + clipH,
oobAlpha
);
}
} else {
this.genericComposeSMask(
smask.context,
smask,
layerCtx,
layerWidth,
layerHeight,
layerOffsetX,
layerOffsetY,
smask.offsetX,
smask.offsetY
layerOffsetY
);
}
@ -1514,8 +1531,8 @@ class CanvasGraphics {
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = smask.blendMode || "source-over";
ctx.setTransform(1, 0, 0, 1, 0, 0);
// Only blit the dirty box region — the rest of the scratch canvas is
// still transparent from the clearRect in compose().
// Blit only the dirty box -- the rest of the scratch canvas was
// cleared in compose().
ctx.drawImage(
layerCtx.canvas,
layerOffsetX,
@ -1530,22 +1547,75 @@ class CanvasGraphics {
ctx.restore();
}
/**
* Fade the dirty box's OOB region by a constant alpha. Called from
* composeSMask when smaskPreparedOOBAlpha is in (0, 255).
*
* destination-in clears every destination pixel outside the source's
* footprint, so four fillRects (one per strip) would each clear the
* others. Instead one fillRect covers the dirty box, restricted by
* an even-odd clip enclosing exactly (dirty_box XOR mask_region);
* within the clip the source covers everything so no "outside
* source" pixels exist.
*/
_applySMaskOOBAlpha(
layerCtx,
layerOffsetX,
layerOffsetY,
layerWidth,
layerHeight,
maskX0,
maskY0,
maskX1,
maskY1,
alpha
) {
const hasInnerCutout = maskX0 < maskX1 && maskY0 < maskY1;
if (
hasInnerCutout &&
maskX0 === layerOffsetX &&
maskY0 === layerOffsetY &&
maskX1 === layerOffsetX + layerWidth &&
maskY1 === layerOffsetY + layerHeight
) {
// Dirty box is entirely inside the mask -- no OOB region to fade.
return;
}
const path = new Path2D();
path.rect(layerOffsetX, layerOffsetY, layerWidth, layerHeight);
if (hasInnerCutout) {
path.rect(maskX0, maskY0, maskX1 - maskX0, maskY1 - maskY0);
}
layerCtx.save();
layerCtx.globalAlpha = alpha / 255;
layerCtx.setTransform(1, 0, 0, 1, 0, 0);
layerCtx.clip(path, "evenodd");
layerCtx.globalCompositeOperation = "destination-in";
// MUST be fully opaque -- destination-in scales dst_a by src_a, and
// globalAlpha must be the only thing scaling source alpha.
layerCtx.fillStyle = "#000000";
layerCtx.fillRect(layerOffsetX, layerOffsetY, layerWidth, layerHeight);
layerCtx.restore();
}
genericComposeSMask(
maskCtx,
smask,
layerCtx,
width,
height,
layerOffsetX,
layerOffsetY,
maskOffsetX,
maskOffsetY
layerOffsetY
) {
// This path is only reached when there is no backdrop and no filter
// (those cases are handled by the _prepareSMaskCanvas fast path).
// A simple destination-in blit of the mask onto the layer suffices.
const maskCanvas = maskCtx.canvas;
const maskX = layerOffsetX - maskOffsetX;
const maskY = layerOffsetY - maskOffsetY;
// composeSMask helper, reached only for plain-alpha masks (no
// filter, no backdrop); every backdrop/filter case prebakes in
// _prepareSMaskCanvas. A single destination-in blit suffices:
// transparent OOB mask samples clear OOB layer pixels.
const {
context: maskCtx,
offsetX: maskOffsetX,
offsetY: maskOffsetY,
} = smask;
layerCtx.save();
layerCtx.globalAlpha = 1;
@ -1555,9 +1625,9 @@ class CanvasGraphics {
layerCtx.clip(clip);
layerCtx.globalCompositeOperation = "destination-in";
layerCtx.drawImage(
maskCanvas,
maskX,
maskY,
maskCtx.canvas,
layerOffsetX - maskOffsetX,
layerOffsetY - maskOffsetY,
width,
height,
layerOffsetX,

View File

@ -908,3 +908,7 @@
!issue21126.pdf
!bug2035197_1.pdf
!bug2035197_2.pdf
!smask_alpha_oob.pdf
!smask_alpha_oob_transfer.pdf
!smask_alpha_bc.pdf
!smask_luminosity_oob_transfer.pdf

View File

@ -0,0 +1,66 @@
%PDF-1.7
%ÿÿÿÿ
1 0 obj
<< /Type /Catalog /Pages 2 0 R >>
endobj
2 0 obj
<< /Type /Pages /Kids [3 0 R] /Count 1 >>
endobj
3 0 obj
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 220 160]
/Resources << /ExtGState << /GS1 4 0 R >> >>
/Contents 6 0 R >>
endobj
4 0 obj
<< /Type /ExtGState
/SMask << /Type /Mask /S /Alpha /G 5 0 R /BC [1] >>
>>
endobj
5 0 obj
<< /Type /XObject /Subtype /Form /FormType 1
/BBox [40 30 100 90]
/Resources << >>
/Group << /Type /Group /S /Transparency /CS /DeviceGray >>
/Length 20 >>
stream
1 g
40 30 60 60 re
f
endstream
endobj
6 0 obj
<< /Length 118 >>
stream
q
0.95 0.95 0.95 rg
0 0 220 160 re
f
Q
q
/GS1 gs
0.2 0.6 0.9 rg
10 10 200 140 re
f
Q
q
0 0 0 RG
1 w
40 30 60 60 re
S
Q
endstream
endobj
xref
0 7
0000000000 65535 f
0000000015 00000 n
0000000064 00000 n
0000000121 00000 n
0000000259 00000 n
0000000352 00000 n
0000000573 00000 n
trailer
<< /Size 7 /Root 1 0 R >>
startxref
742
%%EOF

View File

@ -0,0 +1,65 @@
%PDF-1.7
%âãÏÓ
1 0 obj
<< /Type /Catalog /Pages 2 0 R >>
endobj
2 0 obj
<< /Type /Pages /Kids [3 0 R] /Count 1 >>
endobj
3 0 obj
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 600 600]
/Resources << /ExtGState << /GS1 4 0 R >> >>
/Contents 7 0 R >>
endobj
4 0 obj
<< /Type /ExtGState
/SMask << /Type /Mask /S /Alpha /G 5 0 R /BC [0.5] /TR 6 0 R >>
>>
endobj
5 0 obj
<< /Type /XObject /Subtype /Form /FormType 1
/BBox [50 50 150 150]
/Resources << >>
/Group << /Type /Group /S /Transparency /CS /DeviceGray >>
/Length 25 >>
stream
1.0 g
50 50 100 100 re
f
endstream
endobj
6 0 obj
<< /FunctionType 2 /Domain [0 1] /Range [0 1] /N 1 /C0 [0] /C1 [0.5] >>
endobj
7 0 obj
<< /Length 82 >>
stream
q
/GS1 gs
0.2 0.6 0.9 rg
0 0 600 600 re
f
Q
q
0 0 0 rg
0.5 w
50 50 100 100 re
S
Q
endstream
endobj
xref
0 8
0000000000 65535 f
0000000015 00000 n
0000000064 00000 n
0000000121 00000 n
0000000259 00000 n
0000000364 00000 n
0000000590 00000 n
0000000677 00000 n
trailer
<< /Size 8 /Root 1 0 R >>
startxref
808
%%EOF

View File

@ -0,0 +1,65 @@
%PDF-1.7
%âãÏÓ
1 0 obj
<< /Type /Catalog /Pages 2 0 R >>
endobj
2 0 obj
<< /Type /Pages /Kids [3 0 R] /Count 1 >>
endobj
3 0 obj
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 600 600]
/Resources << /ExtGState << /GS1 4 0 R >> >>
/Contents 7 0 R >>
endobj
4 0 obj
<< /Type /ExtGState
/SMask << /Type /Mask /S /Alpha /G 5 0 R /BC [0.5] /TR 6 0 R >>
>>
endobj
5 0 obj
<< /Type /XObject /Subtype /Form /FormType 1
/BBox [50 50 150 150]
/Resources << >>
/Group << /Type /Group /S /Transparency /CS /DeviceGray >>
/Length 25 >>
stream
1.0 g
50 50 100 100 re
f
endstream
endobj
6 0 obj
<< /FunctionType 2 /Domain [0 1] /Range [0 1] /N 1 /C0[0.5]/C1[1] >>
endobj
7 0 obj
<< /Length 82 >>
stream
q
/GS1 gs
0.2 0.6 0.9 rg
0 0 600 600 re
f
Q
q
0 0 0 rg
0.5 w
50 50 100 100 re
S
Q
endstream
endobj
xref
0 8
0000000000 65535 f
0000000015 00000 n
0000000064 00000 n
0000000121 00000 n
0000000259 00000 n
0000000364 00000 n
0000000590 00000 n
0000000677 00000 n
trailer
<< /Size 8 /Root 1 0 R >>
startxref
808
%%EOF

View File

@ -0,0 +1,73 @@
%PDF-1.7
%ÿÿÿÿ
1 0 obj
<< /Type /Catalog /Pages 2 0 R >>
endobj
2 0 obj
<< /Type /Pages /Kids [3 0 R] /Count 1 >>
endobj
3 0 obj
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 500 300]
/Resources << /ExtGState << /GS1 4 0 R >> >>
/Contents 7 0 R >>
endobj
4 0 obj
<< /Type /ExtGState
/SMask << /Type /Mask /S /Luminosity /G 5 0 R /BC [1] /TR 6 0 R >>
>>
endobj
5 0 obj
<< /Type /XObject /Subtype /Form /FormType 1
/BBox [70 80 150 140]
/Resources << >>
/Group << /Type /Group /S /Transparency /CS /DeviceGray >>
/Length 42 >>
stream
0 g
70 80 40 60 re
f
1 g
110 80 40 60 re
f
endstream
endobj
6 0 obj
<< /FunctionType 2 /Domain [0 1] /Range [0 1] /N 1 /C0 [0.25] /C1 [0.75] >>
endobj
7 0 obj
<< /Length 117 >>
stream
q
0.95 0.95 0.95 rg
0 0 500 300 re
f
Q
q
/GS1 gs
0.85 0.2 0.1 rg
0 0 500 300 re
f
Q
q
0 0 0 RG
1 w
70 80 80 60 re
S
Q
endstream
endobj
xref
0 8
0000000000 65535 f
0000000015 00000 n
0000000064 00000 n
0000000121 00000 n
0000000259 00000 n
0000000367 00000 n
0000000611 00000 n
0000000702 00000 n
trailer
<< /Size 8 /Root 1 0 R >>
startxref
870
%%EOF

View File

@ -14168,5 +14168,33 @@
}
},
"type": "eq"
},
{
"id": "smask_alpha_oob",
"file": "pdfs/smask_alpha_oob.pdf",
"md5": "51b86d51d5311db6805a7e5be2ee0aeb",
"rounds": 1,
"type": "eq"
},
{
"id": "smask_alpha_oob_transfer",
"file": "pdfs/smask_alpha_oob_transfer.pdf",
"md5": "4c5fa755f6cc26283f1b21f9b5f59ada",
"rounds": 1,
"type": "eq"
},
{
"id": "smask_alpha_bc",
"file": "pdfs/smask_alpha_bc.pdf",
"md5": "493e7aeef054aab09f325f48703d5ba8",
"rounds": 1,
"type": "eq"
},
{
"id": "smask_luminosity_oob_transfer",
"file": "pdfs/smask_luminosity_oob_transfer.pdf",
"md5": "267dc76f33cc0c0b6d36ff4605f60907",
"rounds": 1,
"type": "eq"
}
]