Merge pull request #21604 from calixteman/issue21593

Copy the backdrop for non-isolated groups with a soft mask
This commit is contained in:
Tim van der Meij 2026-07-20 20:00:28 +02:00 committed by GitHub
commit d314788368
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 201 additions and 12 deletions

View File

@ -3333,18 +3333,53 @@ class CanvasGraphics {
groupCtx.translate(-offsetX, -offsetY); groupCtx.translate(-offsetX, -offsetY);
groupCtx.transform(...currentTransform); groupCtx.transform(...currentTransform);
if ( const needsBackdropCopy =
!group.isolated && !group.isolated && !group.smask && group.needsIsolation;
!group.smask && const replaceBackdrop =
inSMaskMode && needsBackdropCopy &&
group.needsIsolation !inSMaskMode &&
) { savedKnockoutLevel === 0 &&
// For non-isolated groups that need isolation and are entered from SMask !group.knockout &&
// mode, copy the current canvas background so that inner blend modes !group.isGray &&
// (e.g. "screen") interact correctly with the background rather than group.hasSoftMask &&
// compositing onto a transparent canvas. currentCtx.globalAlpha === 1 &&
// Groups without needsIsolation have no inner blend modes; their content currentCtx.globalCompositeOperation === "source-over" &&
// is composited correctly via the SMask in endGroup without a copy. this.current.transferMaps === "none";
if (needsBackdropCopy && (inSMaskMode || replaceBackdrop)) {
// A non-isolated group that needs isolation (because of an inner blend
// mode and/or a soft mask) can't use the direct path above, so it
// renders on a transparent intermediate canvas. Copy the current
// backdrop into it so the inner blend modes (e.g. "multiply", "screen")
// interact with the real background instead of transparency; otherwise a
// /Multiply highlight, say, would be painted opaquely over the text
// behind it, hiding it (bug 1873345 -- same as the direct path, but here
// the soft mask forces an intermediate canvas).
// This is needed both when entered from SMask mode and at the top level;
// a non-isolated subgroup nested inside a knockout group instead gets its
// backdrop through the hasInnerBackdrop path in endGroup, so it's
// excluded here via savedKnockoutLevel.
//
// Outside SMask mode the copy is limited to a group that *would* have
// qualified for the direct path (source-over, group alpha 1, not
// knockout/gray) but was forced onto the intermediate canvas by its soft
// mask (`hasSoftMask`). Restricting to that domain keeps the change
// surgical:
// - Only the soft-mask case is the bug 1873345 gap; a non-isolated
// blend group pushed onto the intermediate canvas merely by a
// non-default group alpha (no soft mask) is a separate pre-existing
// case, left untouched to avoid changing its rendering (issue 13520).
// - source-over/alpha 1 let endGroup replace the backdrop region with
// the copied-and-updated result; compositing it normally would blend
// partially transparent backdrop pixels twice. Under an outer blend
// mode the outer blend would combine the backdrop with itself (e.g.
// a /Multiply group with an inner soft mask darkens, issue 12798); a
// non-1 group alpha would likewise mix the copied backdrop back in.
// Those groups keep the transparent canvas and blend against the
// real backdrop instead.
// - isGray groups are grayscaled wholesale in endGroup, and an
// inherited transfer filter (`transferMaps`) would be applied on
// write-back, both of which would corrupt the copied backdrop, so
// they're excluded too.
groupCtx.save(); groupCtx.save();
groupCtx.setTransform(1, 0, 0, 1, 0, 0); groupCtx.setTransform(1, 0, 0, 1, 0, 0);
groupCtx.drawImage(currentCtx.canvas, -offsetX, -offsetY); groupCtx.drawImage(currentCtx.canvas, -offsetX, -offsetY);
@ -3413,6 +3448,7 @@ class CanvasGraphics {
offsetX, offsetX,
offsetY, offsetY,
hasInnerBackdrop, hasInnerBackdrop,
replaceBackdrop,
knockoutMaskEntry, knockoutMaskEntry,
// Per-group scratch pools, lazily filled and freed in endGroup. // Per-group scratch pools, lazily filled and freed in endGroup.
knockoutTempEntry: null, knockoutTempEntry: null,
@ -3566,6 +3602,14 @@ class CanvasGraphics {
}); });
} }
} else { } else {
if (groupMeta.replaceBackdrop) {
// "copy" clears the destination outside the source within the
// current clip, so limit it to the intermediate canvas bounds.
const clip = new Path2D();
clip.rect(0, 0, groupCtx.canvas.width, groupCtx.canvas.height);
this.ctx.clip(clip);
this.ctx.globalCompositeOperation = "copy";
}
this.ctx.drawImage(groupCtx.canvas, 0, 0); this.ctx.drawImage(groupCtx.canvas, 0, 0);
} }
this.ctx.restore(); this.ctx.restore();

View File

@ -947,3 +947,4 @@
!issue21579.pdf !issue21579.pdf
!saslprep-r6.pdf !saslprep-r6.pdf
!jpx_smaskindata.pdf !jpx_smaskindata.pdf
!nonisolated_blend_smask.pdf

View File

@ -0,0 +1,137 @@
%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 320 240]
/Group << /Type /Group /S /Transparency /CS /DeviceRGB >>
/Resources <<
/Font << /F1 5 0 R >>
/ExtGState << /Half 7 0 R >>
/XObject << /FxText 6 0 R /FxAlpha 12 0 R >>
>>
/Contents 4 0 R >>
endobj
4 0 obj
<< /Length 419 >>
stream
q
/Half gs
0.10 0.35 1 rg
20 160 120 35 re f
180 160 120 35 re f
Q
BT /F1 17 Tf 0 0 0 rg 48 213 Td (The two blue boxes should match) Tj ET
BT /F1 12 Tf 0 0 0 rg 61 140 Td (control) Tj ET
BT /F1 12 Tf 0 0 0 rg 203 140 Td (inside group) Tj ET
BT /F1 12 Tf 0 0 0 rg 58 108 Td (Original issue: text should stay visible) Tj ET
1 1 1 rg
10 15 300 80 re f
BT /F1 30 Tf 0 0 0 rg 105 39 Td (Hidden) Tj ET
/FxText Do
/FxAlpha Do
endstream
endobj
5 0 obj
<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>
endobj
6 0 obj
<< /Type /XObject /Subtype /Form /FormType 1 /BBox [0 0 320 100]
/Group << /Type /Group /S /Transparency /CS /DeviceRGB /I false >>
/Resources <<
/ExtGState << /Blend 8 0 R /Masked 9 0 R >>
/XObject << /Paint 10 0 R >>
>> /Length 35 >>
stream
/Blend gs
q /Masked gs /Paint Do Q
endstream
endobj
7 0 obj
<< /Type /ExtGState /ca 0.5 /CA 0.5 >>
endobj
8 0 obj
<< /Type /ExtGState /BM /Multiply >>
endobj
9 0 obj
<< /Type /ExtGState /ca 1 /CA 1
/SMask << /Type /Mask /S /Alpha /G 11 0 R >> >>
endobj
10 0 obj
<< /Type /XObject /Subtype /Form /FormType 1 /BBox [0 0 320 100]
/Group << /Type /Group /S /Transparency /CS /DeviceRGB /I true >>
/Resources << >> /Length 36 >>
stream
0.55 0.35 0.95 rg
30 25 260 55 re f
endstream
endobj
11 0 obj
<< /Type /XObject /Subtype /Form /FormType 1 /BBox [0 0 320 100]
/Group << /Type /Group /S /Transparency /CS /DeviceGray /I true >>
/Resources << >> /Length 22 >>
stream
1 g
30 25 260 55 re f
endstream
endobj
12 0 obj
<< /Type /XObject /Subtype /Form /FormType 1 /BBox [170 150 310 200]
/Group << /Type /Group /S /Transparency /CS /DeviceRGB /I false >>
/Resources <<
/ExtGState << /Blend 8 0 R /Masked 13 0 R >>
/XObject << /Paint 14 0 R >>
>> /Length 35 >>
stream
/Blend gs
q /Masked gs /Paint Do Q
endstream
endobj
13 0 obj
<< /Type /ExtGState /ca 1 /CA 1
/SMask << /Type /Mask /S /Alpha /G 15 0 R >> >>
endobj
14 0 obj
<< /Type /XObject /Subtype /Form /FormType 1 /BBox [170 150 310 200]
/Group << /Type /Group /S /Transparency /CS /DeviceRGB /I true >>
/Resources << >> /Length 29 >>
stream
1 0 0 rg
170 150 140 50 re f
endstream
endobj
15 0 obj
<< /Type /XObject /Subtype /Form /FormType 1 /BBox [170 150 310 200]
/Group << /Type /Group /S /Transparency /CS /DeviceGray /I true >>
/Resources << >> /Length 1 >>
stream
endstream
endobj
xref
0 16
0000000000 65535 f
0000000015 00000 n
0000000064 00000 n
0000000121 00000 n
0000000434 00000 n
0000000904 00000 n
0000000974 00000 n
0000001306 00000 n
0000001360 00000 n
0000001412 00000 n
0000001514 00000 n
0000001755 00000 n
0000001983 00000 n
0000002321 00000 n
0000002424 00000 n
0000002662 00000 n
trailer
<< /Size 16 /Root 1 0 R >>
startxref
2872
%%EOF

View File

@ -14501,5 +14501,12 @@
"md5": "f7a8628b14ca176700247f106c16812a", "md5": "f7a8628b14ca176700247f106c16812a",
"rounds": 1, "rounds": 1,
"type": "eq" "type": "eq"
},
{
"id": "nonisolated_blend_smask",
"file": "pdfs/nonisolated_blend_smask.pdf",
"md5": "4b87611416be34894eeb19c158fafbad",
"rounds": 1,
"type": "eq"
} }
] ]