Skip to content

fix: harden canvas badge sizing and tails#44

Merged
PrzemyslawKlys merged 3 commits intofeature/art-enhancements-20260127from
fix/badge-sizing-20260128
Jan 28, 2026
Merged

fix: harden canvas badge sizing and tails#44
PrzemyslawKlys merged 3 commits intofeature/art-enhancements-20260127from
fix/badge-sizing-20260128

Conversation

@PrzemyslawKlys
Copy link
Member

Summary:\n- Skip badge rendering when WidthPx/HeightPx <= 0\n- Clamp ribbon tail dimensions for tiny badges\n- Add regression tests for zero-size and tiny ribbon badges\n\nNotes:\n- Stacks on PR #42 (feature/art-enhancements-20260127)\n

Copilot AI review requested due to automatic review settings January 28, 2026 17:32
@claude
Copy link

claude bot commented Jan 28, 2026

Claude finished @PrzemyslawKlys's task —— View job


Code Review Complete ✅

Reviewing PR #44: "fix: harden canvas badge sizing and tails"

Todo List:

  • Read and analyze changed files
  • Review code quality and best practices
  • Check for potential bugs or issues
  • Evaluate performance considerations
  • Assess security concerns
  • Review test coverage
  • Address Copilot reviewer feedback
  • Provide final recommendations

🔍 Review Summary

This PR implements defensive programming improvements to handle edge cases in canvas badge rendering. The changes are well-structured and include proper test coverage.

Strengths

1. Robust Early Return Logic

  • The early return if (badge.WidthPx <= 0 || badge.HeightPx <= 0) return; at QrPngRenderer.Internal.A.cs:2575 is clean and prevents unnecessary computation
  • Eliminates redundant Math.Max(1, ...) clamping that was masking the underlying issue

2. Smart Tail Dimension Clamping

  • Lines 2718-2721 and 2766-2769 add intelligent bounds checking for ribbon tails
  • The logic var tailBase = Math.Max(4, Math.Min(w / 3, h * 2)) provides reasonable default proportions
  • Additional safety check if (tailWidth < 2) return; prevents degenerate triangles

3. Comprehensive Test Coverage

  • Render_With_Zero_Size_Badge_Skips_Drawing() verifies zero-dimension handling
  • Render_With_Tiny_Ribbon_Badge_Skips_Tails() tests edge cases for tiny ribbons
  • Tests use proper pixel inspection to validate behavior

4. Documentation Updates

  • XML comments in QrPngCanvasBadgeOptions.cs:20,25 clearly state that 0 disables drawing

⚠️ Issues Found

1. Duplicate Test Assertion (Confirmed by Copilot)

// QrPngRendererTests.cs:1537 - This line appears to be duplicate from line 1483
Assert.True(foundBand, "Expected band pixels outside the QR bounds.");

2. Incomplete Test Coverage (Confirmed by Copilot)
The test Render_With_Zero_Size_Badge_Skips_Drawing() only tests WidthPx = 0 but not HeightPx = 0. Since the condition checks both dimensions, both cases should be tested.

3. Minor Code Style

  • The tail clamping logic could benefit from explanatory comments about the magic numbers (4, 2)

🚀 Performance & Security

Performance: ✅ Excellent - Early returns prevent unnecessary computation for invalid badges

Security: ✅ No concerns - All input validation is defensive and bounds-checked

📝 Recommendations

  1. Fix duplicate assertion in QrPngRendererTests.cs:1537

  2. Add HeightPx = 0 test case:

[Fact]
public void Render_With_Zero_Height_Badge_Skips_Drawing() {
    // Similar to existing test but with HeightPx = 0, WidthPx = 24
}
  1. Consider adding explanatory comments for tail dimension constants:
var tailBase = Math.Max(4, Math.Min(w / 3, h * 2)); // Min 4px for visible tail
// ...
if (tailWidth < 2) return; // Skip if too narrow to render properly

🎯 Overall Assessment

Code Quality: ⭐⭐⭐⭐⭐ (Excellent)
Bug Prevention: ⭐⭐⭐⭐⭐ (Excellent)
Test Coverage: ⭐⭐⭐⭐☆ (Very Good - minor gaps)

This is a high-quality defensive programming improvement. The logic is sound, performance is optimized, and the approach follows good software engineering principles. The issues identified are minor and easily addressed.

Recommendation: ✅ Approve with minor fixes for the duplicate assertion and test coverage gap.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens canvas badge rendering to handle zero-size and tiny badge dimensions gracefully.

Changes:

  • Add early return to skip badge rendering when either WidthPx or HeightPx is <= 0
  • Remove redundant Math.Max(1, ...) clamping that's now unnecessary after early return
  • Add ribbon tail dimension clamping to prevent degenerate tails on tiny badges
  • Update documentation to clarify that 0 disables badge drawing
  • Add regression tests for zero-size and tiny ribbon badges

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
CodeGlyphX/Rendering/Png/QrPngRenderer.Internal.A.cs Adds early return for zero-size badges and clamps ribbon tail dimensions to prevent rendering issues with tiny badges
CodeGlyphX/Rendering/Png/QrPngCanvasBadgeOptions.cs Updates XML documentation to clarify that 0 width/height disables badge drawing
CodeGlyphX.Tests/QrPngRendererTests.cs Adds regression tests for zero-size badge skip and tiny ribbon badge tail clamping

}

Assert.True(foundBand, "Expected band pixels outside the QR bounds.");
Assert.True(foundBand, "Expected band pixels outside the QR bounds.");
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate assertion detected. This line is identical to line 1537 and should be removed.

Suggested change
Assert.True(foundBand, "Expected band pixels outside the QR bounds.");

Copilot uses AI. Check for mistakes.
Comment on lines +1542 to +1592
public void Render_With_Zero_Size_Badge_Skips_Drawing() {
var matrix = new BitMatrix(21, 21);
var moduleSize = 6;
var quietZone = 4;
var padding = 20;

var opts = new QrPngRenderOptions {
ModuleSize = moduleSize,
QuietZone = quietZone,
Foreground = Rgba32.Black,
Background = Rgba32.White,
Canvas = new QrPngCanvasOptions {
PaddingPx = padding,
Background = Rgba32.White,
Badge = new QrPngCanvasBadgeOptions {
Shape = QrPngCanvasBadgeShape.Badge,
Position = QrPngCanvasBadgePosition.Top,
WidthPx = 0,
HeightPx = 24,
GapPx = 8,
Color = new Rgba32(200, 20, 60, 220),
},
},
};

var png = QrPngRenderer.Render(matrix, opts);
var (rgba, width, height, stride) = PngTestDecoder.DecodeRgba32(png);

var qrFullPx = (matrix.Width + quietZone * 2) * moduleSize;
var qrX0 = padding;
var qrY0 = padding;
var qrX1 = qrX0 + qrFullPx - 1;
var qrY1 = qrY0 + qrFullPx - 1;

var foundBadge = false;
for (var y = 0; y < height && !foundBadge; y++) {
for (var x = 0; x < width; x++) {
if (x >= qrX0 && x <= qrX1 && y >= qrY0 && y <= qrY1) continue;
var p = y * stride + x * 4;
var r = rgba[p + 0];
var g = rgba[p + 1];
var b = rgba[p + 2];
if (r != 255 || g != 255 || b != 255) {
foundBadge = true;
break;
}
}
}

Assert.False(foundBadge, "Did not expect badge pixels outside the QR bounds.");
}
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test only covers the case where WidthPx = 0, but does not test HeightPx = 0. Since the early return condition checks both dimensions (line 2575 in QrPngRenderer.Internal.A.cs), both cases should be tested to ensure complete coverage of the zero-size badge skip logic.

Copilot uses AI. Check for mistakes.
@PrzemyslawKlys PrzemyslawKlys merged commit 97d65e7 into feature/art-enhancements-20260127 Jan 28, 2026
3 of 6 checks passed
@PrzemyslawKlys PrzemyslawKlys deleted the fix/badge-sizing-20260128 branch January 28, 2026 17:51
@sonarqubecloud
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants