[Android][Windows] Fix GraphicsView passing fractional dirtyRect dimensions to IDrawable.Draw - #34564
Conversation
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 34564Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 34564" |
|
Hey there @@SyedAbdulAzeemSF4852! Thank you so much for your PR! Someone from the team will get assigned to your PR shortly and we'll get it reviewed. |
There was a problem hiding this comment.
Pull request overview
Fixes GraphicsView platform implementations so IDrawable.Draw(ICanvas, RectF dirtyRect) receives integral logical dimensions (no fractional dp/DIP sizes) on Android and Windows, addressing rendering artifacts described in #33110.
Changes:
- Windows: round logical dirty-rect size and apply an adjusted scale so the rounded logical size maps exactly to the allocated physical pixels.
- Android: precompute rounded logical dp dimensions on size changes and use adjusted scale factors when drawing.
- Adds a new HostApp issue page + Appium UITest to validate integer dirty-rect dimensions.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| src/Graphics/src/Graphics/Platforms/Windows/PlatformGraphicsView.cs | Rounds logical size and applies adjusted scaling before calling IDrawable.Draw. |
| src/Graphics/src/Graphics/Platforms/Android/PlatformGraphicsView.cs | Computes rounded logical dp size + adjusted scale factors, then uses them for dirtyRect and canvas scaling. |
| src/Controls/tests/TestCases.HostApp/Issues/Issue33110.cs | Adds a repro page and drawable that records whether dirtyRect dimensions are integral. |
| src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue33110.cs | Adds a UI test that taps “Check” and asserts the result is “Pass”. |
kubaflo
left a comment
There was a problem hiding this comment.
Could you please check the AI's suggestions?
@kubaflo, I reviewed the AI suggestion, and it suggested going with Attempt 2. However Attempt 2 is essentially the same as the round-only workaround shared at comment — rounding the dimensions without adjusting the scale — and the original issue reporter already responded at comment with a screenshot showing that this exact approach produces visible gaps when views are stacked, which is precisely why the PR uses an adjusted scale to ensure the drawable fills the view pixel-for-pixel. |
This comment has been minimized.
This comment has been minimized.
|
/azp run maui-pr-uitests |
MauiBot
left a comment
There was a problem hiding this comment.
Expert Review — 2 findings
See inline comments for details.
| GraphicsView graphicsView = new GraphicsView | ||
| { | ||
| Drawable = drawable, | ||
| WidthRequest = 100, |
There was a problem hiding this comment.
[major] Regression Prevention — The regression test fixes the GraphicsView at exactly 100x50, so on Windows the old implementation would pass because ActualWidth/ActualHeight are already integer DIPs. That means this test does not reproduce the reported Windows dirtyRect failure and would not catch a regression there. Use a size/layout that deterministically produces fractional platform dimensions (for example a fractional request or star layout with an odd available size) before asserting the dirtyRect is rounded.
| { | ||
| adjustedScaleX = actualWidth / logicalWidth; | ||
| adjustedScaleY = actualHeight / logicalHeight; | ||
| _dirty.Width = logicalWidth; |
There was a problem hiding this comment.
[minor] Logic and Correctness — This changes _dirty.Width to the rounded logical width, but the RTL flip below still translates by _dirty.Width after _canvas.Scale(adjustedScaleX, adjustedScaleY) has been applied. On fractional WinUI sizes (for example ActualWidth = 100.3, logicalWidth = 100, adjustedScaleX = 1.003), the flip anchors at 100 instead of the physical right edge at 100.3, shifting RTL rendering by up to half a pixel compared with the previous code. Keep the integer dirtyRect contract here, but use actualWidth for the RTL translation.
MauiBot
left a comment
There was a problem hiding this comment.
Expert Review — 3 findings
See inline comments for details.
| const float scaleEpsilon = 0.0001f; | ||
| if (MathF.Abs(adjustedScaleX - 1f) > scaleEpsilon || MathF.Abs(adjustedScaleY - 1f) > scaleEpsilon) | ||
| { | ||
| _canvas.Scale(adjustedScaleX, adjustedScaleY); |
There was a problem hiding this comment.
[major] Windows Platform Specifics — adjusted scale is composed before the RTL mirror, so fractional-size RTL views are shifted/clipped.
This scale is installed on PlatformCanvas before the existing FlowDirection.RightToLeft transform. PlatformCanvasState.AppendScale() pre-multiplies the scale, while AppendConcatenateTransform() post-multiplies the RTL matrix, so the final matrix is Scale(adjustedScaleX) * (Scale(-1) * Translate(_dirty.Width)). For a fractional allocation such as ActualWidth = 100.5, this PR sets _dirty.Width = 100 and adjustedScaleX = 1.005; an RTL point at logical x=0 maps to x=100 instead of the physical right edge 100.5, and logical x=100 maps to x=-0.5. That reintroduces the same edge gap/clipping this change is trying to remove, but only in RTL on Windows.
Please compose the RTL mirror in the same coordinate space as the adjusted scale (for example translate by the physical/actual width, or apply the mirror before the scale so the translation is scaled to the actual width), and add an RTL fractional-width Windows regression case.
| { | ||
| App.WaitForElement("CheckButton"); | ||
| App.Tap("CheckButton"); | ||
| Assert.That(App.WaitForElement("ResultLabel").GetText(), Is.EqualTo("Pass")); |
There was a problem hiding this comment.
[major] Regression Prevention — Windows fix code path never exercised by this test.
WidthRequest = 100 on Windows produces ActualWidth = 100.0 DIPs exactly (XAML layout honours the fixed request). Because MathF.Round(100.0f) = 100 and adjustedScaleX = 100.0f / 100 = 1.0f, the MathF.Abs(adjustedScaleX - 1f) > 0.0001f guard evaluates to false and _canvas.Scale() is never called. The Windows scale/rounding path added by this PR is dead code for this particular test configuration.
To make this test exercise the Windows fix, the GraphicsView would need a fractional-DIP allocation — for example place it in a Grid with a *-sized column on a 150% or 125% DPI machine so that ActualWidth becomes something like 99.5 or 100.33. Without that, the test passes identically before and after the Windows change and cannot catch a regression.
|
|
||
| class Issue33110Drawable : IDrawable | ||
| { | ||
| public bool HasIntegerDimensions { get; set; } |
There was a problem hiding this comment.
[major] Regression Prevention — Android test is a no-op on integer-density CI emulators.
HasIntegerDimensions defaults to false. On Android emulators running at an integer display density (1.0×, 1.5×, 2.0×, 3.0×), the old code already produced integer values: e.g. at density 2.0, WidthRequest=100 → 200 physical px → 200 / 2.0 = 100.0 (exact integer). HasIntegerDimensions would therefore be true with the old code as well, so the test passes with and without this fix.
The bug only manifests at non-integer densities such as 2.625× (Pixel 6/7 class) or 2.75×. If CI runs on a density-2.0 emulator (the AOSP default), this regression test will never catch a revert. Consider adding a comment documenting which device densities reproduce the issue, or asserting the fix more directly (e.g. record the pre-fix fractional value and verify the post-fix drawable received an integer).
…plementing AzDO (#36080) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ## Problem The `/review rerun` scanner (`rerun-review-scanner`) dispatched **zero** AzDO reviews after #35955, and the few it should dispatch would target the wrong branch. Two independent bugs: ### Bug 1 — every dispatch aborted with a spurious 404 In the **gh-aw safe-output job**, the `gh` CLI returns `HTTP 404` for `repos/dotnet/maui/pulls/N` — even with `pull-requests: write` on a public repo. #35955 misread these as transient and "hardened" the not-found guard, so it now faithfully *confirms* the bogus 404 and skips every PR. Evidence it is not a permission/token problem: the `pre_activation` job (only `Metadata: read`) reads the same PRs fine, and the built-in `safe_outputs` job (octokit) succeeds in the same run. ### Bug 2 — custom review branches silently downgraded to `main` The candidate builder decided whose `/review -b <branch> -p <platform>` to trust using the comment's `author_association`. Under the Actions `GITHUB_TOKEN`, a maintainer whose org membership is private reads as `CONTRIBUTOR` (not `MEMBER`), so their command was dropped and the rerun fell back to the `main` pipeline. A live scan confirmed it dispatched four net11 (`feature/enhanced-reviewer`) PRs on `main`. ## Fix — do exactly what a maintainer `/review` does **Bug 1:** instead of re-implementing PR validation + OIDC + the AzDO trigger inside the safe job, the scanner now **dispatches the same `review-trigger.yml` workflow `/review` runs**, via `workflow_dispatch`. That workflow owns PR validation, the `s/agent-review-in-progress` lock, platform inference, OIDC, and the AzDO trigger. (`workflow_dispatch` via `GITHUB_TOKEN` always creates a run — it is exempt from Actions recursion-prevention.) - `Invoke-RerunReviewTrigger.ps1` is now pure: validate batched `decisions` against `candidates.json`, emit `actions.json`. No `gh`/AzDO/OIDC/lock/rate-limit I/O. - A `github-script` (octokit) step performs all GitHub writes — octokit works in the safe-job context where the `gh` CLI does not. `trigger` → `createWorkflowDispatch(review-trigger.yml,{pr_number,platform,pipeline_ref})` + 👍; `skip` → 👎 + remove the queue label. - Permissions: `+actions:write`, `−id-token:write`; dropped the `AZDO_TRIGGER_*` secrets from the job. **Bug 2:** authorize `/review` options by a **live collaborator-permission lookup** (`collaborators/<user>/permission` → write/maintain/admin) — the exact call `review-trigger.yml`'s auth step makes. It only needs `metadata: read` (every token has it) and reflects current access. **No new secret or permission.** - `Resolve-RerunEligibility.ps1`: add `Test-ReviewOptionLoginTrusted` (cached per login); `Get-LatestReviewCommandOptions` computes trust through it. Removed the `author_association` gate. - `Query-RerunReadyPRs.ps1`: drop the `author_association` helpers; pass `-Owner/-Repo`. ## Validation - **69 Pester tests pass** (36 dispatch + 33 resolver), incl. a regression test that an `author_association=NONE` command is still honored when the login has write access. - **Live, real (non-dry) scan**: the safe job validated all decisions with **no 404s** and octokit performed real `createWorkflowDispatch`, producing two `review-trigger.yml` runs that triggered real AzDO `maui-copilot` builds (HTTP 200, Run IDs 14459427 & 14459428). - **Real-data check for Bug 2**: PR #34564's history now resolves to `pipelineRef=feature/enhanced-reviewer` (author `kubaflo`, `author_association=CONTRIBUTOR`) instead of `main`, using the live permission lookup. ## Files - `.github/scripts/Invoke-RerunReviewTrigger.ps1`, `.github/scripts/Invoke-RerunReviewTrigger.Tests.ps1` - `.github/scripts/Resolve-RerunEligibility.ps1`, `.github/scripts/Resolve-RerunEligibility.Tests.ps1` - `.github/scripts/Query-RerunReadyPRs.ps1` - `.github/workflows/rerun-review-scanner.md` + recompiled `.lock.yml` - `.github/docs/agent-labels.md` --- 🔍 _This PR was created by an AI agent (GitHub Copilot CLI) on behalf of @kubaflo._ --------- Co-authored-by: kubaflo <kubaflo@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
MauiBot
left a comment
There was a problem hiding this comment.
Expert Review — 2 findings
See inline comments for details.
| const float scaleEpsilon = 0.0001f; | ||
| if (MathF.Abs(adjustedScaleX - 1f) > scaleEpsilon || MathF.Abs(adjustedScaleY - 1f) > scaleEpsilon) | ||
| { | ||
| _canvas.Scale(adjustedScaleX, adjustedScaleY); |
There was a problem hiding this comment.
[major] GraphicsView RTL rendering — The adjusted scale is applied before the existing RTL mirror transform, but the mirror still translates by _dirty.Width (the rounded logical width). On a fractional Windows size such as ActualWidth=100.5, the current matrix maps the drawable to roughly [-0.5, 100] instead of [0, 100.5], so RTL content is shifted/clipped and no longer covers the actual view bounds. Use a mirror translation in the post-scale coordinate space (for example the actual width, or apply the mirror before the adjusted scale) so RTL gets the same exact edge-to-edge coverage as LTR.
| // Use adjusted scale factors that map rounded logical dp dimensions | ||
| // back to exact pixel dimensions, avoiding both fractional dp values | ||
| // and sub-pixel gaps at view edges. | ||
| _scalingCanvas.Scale(_adjustedScaleX, _adjustedScaleY); |
There was a problem hiding this comment.
[major] GraphicsView touch coordinate mapping — Drawing now uses _adjustedScaleX/_adjustedScaleY with a rounded dirtyRect, but PlatformTouchGraphicsView still converts touch/hover coordinates and _bounds with width / _scale. At densities like 2.625 (263px -> dirtyRect.Width=100, adjusted scale 2.63), touches near the right edge are reported as 100.19dp and considered inside bounds even though the drawable coordinate space ends at 100dp. Expose/reuse the adjusted logical dimensions/scale for touch and hover mapping so interaction coordinates match what IDrawable.Draw receives.
MauiBot
left a comment
There was a problem hiding this comment.
AI Review Summary
@SyedAbdulAzeemSF4852 — new AI review results are available based on this last commit:
9a9d681. To request a fresh review after new comments or commits, comment/review rerun.
🗂️ Review Sessions — click to expand
🚦 Gate — Test Before & After Fix
Gate Result: ✅ PASSED
Platform: ANDROID · Base: main · Merge base: be0b7019
| Test | Without Fix (expect FAIL) | With Fix (expect PASS) |
|---|---|---|
🖥️ Issue33110 Issue33110 |
✅ FAIL — 877s | ✅ PASS — 1293s |
🔴 Without fix — 🖥️ Issue33110: FAIL ✅ · 877s
Determining projects to restore...
Restored /home/vsts/work/1/s/src/Graphics/src/Graphics/Graphics.csproj (in 794 ms).
Restored /home/vsts/work/1/s/src/Essentials/src/Essentials.csproj (in 4.06 sec).
Restored /home/vsts/work/1/s/src/Core/src/Core.csproj (in 5.18 sec).
Restored /home/vsts/work/1/s/src/Core/maps/src/Maps.csproj (in 2.64 sec).
Restored /home/vsts/work/1/s/src/Controls/src/Xaml/Controls.Xaml.csproj (in 48 ms).
Restored /home/vsts/work/1/s/src/Controls/src/Core/Controls.Core.csproj (in 29 ms).
Restored /home/vsts/work/1/s/src/Controls/Maps/src/Controls.Maps.csproj (in 26 ms).
Restored /home/vsts/work/1/s/src/Controls/Foldable/src/Controls.Foldable.csproj (in 54 ms).
Restored /home/vsts/work/1/s/src/BlazorWebView/src/Maui/Microsoft.AspNetCore.Components.WebView.Maui.csproj (in 624 ms).
Restored /home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj (in 1.55 sec).
1 of 11 projects are up-to-date for restore.
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14485964
Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0-android36.0/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14485964
Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0-android36.0/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14485964
Core -> /home/vsts/work/1/s/artifacts/bin/Core/Debug/net10.0-android36.0/Microsoft.Maui.dll
Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14485964
Maps -> /home/vsts/work/1/s/artifacts/bin/Maps/Debug/net10.0-android36.0/Microsoft.Maui.Maps.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14485964
Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.Core/Debug/net10.0-android36.0/Microsoft.Maui.Controls.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14485964
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14485964
Controls.Foldable -> /home/vsts/work/1/s/artifacts/bin/Controls.Foldable/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Foldable.dll
Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.Xaml/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Xaml.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14485964
Microsoft.AspNetCore.Components.WebView.Maui -> /home/vsts/work/1/s/artifacts/bin/Microsoft.AspNetCore.Components.WebView.Maui/Debug/net10.0-android36.0/Microsoft.AspNetCore.Components.WebView.Maui.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14485964
Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.Maps/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Maps.dll
Controls.TestCases.HostApp -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Controls.TestCases.HostApp.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14485964
Graphics -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14485964
Essentials -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14485964
Core -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14485964
Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Maps.dll
Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14485964
Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14485964
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14485964
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14485964
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14485964
Controls.Foldable -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Foldable.dll
Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Xaml.dll
Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Maps.dll
Microsoft.AspNetCore.Components.WebView.Maui -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.AspNetCore.Components.WebView.Maui.dll
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:09:55.26
Broadcasting: Intent { act=android.intent.action.CLOSE_SYSTEM_DIALOGS flg=0x400000 }
Broadcast completed: result=0
Broadcasting: Intent { act=android.intent.action.CLOSE_SYSTEM_DIALOGS flg=0x400000 }
Broadcast completed: result=0
Broadcasting: Intent { act=android.intent.action.CLOSE_SYSTEM_DIALOGS flg=0x400000 }
Broadcast completed: result=0
Starting: Intent { act=android.settings.SETTINGS }
Broadcasting: Intent { act=android.intent.action.CLOSE_SYSTEM_DIALOGS flg=0x400000 }
Broadcast completed: result=0
Determining projects to restore...
Restored /home/vsts/work/1/s/src/Controls/tests/CustomAttributes/Controls.CustomAttributes.csproj (in 1.67 sec).
Restored /home/vsts/work/1/s/src/TestUtils/src/VisualTestUtils/VisualTestUtils.csproj (in 5 ms).
Restored /home/vsts/work/1/s/src/TestUtils/src/VisualTestUtils.MagickNet/VisualTestUtils.MagickNet.csproj (in 7.71 sec).
Restored /home/vsts/work/1/s/src/Controls/tests/TestCases.Android.Tests/Controls.TestCases.Android.Tests.csproj (in 9.64 sec).
Restored /home/vsts/work/1/s/src/TestUtils/src/UITest.Core/UITest.Core.csproj (in 2 ms).
Restored /home/vsts/work/1/s/src/TestUtils/src/UITest.Appium/UITest.Appium.csproj (in 3 ms).
Restored /home/vsts/work/1/s/src/TestUtils/src/UITest.NUnit/UITest.NUnit.csproj (in 605 ms).
Restored /home/vsts/work/1/s/src/TestUtils/src/UITest.Analyzers/UITest.Analyzers.csproj (in 5.45 sec).
5 of 13 projects are up-to-date for restore.
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14485964
Controls.CustomAttributes -> /home/vsts/work/1/s/artifacts/bin/Controls.CustomAttributes/Debug/net10.0/Controls.CustomAttributes.dll
Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14485964
Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14485964
Core -> /home/vsts/work/1/s/artifacts/bin/Core/Debug/net10.0/Microsoft.Maui.dll
Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14485964
Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.Core/Debug/net10.0/Microsoft.Maui.Controls.dll
UITest.Core -> /home/vsts/work/1/s/artifacts/bin/UITest.Core/Debug/net10.0/UITest.Core.dll
VisualTestUtils -> /home/vsts/work/1/s/artifacts/bin/VisualTestUtils/Debug/netstandard2.0/VisualTestUtils.dll
UITest.NUnit -> /home/vsts/work/1/s/artifacts/bin/UITest.NUnit/Debug/net10.0/UITest.NUnit.dll
VisualTestUtils.MagickNet -> /home/vsts/work/1/s/artifacts/bin/VisualTestUtils.MagickNet/Debug/netstandard2.0/VisualTestUtils.MagickNet.dll
UITest.Appium -> /home/vsts/work/1/s/artifacts/bin/UITest.Appium/Debug/net10.0/UITest.Appium.dll
UITest.Analyzers -> /home/vsts/work/1/s/artifacts/bin/UITest.Analyzers/Debug/netstandard2.0/UITest.Analyzers.dll
Controls.TestCases.Android.Tests -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll
Test run for /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll (.NETCoreApp,Version=v10.0)
VSTest version 18.0.1 (x64)
Starting test execution, please wait...
A total of 1 test files matched the specified pattern.
NUnit Adapter 4.5.0.0: Test execution started
Running selected tests in /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll
NUnit3TestExecutor discovered 1 of 1 NUnit test cases using Current Discovery mode, Non-Explicit run
>>>>> 06/25/2026 14:44:49 FixtureSetup for Issue33110(Android)
>>>>> 06/25/2026 14:44:53 GraphicsViewDirtyRectShouldHaveIntegerDimensions Start
>>>>> 06/25/2026 14:44:57 GraphicsViewDirtyRectShouldHaveIntegerDimensions Stop
>>>>> 06/25/2026 14:44:57 Log types: logcat, bugreport, server
Failed GraphicsViewDirtyRectShouldHaveIntegerDimensions [5 s]
Error Message:
Assert.That(App.WaitForElement("ResultLabel").GetText(), Is.EqualTo("Pass"))
String lengths are both 4. Strings differ at index 0.
Expected: "Pass"
But was: "Fail"
-----------^
Stack Trace:
at Microsoft.Maui.TestCases.Tests.Issues.Issue33110.GraphicsViewDirtyRectShouldHaveIntegerDimensions() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue33110.cs:line 21
1) at Microsoft.Maui.TestCases.Tests.Issues.Issue33110.GraphicsViewDirtyRectShouldHaveIntegerDimensions() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue33110.cs:line 21
NUnit Adapter 4.5.0.0: Test execution complete
[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.8.2+699d445a1a (64-bit .NET 10.0.0)
[xUnit.net 00:00:00.16] Discovering: Controls.TestCases.Android.Tests
[xUnit.net 00:00:00.70] Discovered: Controls.TestCases.Android.Tests
Results File: /home/vsts/work/1/s/CustomAgentLogsTmp/UITests/TestResults/Issue33110.trx
Total tests: 1
Failed: 1
Test Run Failed.
Total time: 1.3334 Minutes
>>> TRX_RESULT_FILE: /home/vsts/work/1/s/CustomAgentLogsTmp/UITests/TestResults/Issue33110.trx
🟢 With fix — 🖥️ Issue33110: PASS ✅ · 1293s
(truncated to last 15,000 chars)
ests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: --- End of stack trace from previous location --- [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: at AndroidDeviceExtensions.PushAndInstallPackageAsync(AndroidDevice device, PushAndInstallCommand command, CancellationToken token) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: at AndroidDeviceExtensions.PushAndInstallPackageAsync(AndroidDevice device, PushAndInstallCommand command, CancellationToken token) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: at Xamarin.Android.Tasks.FastDeploy.InstallPackage(Boolean installed) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: at Xamarin.Android.Tasks.FastDeploy.InstallPackage(Boolean installed) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: at Xamarin.Android.Tasks.FastDeploy.RunInstall() [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
Build FAILED.
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: Mono.AndroidTools.InstallFailedException: Unexpected install output: cmd: Can't find service: package [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: at Mono.AndroidTools.Internal.AdbOutputParsing.CheckInstallSuccess(String output, String packageName) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: at Mono.AndroidTools.AndroidDevice.<>c__DisplayClass105_0.<InstallPackage>b__0(Task`1 t) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: --- End of stack trace from previous location --- [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: --- End of stack trace from previous location --- [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: at AndroidDeviceExtensions.PushAndInstallPackageAsync(AndroidDevice device, PushAndInstallCommand command, CancellationToken token) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: at AndroidDeviceExtensions.PushAndInstallPackageAsync(AndroidDevice device, PushAndInstallCommand command, CancellationToken token) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: at Xamarin.Android.Tasks.FastDeploy.InstallPackage(Boolean installed) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: at Xamarin.Android.Tasks.FastDeploy.InstallPackage(Boolean installed) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: at Xamarin.Android.Tasks.FastDeploy.RunInstall() [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
0 Warning(s)
1 Error(s)
Time Elapsed 00:10:12.04
* daemon not running; starting now at tcp:5037
* daemon started successfully
Determining projects to restore...
All projects are up-to-date for restore.
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14485964
Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0-android36.0/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14485964
Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0-android36.0/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14485964
Core -> /home/vsts/work/1/s/artifacts/bin/Core/Debug/net10.0-android36.0/Microsoft.Maui.dll
Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14485964
Maps -> /home/vsts/work/1/s/artifacts/bin/Maps/Debug/net10.0-android36.0/Microsoft.Maui.Maps.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14485964
Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.Core/Debug/net10.0-android36.0/Microsoft.Maui.Controls.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14485964
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14485964
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14485964
Controls.Foldable -> /home/vsts/work/1/s/artifacts/bin/Controls.Foldable/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Foldable.dll
Microsoft.AspNetCore.Components.WebView.Maui -> /home/vsts/work/1/s/artifacts/bin/Microsoft.AspNetCore.Components.WebView.Maui/Debug/net10.0-android36.0/Microsoft.AspNetCore.Components.WebView.Maui.dll
Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.Xaml/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Xaml.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14485964
Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.Maps/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Maps.dll
Controls.TestCases.HostApp -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Controls.TestCases.HostApp.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14485964
Graphics -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14485964
Essentials -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14485964
Core -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.dll
Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14485964
Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14485964
Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Maps.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14485964
Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Xaml.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14485964
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14485964
Microsoft.AspNetCore.Components.WebView.Maui -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.AspNetCore.Components.WebView.Maui.dll
Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Maps.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14485964
Controls.Foldable -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Foldable.dll
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:08:54.58
Broadcasting: Intent { act=android.intent.action.CLOSE_SYSTEM_DIALOGS flg=0x400000 }
Broadcast completed: result=0
Broadcasting: Intent { act=android.intent.action.CLOSE_SYSTEM_DIALOGS flg=0x400000 }
Broadcast completed: result=0
Broadcasting: Intent { act=android.intent.action.CLOSE_SYSTEM_DIALOGS flg=0x400000 }
Broadcast completed: result=0
Starting: Intent { act=android.settings.SETTINGS }
Broadcasting: Intent { act=android.intent.action.CLOSE_SYSTEM_DIALOGS flg=0x400000 }
Broadcast completed: result=0
Determining projects to restore...
All projects are up-to-date for restore.
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14485964
Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0/Microsoft.Maui.Graphics.dll
Controls.CustomAttributes -> /home/vsts/work/1/s/artifacts/bin/Controls.CustomAttributes/Debug/net10.0/Controls.CustomAttributes.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14485964
Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14485964
Core -> /home/vsts/work/1/s/artifacts/bin/Core/Debug/net10.0/Microsoft.Maui.dll
Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14485964
Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.Core/Debug/net10.0/Microsoft.Maui.Controls.dll
VisualTestUtils -> /home/vsts/work/1/s/artifacts/bin/VisualTestUtils/Debug/netstandard2.0/VisualTestUtils.dll
VisualTestUtils.MagickNet -> /home/vsts/work/1/s/artifacts/bin/VisualTestUtils.MagickNet/Debug/netstandard2.0/VisualTestUtils.MagickNet.dll
UITest.Core -> /home/vsts/work/1/s/artifacts/bin/UITest.Core/Debug/net10.0/UITest.Core.dll
UITest.Appium -> /home/vsts/work/1/s/artifacts/bin/UITest.Appium/Debug/net10.0/UITest.Appium.dll
UITest.NUnit -> /home/vsts/work/1/s/artifacts/bin/UITest.NUnit/Debug/net10.0/UITest.NUnit.dll
UITest.Analyzers -> /home/vsts/work/1/s/artifacts/bin/UITest.Analyzers/Debug/netstandard2.0/UITest.Analyzers.dll
Controls.TestCases.Android.Tests -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll
Test run for /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll (.NETCoreApp,Version=v10.0)
VSTest version 18.0.1 (x64)
Starting test execution, please wait...
A total of 1 test files matched the specified pattern.
NUnit Adapter 4.5.0.0: Test execution started
Running selected tests in /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll
NUnit3TestExecutor discovered 1 of 1 NUnit test cases using Current Discovery mode, Non-Explicit run
>>>>> 06/25/2026 15:06:27 FixtureSetup for Issue33110(Android)
>>>>> 06/25/2026 15:06:30 GraphicsViewDirtyRectShouldHaveIntegerDimensions Start
>>>>> 06/25/2026 15:06:33 GraphicsViewDirtyRectShouldHaveIntegerDimensions Stop
Passed GraphicsViewDirtyRectShouldHaveIntegerDimensions [3 s]
NUnit Adapter 4.5.0.0: Test execution complete
[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.8.2+699d445a1a (64-bit .NET 10.0.0)
[xUnit.net 00:00:00.13] Discovering: Controls.TestCases.Android.Tests
[xUnit.net 00:00:00.61] Discovered: Controls.TestCases.Android.Tests
Results File: /home/vsts/work/1/s/CustomAgentLogsTmp/UITests/TestResults/Issue33110.trx
Test Run Successful.
Total tests: 1
Passed: 1
Total time: 25.7398 Seconds
>>> TRX_RESULT_FILE: /home/vsts/work/1/s/CustomAgentLogsTmp/UITests/TestResults/Issue33110.trx
📁 Fix files reverted (2 files)
src/Graphics/src/Graphics/Platforms/Android/PlatformGraphicsView.cssrc/Graphics/src/Graphics/Platforms/Windows/PlatformGraphicsView.cs
📱 UI Tests — GraphicsView
Detected UI test categories: GraphicsView
✅ Deep UI tests — 44 passed, 0 failed across 1 category on platform-pool agent (replaces in-process counts above).
🧪 UI Test Execution Results (deep, platform pool)
| Category | Tests | Snapshot diffs |
|---|---|---|
GraphicsView |
44/44 ✓ | — |
📎 Download drop-deep-uitests artifact (TRX + snapshot diffs) |
📋 Pre-Flight — Context & Validation
Issue: #33110 - GraphicsView dirtyRect dimensions should be integers, not fractional values
PR: #34564 - Fix GraphicsView dirtyRect dimensions being fractional
Platforms Affected: Android, Windows
Files Changed: 2 implementation, 9 test/snapshot
Key Findings
- GitHub CLI authentication was unavailable (
gh auth loginrequired), so PR narrative/comments/checks could not be fetched live; context was gathered from the localpr-review-34564branch, local diff, and existing gate output. - PR changes Android and Windows
PlatformGraphicsViewto round logical dirtyRect dimensions and adjust scale so rounded logical size maps back to exact native pixels. - Gate was already completed separately and passed for Android: Issue33110 failed without the fix and passed with the PR fix. Gate was not rerun.
- The independent code review found the core approach sound but identified Windows RTL translation using rounded logical width instead of actual physical width, plus Android touch bounds remaining unrounded.
Code Review Summary
Verdict: NEEDS_CHANGES
Confidence: low
Errors: 1 | Warnings: 1 | Suggestions: 2
Key code review findings:
- ❌
src/Graphics/src/Graphics/Platforms/Windows/PlatformGraphicsView.cs: Windows RTL mirror translation should useactualWidth, not rounded_dirty.Width, after adjusted scaling. ⚠️ src/Core/src/Platform/Android/PlatformTouchGraphicsView.cs: touch containment bounds remain based onwidth / _scale, diverging from the rounded drawable dirtyRect space.- 💡 iOS/Mac follow-up may be warranted if fractional dirtyRect can occur at non-standard scales.
- 💡 Windows RTL/fractional-DIP coverage is not covered by the new Android-focused UI test.
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| PR | PR #34564 | Cache rounded logical dimensions and adjusted scale in platform draw code | ✅ PASSED (Gate) | PlatformGraphicsView.cs Android/Windows + tests/snapshots |
Original PR; gate confirmed Android regression test behavior |
🔬 Code Review — Deep Analysis
Code Review — PR #34564
Independent Assessment
What this changes: Android and Windows PlatformGraphicsView pass integer logical dimensions in the dirtyRect argument to IDrawable.Draw(). The old code divided raw pixel dimensions by display density and could pass fractional values; the PR rounds the quotient and derives adjusted scale factors so rounded logical dimensions map back to the exact native allocation.
Inferred motivation: On fractional densities such as Android 2.625x, a 100 dp view can receive 263 px, and 263 / 2.625 = 100.19. Drawables that use dirtyRect.Width as an authoritative logical size can render off-by-one/sub-pixel artifacts.
Is the approach sound? The adjusted-scale invariant is sound for the reported Android failure. However, code review found two concrete edge cases: Windows RTL transform anchors to rounded logical width, and Android touch bounds remain in the old unrounded coordinate space.
Reconciliation with PR Narrative
Author claims: Fixes fractional IDrawable.Draw() dirtyRect dimensions for Android and Windows and validates Android through a new Issue33110 UI test.
Agreement/disagreement: The root cause matches the code and gate evidence. The implementation needs follow-up for Windows RTL and Android touch coordinate consistency.
Prior Review Reconciliation
No prior ❌ Error findings found in accessible local context. GitHub review/comment surfaces could not be queried because gh was unauthenticated in this environment.
Blast Radius Assessment
- Runs for all instances: Yes. All Android
GraphicsView-derived drawing paths usingPlatformGraphicsView.DrawContent, and WindowsGraphicsViewdrawing, are affected. - Startup impact: No. The changed code executes during draw/layout, not startup.
- Static/shared state: No new static state.
CI Status
- Required-check result: undetermined;
gh pr checks --requiredcould not run becauseghwas unauthenticated. - Gate result supplied by caller: ✅ PASSED on Android; tests fail without fix and pass with PR fix.
- Classification: targeted gate evidence passed; full CI remained unavailable from this environment.
- Action taken: confidence capped low for full CI uncertainty.
Findings
❌ Error — Windows RTL translation uses logical width after scaling
src/Graphics/src/Graphics/Platforms/Windows/PlatformGraphicsView.cs uses _dirty.Width in Matrix3x2.CreateTranslation(_dirty.Width, 0) after _dirty.Width has been rounded to logical width and an adjusted scale has been applied. The RTL mirror should be anchored to the physical width (actualWidth) so the mirrored origin maps to the actual right edge instead of the rounded logical edge.
⚠️ Warning — Android touch bounds diverge from rounded draw space
src/Core/src/Platform/Android/PlatformTouchGraphicsView.cs computes _bounds = new RectF(0, 0, width / _scale, height / _scale), while the PR's drawable receives rounded logical dimensions. Touches in a narrow sub-dp strip near the edge can be classified differently from the drawn content.
💡 Suggestion — iOS/Mac follow-up
Consider documenting or testing whether fractional dirtyRect values can occur on iOS/Mac at non-standard display scales.
💡 Suggestion — Windows RTL test coverage
The new test covers Android dirtyRect integrality but not Windows RTL/fractional-DIP transform behavior.
Failure-Mode Probing
- Zero-size view: fallback paths preserve zero-size behavior and avoid division by zero when logical dimensions are zero.
- Drawable set after size: invalidation redraws with size state already available.
- Android RTL: native canvas flip occurs before MAUI logical scaling and does not introduce the Windows matrix issue.
- Windows RTL with adjustedScaleX > 1: current PR maps the mirrored edge to logical width instead of actual width, causing a sub-pixel offset.
- Touch near Android right/bottom edge: PR draw space is rounded while touch bounds are unrounded, so edge containment can diverge.
Verdict: NEEDS_CHANGES
Confidence: low
Summary: The PR's core adjusted-scale fix is well-motivated and the Android gate passed, but code review found concrete Windows RTL and Android touch-bound consistency issues. These should be addressed or explicitly deferred before selecting the PR fix as best.
🛠️ Fix — Analysis & Comparison
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| 1 | try-fix-1 | Round dirtyRect in Android DrawContent, keep raw density scale, round touch bounds |
❌ FAIL | 2 files | Rejected: environment test failed after one recovery attempt; expert review also found trailing-edge pixel gap risk |
| 2 | try-fix-2 | Use VirtualView width/height as canonical logical size via new base hook | ❌ FAIL | 0 files | Rejected at expert-review/design stage: critical lifecycle/cache-refresh risk and unnecessary base surface expansion |
| 3 | try-fix-3 | Compute adjusted draw scale on demand, compute matching Android touch scale in layout, use actualWidth for Windows RTL |
✅ PASS | 3 files | Passed Android Issue33110 UI test and addresses pre-flight Windows/touch findings |
| PR | PR #34564 | Cache rounded logical dimensions and adjusted scale in Android/Windows platform draw code | ✅ PASSED (Gate) | 2 implementation files + tests/snapshots | Original PR; gate passed but code review found Windows RTL and Android touch-bound gaps |
Cross-Pollination
| Model | Round | New Ideas? | Details |
|---|---|---|---|
| maui-expert-reviewer | 1 | Yes | Candidate 1: round dirtyRect inline and keep raw density scale; simpler but risks trailing-edge gaps |
| maui-expert-reviewer | 2 | Yes | Candidate 2: use VirtualView logical size as source of truth; rejected because lifecycle/API-surface risks were too high |
| maui-expert-reviewer | 3 | Yes | Candidate 3: preserve adjusted-scale exact coverage but derive it on demand and align Android touch scaling; passed target test |
Exhausted: No — stopped because candidate 3 passed all available Android target tests and is demonstrably better than the PR's current fix.
Selected Fix: Candidate #3 — It preserves the PR's exact pixel coverage invariant, passes Issue33110 on Android, fixes the Android touch-coordinate drift identified in pre-flight, and fixes the Windows RTL physical-anchor issue. It avoids candidate 2's new API/lifecycle risk by keeping the logic private to existing platform classes.
Iteration Narrative
try-fix-1tested a minimal dirtyRect-only snap. The expert loop rejected it because rounded logical dimensions with raw density scale can leave clipped or uncovered sub-pixel edges on fractional-density devices.try-fix-2explored a deeper VirtualView-canonical design. The expert loop rejected it before implementation because it would require new base hooks/protected state and had a concrete lifecycle risk if layout occurs beforeConnect()supplies_graphicsView.try-fix-3incorporated both lessons: keep adjusted scaling for exact pixel coverage, compute it near use to avoid stale base cached state, add matching adjusted touch scaling inPlatformTouchGraphicsView, and fix Windows RTL translation toactualWidth. The Android UI test passed.
Test Evidence
- Candidate 3 command:
pwsh .github/scripts/BuildAndRunHostApp.ps1 -Platform android -TestFilter "FullyQualifiedName~Issue33110" - Candidate 3 result: ✅
GraphicsViewDirtyRectShouldHaveIntegerDimensionspassed onemulator-5554. - Full output:
CustomAgentLogsTmp/PRState/34564/PRAgent/try-fix/attempt-3/test-output.log
📝 Recommended PR Title & Description
Assessment: ✏️ Recommend updating — the winning fix preserves the current dirtyRect behavior but also includes Android touch-coordinate alignment and Windows RTL anchoring that the current description does not mention.
Recommended title
[Android][Windows] GraphicsView: Pass integer dirtyRect dimensions to IDrawable.Draw
Recommended description
### Issue Details
- In a simple layout where a parent ContentView sets fixed size (Height=50, Width=100) and hosts a custom GraphicsView with an IDrawable implementation, the Draw(ICanvas canvas, RectF dirtyRect) method receives fractional dimensions (e.g., 50.28 x 100.19) instead of the expected 50 x 100.
- This leads to improper drawing, such as thinner lines, off-by-one strokes, and visible border breaks due to the sub-pixel canvas size.
### Root Cause
- **Android**: PlatformGraphicsView passed raw fractional dp values as dirtyRect to IDrawable.Draw(). These fractions arise because Android layouts are calculated in whole pixels, and converting back to dp results in non-integer values (e.g., 263px ÷ 2.625 = 100.19 dp).
- **Windows**: PlatformGraphicsView passed raw fractional DIP values as dirtyRect to IDrawable.Draw(). This occurs because WinUI aligns layout to whole physical pixels, and converting back to DIPs produces non-integer values (e.g., 63px ÷ 1.25 = 50.4 DIPs).
### Description of Change
- **Android**: PlatformGraphicsView now precomputes logical (dp) dimensions by rounding to the nearest integer and adjusts scaling factors so that the logical dimensions, when scaled, exactly match the pixel allocation. This prevents fractional dp values and sub-pixel gaps. (src/Graphics/src/Graphics/Platforms/Android/PlatformGraphicsView.cs).
- **Android touch/hover**: PlatformTouchGraphicsView now uses matching rounded logical bounds and adjusted touch scale factors so touch and hover coordinates stay aligned with the drawable coordinate space. (src/Core/src/Platform/Android/PlatformTouchGraphicsView.cs).
- **Windows**: PlatformGraphicsView now rounds the actual size to integer logical dimensions, adjusts the scale accordingly, and applies this scale via the platform canvas. This ensures the drawable area matches the view's pixel size exactly, with no fractional or sub-pixel rendering. (src/Graphics/src/Graphics/Platforms/Windows/PlatformGraphicsView.cs).
- **Windows RTL**: The RTL mirror transform is anchored to the actual physical width instead of the rounded dirtyRect width, preserving edge-to-edge coverage when the adjusted scale is active.
### Issues Fixed
Fixes #33110
### Validated the behaviour in the following platforms
- [x] Windows
- [x] Android
- [ ] iOS
- [ ] Mac
### Output
| Platform | Before | After |
|----------|----------|----------|
| Android | <video src="https://github.com/user-attachments/assets/53231732-37e4-4452-b6eb-90fbb15cc6fe"> | <video src="https://github.com/user-attachments/assets/de782018-5b51-473c-9f3b-805f319cf4cf"> |
| Windows | <video src="https://github.com/user-attachments/assets/19051640-33b0-436f-82bf-f770ff604310"> | <video src="https://github.com/user-attachments/assets/df1f97b9-6c32-413c-bc81-4ad917ef8d9d"> |
🏁 Report — Final Recommendation
Comparative Fix Report — PR #34564
Candidates compared
| Rank | Candidate | Regression result | Assessment |
|---|---|---|---|
| 1 | pr-plus-reviewer |
✅ Based on gate-passing PR fix; sandbox Android Core build passed | Best balance. Preserves the PR's validated adjusted-scale dirtyRect fix and applies expert reviewer feedback for Windows RTL anchoring and Android touch/hover coordinate consistency. |
| 2 | try-fix-3 |
✅ PASS | Strong independent candidate. It also fixes Android touch scaling and Windows RTL, and passed the Android Issue33110 UI test. Ranked below pr-plus-reviewer because it replaces more of the PR's draw-state design, while pr-plus-reviewer is the smaller improvement on top of the already gate-passing PR. |
| 3 | pr |
✅ PASSED gate | Correctly fixes the reported Android dirtyRect regression and implements the same adjusted-scale invariant for Windows, but expert review found unresolved Windows RTL and Android touch-coordinate gaps. |
| 4 | try-fix-1 |
❌ FAIL | Minimal dirtyRect rounding was not proven by tests and was rejected for a concrete trailing-edge pixel-gap risk on fractional-density devices because it kept the raw density scale. |
| 5 | try-fix-2 |
❌ FAIL | Rejected before implementation. The VirtualView-canonical design added unnecessary base surface/lifecycle risk and did not produce a tested fix. |
Analysis
The raw PR's core idea is sound: pass integer logical dirtyRect dimensions to IDrawable.Draw() while deriving scale factors that map those rounded dimensions back to the exact native allocation. This avoids both fractional dirtyRect dimensions and trailing edge gaps, and the supplied Android gate confirms the regression test catches the bug.
The raw PR is not the best final candidate because expert review found two concrete runtime issues. Windows RTL uses _dirty.Width after _dirty.Width has been rounded, so the post-scale mirror can be anchored short of the actual right edge. Android touch and hover mapping also remain in the old raw-density coordinate system, so interactions near fractional-density edges can disagree with the rendered drawable bounds.
try-fix-3 independently reaches a high-quality result by computing Android draw scale on demand, aligning Android touch scaling, and using actualWidth for Windows RTL. It passed the target Android test and is clearly better than the raw PR.
pr-plus-reviewer wins because it incorporates the same actionable improvements while preserving the PR's existing implementation shape and validated behavior. It keeps the PR's cached adjusted-scale dirtyRect fix, adds matching Android touch/hover coordinate conversion, and fixes Windows RTL anchoring with a minimal diff. Its sandbox Android Core build passed, and the changes do not disturb the draw behavior already proven by the gate.
Winning candidate
pr-plus-reviewer
Rationale: It is the smallest complete fix: the original PR's gate-passing dirtyRect correction plus the expert review fixes for Windows RTL and Android touch mapping. Failed candidates are ranked lower as required, and the only other passing candidate (try-fix-3) is slightly more invasive without a demonstrated advantage over the reviewer-improved PR.
🧭 Next Steps — review latest findings
No alternative fix was selected for this run. Review the session findings and CI results before merging.
…nsions to IDrawable.Draw (#34564) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Issue Details - In a simple layout where a parent ContentView sets fixed size (Height=50, Width=100) and hosts a custom GraphicsView with an IDrawable implementation, the Draw(ICanvas canvas, RectF dirtyRect) method receives fractional dimensions (e.g., 50.28 x 100.19) instead of the expected 50 x 100. - This leads to improper drawing, such as thinner lines, off-by-one strokes, and visible border breaks due to the sub-pixel canvas size. ### Root Cause - **Android**: PlatformGraphicsView passes raw fractional dp values as dirtyRect to IDrawable.Draw(). These fractions arise because Android layouts are calculated in whole pixels, and converting back to dp results in non-integer values (e.g., 263px ÷ 2.625 = 100.19 dp). - **Windows:** PlatformGraphicsView passes raw fractional DIP values as dirtyRect to IDrawable.Draw(). This occurs because WinUI aligns layout to whole physical pixels, and converting back to DIPs produces non-integer values (e.g., 63px ÷ 1.25 = 50.4 DIPs). ### Description of Change - **Android**: The logic in PlatformGraphicsView now precomputes logical (dp) dimensions by rounding to the nearest integer and adjusts scaling factors so that the logical dimensions, when scaled, exactly match the pixel allocation. This prevents fractional dp values and sub-pixel gaps. (src/Graphics/src/Graphics/Platforms/Android/PlatformGraphicsView.cs). - **Windows**: The OnDraw method in PlatformGraphicsView now rounds the actual size to integer logical dimensions, adjusts the scale accordingly, and applies this scale via the platform canvas. This ensures the drawable area always matches the view's pixel size exactly, with no fractional or sub-pixel rendering. (src/Graphics/src/Graphics/Platforms/Windows/PlatformGraphicsView.cs). ### Issues Fixed Fixes #33110 ### Validated the behaviour in the following platforms - [x] Windows - [x] Android - [ ] iOS - [ ] Mac ### Output | Platform | Before | After | |----------|----------|----------| | Android | <video src="https://github.com/user-attachments/assets/53231732-37e4-4452-b6eb-90fbb15cc6fe"> | <video src="https://github.com/user-attachments/assets/de782018-5b51-473c-9f3b-805f319cf4cf"> | | Windows | <video src="https://github.com/user-attachments/assets/19051640-33b0-436f-82bf-f770ff604310"> | <video src="https://github.com/user-attachments/assets/df1f97b9-6c32-413c-bc81-4ad917ef8d9d"> | --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Jakub Florkowski <42434498+kubaflo@users.noreply.github.com>
…nsions to IDrawable.Draw (#34564) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Issue Details - In a simple layout where a parent ContentView sets fixed size (Height=50, Width=100) and hosts a custom GraphicsView with an IDrawable implementation, the Draw(ICanvas canvas, RectF dirtyRect) method receives fractional dimensions (e.g., 50.28 x 100.19) instead of the expected 50 x 100. - This leads to improper drawing, such as thinner lines, off-by-one strokes, and visible border breaks due to the sub-pixel canvas size. ### Root Cause - **Android**: PlatformGraphicsView passes raw fractional dp values as dirtyRect to IDrawable.Draw(). These fractions arise because Android layouts are calculated in whole pixels, and converting back to dp results in non-integer values (e.g., 263px ÷ 2.625 = 100.19 dp). - **Windows:** PlatformGraphicsView passes raw fractional DIP values as dirtyRect to IDrawable.Draw(). This occurs because WinUI aligns layout to whole physical pixels, and converting back to DIPs produces non-integer values (e.g., 63px ÷ 1.25 = 50.4 DIPs). ### Description of Change - **Android**: The logic in PlatformGraphicsView now precomputes logical (dp) dimensions by rounding to the nearest integer and adjusts scaling factors so that the logical dimensions, when scaled, exactly match the pixel allocation. This prevents fractional dp values and sub-pixel gaps. (src/Graphics/src/Graphics/Platforms/Android/PlatformGraphicsView.cs). - **Windows**: The OnDraw method in PlatformGraphicsView now rounds the actual size to integer logical dimensions, adjusts the scale accordingly, and applies this scale via the platform canvas. This ensures the drawable area always matches the view's pixel size exactly, with no fractional or sub-pixel rendering. (src/Graphics/src/Graphics/Platforms/Windows/PlatformGraphicsView.cs). ### Issues Fixed Fixes #33110 ### Validated the behaviour in the following platforms - [x] Windows - [x] Android - [ ] iOS - [ ] Mac ### Output | Platform | Before | After | |----------|----------|----------| | Android | <video src="https://github.com/user-attachments/assets/53231732-37e4-4452-b6eb-90fbb15cc6fe"> | <video src="https://github.com/user-attachments/assets/de782018-5b51-473c-9f3b-805f319cf4cf"> | | Windows | <video src="https://github.com/user-attachments/assets/19051640-33b0-436f-82bf-f770ff604310"> | <video src="https://github.com/user-attachments/assets/df1f97b9-6c32-413c-bc81-4ad917ef8d9d"> | --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Jakub Florkowski <42434498+kubaflo@users.noreply.github.com>
…nsions to IDrawable.Draw (#34564) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Issue Details - In a simple layout where a parent ContentView sets fixed size (Height=50, Width=100) and hosts a custom GraphicsView with an IDrawable implementation, the Draw(ICanvas canvas, RectF dirtyRect) method receives fractional dimensions (e.g., 50.28 x 100.19) instead of the expected 50 x 100. - This leads to improper drawing, such as thinner lines, off-by-one strokes, and visible border breaks due to the sub-pixel canvas size. ### Root Cause - **Android**: PlatformGraphicsView passes raw fractional dp values as dirtyRect to IDrawable.Draw(). These fractions arise because Android layouts are calculated in whole pixels, and converting back to dp results in non-integer values (e.g., 263px ÷ 2.625 = 100.19 dp). - **Windows:** PlatformGraphicsView passes raw fractional DIP values as dirtyRect to IDrawable.Draw(). This occurs because WinUI aligns layout to whole physical pixels, and converting back to DIPs produces non-integer values (e.g., 63px ÷ 1.25 = 50.4 DIPs). ### Description of Change - **Android**: The logic in PlatformGraphicsView now precomputes logical (dp) dimensions by rounding to the nearest integer and adjusts scaling factors so that the logical dimensions, when scaled, exactly match the pixel allocation. This prevents fractional dp values and sub-pixel gaps. (src/Graphics/src/Graphics/Platforms/Android/PlatformGraphicsView.cs). - **Windows**: The OnDraw method in PlatformGraphicsView now rounds the actual size to integer logical dimensions, adjusts the scale accordingly, and applies this scale via the platform canvas. This ensures the drawable area always matches the view's pixel size exactly, with no fractional or sub-pixel rendering. (src/Graphics/src/Graphics/Platforms/Windows/PlatformGraphicsView.cs). ### Issues Fixed Fixes #33110 ### Validated the behaviour in the following platforms - [x] Windows - [x] Android - [ ] iOS - [ ] Mac ### Output | Platform | Before | After | |----------|----------|----------| | Android | <video src="https://github.com/user-attachments/assets/53231732-37e4-4452-b6eb-90fbb15cc6fe"> | <video src="https://github.com/user-attachments/assets/de782018-5b51-473c-9f3b-805f319cf4cf"> | | Windows | <video src="https://github.com/user-attachments/assets/19051640-33b0-436f-82bf-f770ff604310"> | <video src="https://github.com/user-attachments/assets/df1f97b9-6c32-413c-bc81-4ad917ef8d9d"> | --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Jakub Florkowski <42434498+kubaflo@users.noreply.github.com>
…nsions to IDrawable.Draw (#34564) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Issue Details - In a simple layout where a parent ContentView sets fixed size (Height=50, Width=100) and hosts a custom GraphicsView with an IDrawable implementation, the Draw(ICanvas canvas, RectF dirtyRect) method receives fractional dimensions (e.g., 50.28 x 100.19) instead of the expected 50 x 100. - This leads to improper drawing, such as thinner lines, off-by-one strokes, and visible border breaks due to the sub-pixel canvas size. ### Root Cause - **Android**: PlatformGraphicsView passes raw fractional dp values as dirtyRect to IDrawable.Draw(). These fractions arise because Android layouts are calculated in whole pixels, and converting back to dp results in non-integer values (e.g., 263px ÷ 2.625 = 100.19 dp). - **Windows:** PlatformGraphicsView passes raw fractional DIP values as dirtyRect to IDrawable.Draw(). This occurs because WinUI aligns layout to whole physical pixels, and converting back to DIPs produces non-integer values (e.g., 63px ÷ 1.25 = 50.4 DIPs). ### Description of Change - **Android**: The logic in PlatformGraphicsView now precomputes logical (dp) dimensions by rounding to the nearest integer and adjusts scaling factors so that the logical dimensions, when scaled, exactly match the pixel allocation. This prevents fractional dp values and sub-pixel gaps. (src/Graphics/src/Graphics/Platforms/Android/PlatformGraphicsView.cs). - **Windows**: The OnDraw method in PlatformGraphicsView now rounds the actual size to integer logical dimensions, adjusts the scale accordingly, and applies this scale via the platform canvas. This ensures the drawable area always matches the view's pixel size exactly, with no fractional or sub-pixel rendering. (src/Graphics/src/Graphics/Platforms/Windows/PlatformGraphicsView.cs). ### Issues Fixed Fixes #33110 ### Validated the behaviour in the following platforms - [x] Windows - [x] Android - [ ] iOS - [ ] Mac ### Output | Platform | Before | After | |----------|----------|----------| | Android | <video src="https://github.com/user-attachments/assets/53231732-37e4-4452-b6eb-90fbb15cc6fe"> | <video src="https://github.com/user-attachments/assets/de782018-5b51-473c-9f3b-805f319cf4cf"> | | Windows | <video src="https://github.com/user-attachments/assets/19051640-33b0-436f-82bf-f770ff604310"> | <video src="https://github.com/user-attachments/assets/df1f97b9-6c32-413c-bc81-4ad917ef8d9d"> | --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Jakub Florkowski <42434498+kubaflo@users.noreply.github.com>
…nsions to IDrawable.Draw (#34564) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Issue Details - In a simple layout where a parent ContentView sets fixed size (Height=50, Width=100) and hosts a custom GraphicsView with an IDrawable implementation, the Draw(ICanvas canvas, RectF dirtyRect) method receives fractional dimensions (e.g., 50.28 x 100.19) instead of the expected 50 x 100. - This leads to improper drawing, such as thinner lines, off-by-one strokes, and visible border breaks due to the sub-pixel canvas size. ### Root Cause - **Android**: PlatformGraphicsView passes raw fractional dp values as dirtyRect to IDrawable.Draw(). These fractions arise because Android layouts are calculated in whole pixels, and converting back to dp results in non-integer values (e.g., 263px ÷ 2.625 = 100.19 dp). - **Windows:** PlatformGraphicsView passes raw fractional DIP values as dirtyRect to IDrawable.Draw(). This occurs because WinUI aligns layout to whole physical pixels, and converting back to DIPs produces non-integer values (e.g., 63px ÷ 1.25 = 50.4 DIPs). ### Description of Change - **Android**: The logic in PlatformGraphicsView now precomputes logical (dp) dimensions by rounding to the nearest integer and adjusts scaling factors so that the logical dimensions, when scaled, exactly match the pixel allocation. This prevents fractional dp values and sub-pixel gaps. (src/Graphics/src/Graphics/Platforms/Android/PlatformGraphicsView.cs). - **Windows**: The OnDraw method in PlatformGraphicsView now rounds the actual size to integer logical dimensions, adjusts the scale accordingly, and applies this scale via the platform canvas. This ensures the drawable area always matches the view's pixel size exactly, with no fractional or sub-pixel rendering. (src/Graphics/src/Graphics/Platforms/Windows/PlatformGraphicsView.cs). ### Issues Fixed Fixes #33110 ### Validated the behaviour in the following platforms - [x] Windows - [x] Android - [ ] iOS - [ ] Mac ### Output | Platform | Before | After | |----------|----------|----------| | Android | <video src="https://github.com/user-attachments/assets/53231732-37e4-4452-b6eb-90fbb15cc6fe"> | <video src="https://github.com/user-attachments/assets/de782018-5b51-473c-9f3b-805f319cf4cf"> | | Windows | <video src="https://github.com/user-attachments/assets/19051640-33b0-436f-82bf-f770ff604310"> | <video src="https://github.com/user-attachments/assets/df1f97b9-6c32-413c-bc81-4ad917ef8d9d"> | --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Jakub Florkowski <42434498+kubaflo@users.noreply.github.com>
…nsions to IDrawable.Draw (#34564) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Issue Details - In a simple layout where a parent ContentView sets fixed size (Height=50, Width=100) and hosts a custom GraphicsView with an IDrawable implementation, the Draw(ICanvas canvas, RectF dirtyRect) method receives fractional dimensions (e.g., 50.28 x 100.19) instead of the expected 50 x 100. - This leads to improper drawing, such as thinner lines, off-by-one strokes, and visible border breaks due to the sub-pixel canvas size. ### Root Cause - **Android**: PlatformGraphicsView passes raw fractional dp values as dirtyRect to IDrawable.Draw(). These fractions arise because Android layouts are calculated in whole pixels, and converting back to dp results in non-integer values (e.g., 263px ÷ 2.625 = 100.19 dp). - **Windows:** PlatformGraphicsView passes raw fractional DIP values as dirtyRect to IDrawable.Draw(). This occurs because WinUI aligns layout to whole physical pixels, and converting back to DIPs produces non-integer values (e.g., 63px ÷ 1.25 = 50.4 DIPs). ### Description of Change - **Android**: The logic in PlatformGraphicsView now precomputes logical (dp) dimensions by rounding to the nearest integer and adjusts scaling factors so that the logical dimensions, when scaled, exactly match the pixel allocation. This prevents fractional dp values and sub-pixel gaps. (src/Graphics/src/Graphics/Platforms/Android/PlatformGraphicsView.cs). - **Windows**: The OnDraw method in PlatformGraphicsView now rounds the actual size to integer logical dimensions, adjusts the scale accordingly, and applies this scale via the platform canvas. This ensures the drawable area always matches the view's pixel size exactly, with no fractional or sub-pixel rendering. (src/Graphics/src/Graphics/Platforms/Windows/PlatformGraphicsView.cs). ### Issues Fixed Fixes #33110 ### Validated the behaviour in the following platforms - [x] Windows - [x] Android - [ ] iOS - [ ] Mac ### Output | Platform | Before | After | |----------|----------|----------| | Android | <video src="https://github.com/user-attachments/assets/53231732-37e4-4452-b6eb-90fbb15cc6fe"> | <video src="https://github.com/user-attachments/assets/de782018-5b51-473c-9f3b-805f319cf4cf"> | | Windows | <video src="https://github.com/user-attachments/assets/19051640-33b0-436f-82bf-f770ff604310"> | <video src="https://github.com/user-attachments/assets/df1f97b9-6c32-413c-bc81-4ad917ef8d9d"> | --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Jakub Florkowski <42434498+kubaflo@users.noreply.github.com>
…nsions to IDrawable.Draw (#34564) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Issue Details - In a simple layout where a parent ContentView sets fixed size (Height=50, Width=100) and hosts a custom GraphicsView with an IDrawable implementation, the Draw(ICanvas canvas, RectF dirtyRect) method receives fractional dimensions (e.g., 50.28 x 100.19) instead of the expected 50 x 100. - This leads to improper drawing, such as thinner lines, off-by-one strokes, and visible border breaks due to the sub-pixel canvas size. ### Root Cause - **Android**: PlatformGraphicsView passes raw fractional dp values as dirtyRect to IDrawable.Draw(). These fractions arise because Android layouts are calculated in whole pixels, and converting back to dp results in non-integer values (e.g., 263px ÷ 2.625 = 100.19 dp). - **Windows:** PlatformGraphicsView passes raw fractional DIP values as dirtyRect to IDrawable.Draw(). This occurs because WinUI aligns layout to whole physical pixels, and converting back to DIPs produces non-integer values (e.g., 63px ÷ 1.25 = 50.4 DIPs). ### Description of Change - **Android**: The logic in PlatformGraphicsView now precomputes logical (dp) dimensions by rounding to the nearest integer and adjusts scaling factors so that the logical dimensions, when scaled, exactly match the pixel allocation. This prevents fractional dp values and sub-pixel gaps. (src/Graphics/src/Graphics/Platforms/Android/PlatformGraphicsView.cs). - **Windows**: The OnDraw method in PlatformGraphicsView now rounds the actual size to integer logical dimensions, adjusts the scale accordingly, and applies this scale via the platform canvas. This ensures the drawable area always matches the view's pixel size exactly, with no fractional or sub-pixel rendering. (src/Graphics/src/Graphics/Platforms/Windows/PlatformGraphicsView.cs). ### Issues Fixed Fixes #33110 ### Validated the behaviour in the following platforms - [x] Windows - [x] Android - [ ] iOS - [ ] Mac ### Output | Platform | Before | After | |----------|----------|----------| | Android | <video src="https://github.com/user-attachments/assets/53231732-37e4-4452-b6eb-90fbb15cc6fe"> | <video src="https://github.com/user-attachments/assets/de782018-5b51-473c-9f3b-805f319cf4cf"> | | Windows | <video src="https://github.com/user-attachments/assets/19051640-33b0-436f-82bf-f770ff604310"> | <video src="https://github.com/user-attachments/assets/df1f97b9-6c32-413c-bc81-4ad917ef8d9d"> | --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Jakub Florkowski <42434498+kubaflo@users.noreply.github.com>
…nsions to IDrawable.Draw (#34564) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Issue Details - In a simple layout where a parent ContentView sets fixed size (Height=50, Width=100) and hosts a custom GraphicsView with an IDrawable implementation, the Draw(ICanvas canvas, RectF dirtyRect) method receives fractional dimensions (e.g., 50.28 x 100.19) instead of the expected 50 x 100. - This leads to improper drawing, such as thinner lines, off-by-one strokes, and visible border breaks due to the sub-pixel canvas size. ### Root Cause - **Android**: PlatformGraphicsView passes raw fractional dp values as dirtyRect to IDrawable.Draw(). These fractions arise because Android layouts are calculated in whole pixels, and converting back to dp results in non-integer values (e.g., 263px ÷ 2.625 = 100.19 dp). - **Windows:** PlatformGraphicsView passes raw fractional DIP values as dirtyRect to IDrawable.Draw(). This occurs because WinUI aligns layout to whole physical pixels, and converting back to DIPs produces non-integer values (e.g., 63px ÷ 1.25 = 50.4 DIPs). ### Description of Change - **Android**: The logic in PlatformGraphicsView now precomputes logical (dp) dimensions by rounding to the nearest integer and adjusts scaling factors so that the logical dimensions, when scaled, exactly match the pixel allocation. This prevents fractional dp values and sub-pixel gaps. (src/Graphics/src/Graphics/Platforms/Android/PlatformGraphicsView.cs). - **Windows**: The OnDraw method in PlatformGraphicsView now rounds the actual size to integer logical dimensions, adjusts the scale accordingly, and applies this scale via the platform canvas. This ensures the drawable area always matches the view's pixel size exactly, with no fractional or sub-pixel rendering. (src/Graphics/src/Graphics/Platforms/Windows/PlatformGraphicsView.cs). ### Issues Fixed Fixes #33110 ### Validated the behaviour in the following platforms - [x] Windows - [x] Android - [ ] iOS - [ ] Mac ### Output | Platform | Before | After | |----------|----------|----------| | Android | <video src="https://github.com/user-attachments/assets/53231732-37e4-4452-b6eb-90fbb15cc6fe"> | <video src="https://github.com/user-attachments/assets/de782018-5b51-473c-9f3b-805f319cf4cf"> | | Windows | <video src="https://github.com/user-attachments/assets/19051640-33b0-436f-82bf-f770ff604310"> | <video src="https://github.com/user-attachments/assets/df1f97b9-6c32-413c-bc81-4ad917ef8d9d"> | --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Jakub Florkowski <42434498+kubaflo@users.noreply.github.com>
Note
Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!
Issue Details
Root Cause
Description of Change
Issues Fixed
Fixes #33110
Validated the behaviour in the following platforms
Output
Android_Before.mov
Android_After.mov
Windows_Before.mp4
Windows_After.mp4