Skip to content

fix(ArtQRCode): scale and dispose background image#88

Merged
afonsoft merged 3 commits into
feature/devin-20260712-sonar-qualityfrom
feature/devin-20260712-sonar-resize-fix
Jul 12, 2026
Merged

fix(ArtQRCode): scale and dispose background image#88
afonsoft merged 3 commits into
feature/devin-20260712-sonar-qualityfrom
feature/devin-20260712-sonar-resize-fix

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

All Submissions:

  • Have you followed the guidelines in our Contributing document?
  • Have you checked to ensure there aren't other open Pull Requests for the same update/change?

Changes to Core Features:

  • Have you added an explanation of what your changes do and why you'd like us to include them?
  • Have you successfully ran tests with your changes locally?

Summary

Fixes the pre-existing ArtQRCode background image bug reported by Devin Review:

  • Resize now actually scales the source image using SKBitmap.Resize instead of returning an empty scaledImage.
  • Resize disposes the temporary scaledImage and the SKCanvas/SKPaint resources.
  • RenderGraphicCore now disposes the SKBitmap returned by Resize after drawing it.
  • Resize is now static and guards against invalid dimensions.
  • ArtQRCodeRendererTests.can_create_standard_qrcode_graphic_with_background expected hash updated to reflect the rendered background image.

Build:

  • dotnet build QRCoder.Core.sln --configuration Release passes.
  • dotnet test QRCoder.Core.Tests/ -f net10.0 --configuration Release passes (502 tests).
  • dotnet test QRCoder.Core.Tests/ -f net8.0 --configuration Release passes (502 tests).
  • net48 tests are skipped on Linux due to missing mono.

Link to Devin session: https://app.devin.ai/sessions/20c92a1bad724f63b8be89a9e195e664
Requested by: @afonsoft


Open in Devin Review

devin-ai-integration Bot and others added 2 commits July 12, 2026 18:05
Resize was creating an empty scaledImage and returning it without
drawing the source image, so the background image was invisible.
Use image.Resize() to scale and dispose the temporary bitmap,
and dispose the resized result in RenderGraphicCore.

Co-Authored-By: Afonso Dutra Nogueira Filho <afonsoft@gmail.com>
Co-Authored-By: Afonso Dutra Nogueira Filho <afonsoft@gmail.com>
@afonsoft afonsoft self-assigned this Jul 12, 2026
@afonsoft
afonsoft self-requested a review July 12, 2026 18:06
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Devin Review found 4 potential issues.

Open in Devin Review

Comment thread QRCoder.Core/Renderers/ArtQRCode.cs Outdated
return null;

float scale = Math.Min((float)newSize / image.Width, (float)newSize / image.Height);
var scaledWidth = (int)(image.Width * scale);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

🟡 Resized image dimensions can round down to zero for extreme aspect ratios, risking an exception and bitmap leak

The computed scaled dimensions can truncate to zero (scaledWidth or scaledHeight at QRCoder.Core/Renderers/ArtQRCode.cs:340-341) when the source image has an extreme aspect ratio, so the resize call may throw or silently fail and leak the already-allocated output bitmap.

Impact: A background image with a very extreme aspect ratio could cause an unhandled exception or a native bitmap resource leak.

Truncation mechanism and leak path

When image.Width is much larger than image.Height (or vice versa) relative to newSize, the scale factor becomes very small. For example, with image.Width=1000, image.Height=1, newSize=1: scale = 0.001, scaledHeight = (int)(1 * 0.001) = 0. Then image.Resize(new SKSizeI(1, 0), ...) at QRCoder.Core/Renderers/ArtQRCode.cs:349 will either return null (handled) or throw an ArgumentException for invalid dimensions. If it throws, the bm bitmap allocated at line 345 is not protected by try/finally, so it leaks.

The guard at QRCoder.Core/Renderers/ArtQRCode.cs:336 checks newSize <= 0 but does not check the computed scaledWidth and scaledHeight after truncation.

(Refers to lines 340-341)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread QRCoder.Core/Renderers/ArtQRCode.cs Outdated
{
using (var brush = new SKPaint { Color = SKColors.Transparent, })
graphics.Clear(SKColors.Transparent);
using (var scaledImage = image.Resize(new SKSizeI(scaledWidth, scaledHeight), new SKSamplingOptions(SKFilterMode.Linear)))

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

📝 Info: Old Resize method never actually resized the image — this fix is a significant behavioral change

The old Resize method at QRCoder.Core/Renderers/ArtQRCode.cs:334 created var scaledImage = new SKBitmap(scaledWidth, scaledHeight) — an empty, uninitialized bitmap — and drew that empty bitmap onto the output canvas. The source image was never actually scaled or copied. This meant background images were silently ignored in all prior versions.

The new code correctly calls image.Resize(new SKSizeI(scaledWidth, scaledHeight), ...) which produces a properly scaled copy. This is confirmed by the test: the old hash for can_create_standard_qrcode_graphic_with_background was b9ecef2ee7e769d17f5e00914c7452bb — identical to the no-background test can_create_standard_qrcode_graphic — proving the background was never rendered before.

This is a real behavioral change for any consumer relying on ArtQRCode with a background image. Previously they got no background; now they'll get one.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

{
if (resizedImage != null)
graphics.DrawBitmap(resizedImage, 0, 0);
graphics.Flush();

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

📝 Info: Redundant graphics.Flush() calls inside background image rendering blocks

The graphics.Flush() calls at lines 205 and 215 are called immediately after drawing the background image, but before the module dots are rendered. There is already a graphics.Flush() at line 249 after all rendering (modules + finder patterns) is complete. The intermediate flushes are unnecessary — SKCanvas.Flush() forces pending draw operations to be submitted, but SkiaSharp doesn't require intermediate flushes between draw calls on the same canvas. These add minor overhead without benefit.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +201 to +216
using (var resizedImage = Resize(backgroundImage, size))
{
if (resizedImage != null)
graphics.DrawBitmap(resizedImage, 0, 0);
graphics.Flush();
}
}
else if (backgroundImageStyle == BackgroundImageStyle.DataAreaOnly)
{
var bgOffset = 4 - offset;
graphics.DrawBitmap(Resize(backgroundImage, size - (2 * bgOffset * pixelsPerModule)), 0 + (bgOffset * pixelsPerModule), (bgOffset * pixelsPerModule));
using (var resizedImage = Resize(backgroundImage, size - (2 * bgOffset * pixelsPerModule)))
{
if (resizedImage != null)
graphics.DrawBitmap(resizedImage, 0 + (bgOffset * pixelsPerModule), (bgOffset * pixelsPerModule));
graphics.Flush();
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

📝 Info: Old code also leaked the scaledImage bitmap — fixed by this PR

In the old Resize method, var scaledImage = new SKBitmap(scaledWidth, scaledHeight) was allocated but never disposed. The new code replaces this with image.Resize(...) wrapped in a using block at line 349, properly disposing the intermediate bitmap. Similarly, the callers in RenderGraphicCore (lines 201, 211) now wrap the Resize() return value in using blocks, fixing a resource leak where the resized bitmap was previously passed directly to DrawBitmap and never disposed.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

…t ratios

Ensure scaled dimensions are at least 1 pixel before calling
image.Resize(). Create the output bitmap only after a successful
resize to prevent leaking it if the resize operation fails.

Co-Authored-By: Afonso Dutra Nogueira Filho <afonsoft@gmail.com>
@sonarqubecloud

Copy link
Copy Markdown

@afonsoft
afonsoft merged commit 04829e5 into feature/devin-20260712-sonar-quality Jul 12, 2026
6 checks passed
@afonsoft
afonsoft deleted the feature/devin-20260712-sonar-resize-fix branch July 12, 2026 18:12
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.

1 participant