Skip to content

Commit 2415d9f

Browse files
document qr alpha compositing helpers
1 parent a408fc5 commit 2415d9f

File tree

3 files changed

+49
-0
lines changed

3 files changed

+49
-0
lines changed

CodeGlyphX.Tests/QrPngRendererTests.cs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,25 @@ public void Render_SimpleRgba_Preserves_Straight_Alpha_Over_Transparent_Backgrou
116116
Assert.Equal(128, rgba[3]);
117117
}
118118

119+
[Fact]
120+
public void Render_SimpleRgba_TransparentForeground_LeavesBackgroundUnchanged() {
121+
var matrix = new BitMatrix(1, 1);
122+
matrix[0, 0] = true;
123+
124+
var png = QrPngRenderer.Render(matrix, new QrPngRenderOptions {
125+
ModuleSize = 1,
126+
QuietZone = 0,
127+
Foreground = new Rgba32(0, 0, 0, 0),
128+
Background = new Rgba32(12, 34, 56, 78),
129+
});
130+
131+
var (rgba, _, _, _) = PngTestDecoder.DecodeRgba32(png);
132+
Assert.Equal(12, rgba[0]);
133+
Assert.Equal(34, rgba[1]);
134+
Assert.Equal(56, rgba[2]);
135+
Assert.Equal(78, rgba[3]);
136+
}
137+
119138
[Fact]
120139
public void Render_GeneralPath_Preserves_Straight_Alpha_Over_Transparent_Background() {
121140
var matrix = new BitMatrix(1, 1);
@@ -138,6 +157,28 @@ public void Render_GeneralPath_Preserves_Straight_Alpha_Over_Transparent_Backgro
138157
Assert.Equal(128, rgba[center + 3]);
139158
}
140159

160+
[Fact]
161+
public void Render_GeneralPath_TransparentForeground_LeavesBackgroundUnchanged() {
162+
var matrix = new BitMatrix(1, 1);
163+
matrix[0, 0] = true;
164+
165+
var png = QrPngRenderer.Render(matrix, new QrPngRenderOptions {
166+
ModuleSize = 5,
167+
QuietZone = 0,
168+
Foreground = new Rgba32(0, 0, 0, 0),
169+
Background = new Rgba32(12, 34, 56, 78),
170+
ModuleShape = QrPngModuleShape.Circle,
171+
ModuleScale = 0.8,
172+
});
173+
174+
var (rgba, width, height, stride) = PngTestDecoder.DecodeRgba32(png);
175+
var center = (height / 2) * stride + (width / 2) * 4;
176+
Assert.Equal(12, rgba[center + 0]);
177+
Assert.Equal(34, rgba[center + 1]);
178+
Assert.Equal(56, rgba[center + 2]);
179+
Assert.Equal(78, rgba[center + 3]);
180+
}
181+
141182
[Fact]
142183
public void Render_LogoOverlay_Preserves_Straight_Alpha_Over_Transparent_Background() {
143184
var matrix = new BitMatrix(1, 1);

CodeGlyphX/Rendering/Png/QrPngRenderer.Internal.Render.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,8 @@ private static bool IsSolidMask(bool[] mask) {
520520
}
521521

522522
private static Rgba32 CompositeColor(Rgba32 foreground, Rgba32 background) {
523+
// Keep solid-module precompositing aligned with the per-pixel renderer so
524+
// transparent output stores straight-alpha colors consistently.
523525
return ComposeOver(foreground, background);
524526
}
525527

@@ -3142,6 +3144,8 @@ private static int PositiveMod(int value, int modulus) {
31423144
return m < 0 ? m + modulus : m;
31433145
}
31443146

3147+
// Compose straight-alpha colors using Porter-Duff "over" so transparent
3148+
// canvases preserve the source RGB instead of leaking hidden background RGB.
31453149
private static Rgba32 ComposeOver(Rgba32 top, Rgba32 bottom) {
31463150
var ta = top.A;
31473151
if (ta == 0) return bottom;
@@ -3177,6 +3181,8 @@ private static void BlendPixel(byte[] scanlines, int stride, int x, int y, Rgba3
31773181
BlendPixelAt(scanlines, rowStart, color);
31783182
}
31793183

3184+
// Blend onto the stored RGBA pixel with the same straight-alpha math used by
3185+
// the simple and solid paths. This keeps modules, eyes, and logos consistent.
31803186
private static void BlendPixelAt(byte[] buffer, int offset, Rgba32 color) {
31813187
if (color.A == 0) return;
31823188
if (color.A == 255) {

CodeGlyphX/Rendering/Png/QrPngRenderer.Internal.SimpleRgba.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,8 @@ private static void FillRowSimple(BitMatrix modules, QrPngRenderOptions opts, in
194194
}
195195

196196
private static Rgba32 CompositeForeground(Rgba32 foreground, Rgba32 background) {
197+
// Reuse the shared compositor so the simple square path matches the
198+
// general renderer when foreground alpha is less than fully opaque.
197199
return ComposeOver(foreground, background);
198200
}
199201

0 commit comments

Comments
 (0)