Skip to content

Conversation

TimothyMakkison
Copy link
Contributor

@TimothyMakkison TimothyMakkison commented Oct 2, 2025

Fill bitmap with the light colour and then draw with the black, halving the number of updates.

Would love to benchmark this, but I can't get the benchmark file to use System.Drawing to reference QRCoder.QRCode. I've tried setting the .NET version of the benchmarks to 6.0.0 and referencing System.Drawing.Common but I can't reference QRCoder.QRCode

Edit: I can do this in other places

Related #240

Summary by CodeRabbit

  • Refactor
    • Improved QR code rendering by pre-filling the background and drawing only dark modules, reducing per-pixel work.
    • Faster and more responsive rendering, especially for large or high‑DPI outputs.
    • Visual output for valid QR codes remains unchanged and no public interfaces were modified.

Copy link
Contributor

coderabbitai bot commented Oct 2, 2025

📝 Walkthrough

Walkthrough

Pre-fills the entire bitmap with the lightColor, then draws only dark modules; removes the per-module lightColor branch. Rendering flow changes from per-module dual-branch painting to background-then-dark-only painting. No public APIs were changed.

Changes

Cohort / File(s) Summary
QR code renderer drawing simplification
QRCoder/QRCode.cs
Fill bitmap once with lightColor, remove per-module lightColor draw, and draw only dark modules. Control flow simplified; output for valid modules unchanged.

Sequence Diagram(s)

sequenceDiagram
    participant Renderer as QRCode Renderer
    participant Bitmap as Bitmap Canvas
    participant Modules as QR Modules

    Renderer->>Bitmap: Create bitmap
    note right of Bitmap #f0f4ff: New step — pre-fill background
    Renderer->>Bitmap: Fill entire background with lightColor

    loop For each module
        Renderer->>Modules: Is module dark?
        alt Dark module
            Renderer->>Bitmap: Draw darkColor rectangle at module position
        else Light module
            note right of Bitmap #f8f8f8: No-op — background already present
        end
    end

    Renderer-->>Bitmap: Return rendered image
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Suggested reviewers

  • gfoidl

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title clearly and concisely summarizes the primary change of the pull request, which is to optimise QRCode rendering by pre-filling it with a single colour instead of handling each module separately, making it immediately understandable to reviewers.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between da5c360 and d29ce69.

📒 Files selected for processing (1)
  • QRCoder/QRCode.cs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • QRCoder/QRCode.cs
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Test .NET 6.0 Windows
  • GitHub Check: additional-tests
  • GitHub Check: Test .NET Core 3.1
  • GitHub Check: Test .NET Core 2.1
  • GitHub Check: Test .NET 5.0 Windows

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
QRCoder/QRCode.cs (1)

98-145: Consider applying the same optimization to the icon-enabled overload.

The icon-enabled GetGraphic method at line 111 uses gfx.Clear(lightColor), which pre-fills the background similarly to line 66's new approach. However, lines 118-119 still conditionally draw both dark and light modules.

Since the background is already filled with lightColor at line 111, you could optimize lines 118-119 to only draw dark modules, consistent with the approach taken in the simpler overload (lines 73-76).

Apply this diff to align the icon-enabled method with the optimization:

-                    var moduleBrush = QrCodeData.ModuleMatrix[(y + pixelsPerModule) / pixelsPerModule - 1][(x + pixelsPerModule) / pixelsPerModule - 1] ? darkBrush : lightBrush;
-                    gfx.FillRectangle(moduleBrush, new Rectangle(x - offset, y - offset, pixelsPerModule, pixelsPerModule));
+                    var module = QrCodeData.ModuleMatrix[(y + pixelsPerModule) / pixelsPerModule - 1][(x + pixelsPerModule) / pixelsPerModule - 1];
+                    if (module)
+                    {
+                        gfx.FillRectangle(darkBrush, new Rectangle(x - offset, y - offset, pixelsPerModule, pixelsPerModule));
+                    }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 804279a and 378540e.

📒 Files selected for processing (1)
  • QRCoder/QRCode.cs (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
QRCoder/QRCode.cs (1)
QRCoder/QRCodeGenerator/Rectangle.cs (1)
  • Rectangle (37-43)
🪛 GitHub Actions: Format (Pull Request)
QRCoder/QRCode.cs

[error] 66-66: WHITESPACE: Fix whitespace formatting. Insert '\s'.


[error] 66-66: IDE0055: Fix formatting


[warning] 1-1: Warnings were encountered while loading the workspace. Set the verbosity option to the 'diagnostic' level to log warnings.

🪛 GitHub Check: format
QRCoder/QRCode.cs

[failure] 66-66:
Fix formatting


[failure] 66-66:
Fix formatting


[failure] 66-66:
Fix formatting


[failure] 66-66:
Fix formatting


[failure] 66-66:
Fix formatting


[failure] 66-66:
Fix formatting

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build

@Shane32
Copy link
Owner

Shane32 commented Oct 2, 2025

Try changing the benchmark project's target framework to net8.0-windows

Ref:

@Shane32 Shane32 added the performance Performance related enhancements or benchmarks label Oct 2, 2025
@Shane32 Shane32 added this to the 1.7.0 milestone Oct 2, 2025
@Shane32
Copy link
Owner

Shane32 commented Oct 2, 2025

FYI, it may be possible to create a single path for the dark modules, issuing a single Fill call, further improving performance. Just guessing, and not sure what the performance impact would be.

https://chatgpt.com/s/t_68ded921c014819186a24654c04b167a

@TimothyMakkison
Copy link
Contributor Author

TimothyMakkison commented Oct 2, 2025

net8.0-windows

Thanks 🙏

FYI, it may be possible to create a single path for the dark modules, issuing a single Fill call, further improving performance. Just guessing, and not sure what the performance impact would be.

https://chatgpt.com/s/t_68ded921c014819186a24654c04b167a

Yeah, I wanted to use benchmarks to test this. I'd also like to know if it's worth filling the bitmap with the light or dark colour or how much faster gfx.Clear is vs painting a large rectangle.

IIRC there was an issue with mutating a bitmap a lot in C#, I can't remember if it was related to bmp.LockBits and unlocking. I did wonder if it would be better to write to a scale bitmap and then scale it up to the correct size Bitmap resized = new Bitmap(original,new Size(original.Width * 4,original.Height*4));

I'll experiment more in the morning

After

Method Mean Error StdDev Allocated
DrawingBitmapByteQRCodeSmall 855.7 us 16.95 us 32.25 us 313 B
DrawingBitmapByteQRCodeMedium 2,781.1 us 55.02 us 127.52 us 316 B
DrawingBitmapByteQRCodeBig 46,853.3 us 968.01 us 2,649.90 us 401 B
DrawingBitmapByteQRCodeHuge 325,011.8 us 6,481.17 us 12,793.20 us 848 B

Before

Method Mean Error StdDev Allocated
DrawingBitmapByteQRCodeSmall 2.411 ms 0.0481 ms 0.0843 ms 316 B
DrawingBitmapByteQRCodeMedium 6.795 ms 0.1246 ms 0.2545 ms 320 B
DrawingBitmapByteQRCodeBig 90.470 ms 1.7894 ms 4.0023 ms 491 B
DrawingBitmapByteQRCodeHuge 521.257 ms 10.3711 ms 18.9641 ms 1384 B

I can see why #240 was created, half a second for a large QR code is crazy 0_0, not sure why the memory usage is so low for a huge bitmap

@Shane32 Shane32 marked this pull request as ready for review October 8, 2025 17:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

performance Performance related enhancements or benchmarks

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants