Skip to content

[Android][Windows] Fix GraphicsView passing fractional dirtyRect dimensions to IDrawable.Draw - #34564

Merged
kubaflo merged 11 commits into
dotnet:inflight/currentfrom
SyedAbdulAzeemSF4852:fix-33110
Jun 25, 2026
Merged

[Android][Windows] Fix GraphicsView passing fractional dirtyRect dimensions to IDrawable.Draw#34564
kubaflo merged 11 commits into
dotnet:inflight/currentfrom
SyedAbdulAzeemSF4852:fix-33110

Conversation

@SyedAbdulAzeemSF4852

Copy link
Copy Markdown
Contributor

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

  • 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

  • Windows
  • Android
  • iOS
  • Mac

Output

Platform Before After
Android
Android_Before.mov
Android_After.mov
Windows
Windows_Before.mp4
Windows_After.mp4

@github-actions

github-actions Bot commented Mar 19, 2026

Copy link
Copy Markdown
Contributor

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 34564

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 34564"

@dotnet-policy-service dotnet-policy-service Bot added the community ✨ Community Contribution label Mar 19, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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.

@dotnet-policy-service dotnet-policy-service Bot added the partner/syncfusion Issues / PR's with Syncfusion collaboration label Mar 19, 2026
@sheiksyedm
sheiksyedm marked this pull request as ready for review March 23, 2026 11:37
Copilot AI review requested due to automatic review settings March 23, 2026 11:37
@sheiksyedm sheiksyedm added platform/windows platform/android area-drawing Shapes, Borders, Shadows, Graphics, BoxView, custom drawing labels Mar 23, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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”.

Comment thread src/Graphics/src/Graphics/Platforms/Windows/PlatformGraphicsView.cs Outdated
Comment thread src/Graphics/src/Graphics/Platforms/Android/PlatformGraphicsView.cs
Comment thread src/Graphics/src/Graphics/Platforms/Android/PlatformGraphicsView.cs
@MauiBot MauiBot added s/agent-changes-requested AI agent recommends changes - found a better alternative or issues s/agent-fix-win AI found a better alternative fix than the PR s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review) s/agent-review-incomplete and removed s/agent-changes-requested AI agent recommends changes - found a better alternative or issues s/agent-review-incomplete labels Mar 27, 2026

@kubaflo kubaflo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could you please check the AI's suggestions?

@SyedAbdulAzeemSF4852

Copy link
Copy Markdown
Contributor Author

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.

@MauiBot MauiBot added s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates and removed s/agent-fix-win AI found a better alternative fix than the PR labels Apr 8, 2026
@dotnet dotnet deleted a comment from MauiBot May 11, 2026
@MauiBot MauiBot added s/agent-review-incomplete and removed s/agent-changes-requested AI agent recommends changes - found a better alternative or issues labels May 12, 2026
@dotnet dotnet deleted a comment from MauiBot May 12, 2026
@MauiBot

This comment has been minimized.

@kubaflo kubaflo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Some tests failed

@kubaflo

kubaflo commented May 12, 2026

Copy link
Copy Markdown
Contributor

/azp run maui-pr-uitests

@github-actions github-actions Bot added the s/agent-review-in-progress AI review is currently running for this PR label Jun 24, 2026

@MauiBot MauiBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Expert Review — 2 findings

See inline comments for details.

GraphicsView graphicsView = new GraphicsView
{
Drawable = drawable,
WidthRequest = 100,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[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;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[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 MauiBot added s/agent-gate-failed AI could not verify tests catch the bug s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates and removed s/agent-fix-win AI found a better alternative fix than the PR labels Jun 24, 2026
MauiBot

This comment was marked as outdated.

@MauiBot MauiBot removed the s/agent-review-in-progress AI review is currently running for this PR label Jun 24, 2026

@MauiBot MauiBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[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"));

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[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; }

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[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).

@MauiBot MauiBot added s/agent-gate-passed AI verified tests catch the bug (fail without fix, pass with fix) and removed s/agent-gate-failed AI could not verify tests catch the bug labels Jun 24, 2026
MauiBot

This comment was marked as outdated.

PureWeen pushed a commit that referenced this pull request Jun 25, 2026
…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 MauiBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[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);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[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 MauiBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

Gate Passed Confidence Low Platform Android


🗂️ 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.cs
  • src/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 login required), so PR narrative/comments/checks could not be fetched live; context was gathered from the local pr-review-34564 branch, local diff, and existing gate output.
  • PR changes Android and Windows PlatformGraphicsView to 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 use actualWidth, not rounded _dirty.Width, after adjusted scaling.
  • ⚠️ src/Core/src/Platform/Android/PlatformTouchGraphicsView.cs: touch containment bounds remain based on width / _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 using PlatformGraphicsView.DrawContent, and Windows GraphicsView drawing, 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 --required could not run because gh was 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

  1. try-fix-1 tested 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.
  2. try-fix-2 explored 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 before Connect() supplies _graphicsView.
  3. try-fix-3 incorporated both lessons: keep adjusted scaling for exact pixel coverage, compute it near use to avoid stale base cached state, add matching adjusted touch scaling in PlatformTouchGraphicsView, and fix Windows RTL translation to actualWidth. The Android UI test passed.

Test Evidence

  • Candidate 3 command: pwsh .github/scripts/BuildAndRunHostApp.ps1 -Platform android -TestFilter "FullyQualifiedName~Issue33110"
  • Candidate 3 result: ✅ GraphicsViewDirtyRectShouldHaveIntegerDimensions passed on emulator-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.

@kubaflo
kubaflo changed the base branch from main to inflight/current June 25, 2026 19:33
@kubaflo
kubaflo merged commit 37498e1 into dotnet:inflight/current Jun 25, 2026
30 of 38 checks passed
@github-actions github-actions Bot added this to the .NET 10 SR9 milestone Jun 25, 2026
kubaflo added a commit that referenced this pull request Jun 25, 2026
…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>
kubaflo added a commit that referenced this pull request Jul 3, 2026
…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>
@kubaflo kubaflo mentioned this pull request Jul 6, 2026
kubaflo added a commit that referenced this pull request Jul 6, 2026
…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>
PureWeen pushed a commit that referenced this pull request Jul 7, 2026
…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>
PureWeen pushed a commit that referenced this pull request Jul 7, 2026
…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>
kubaflo added a commit that referenced this pull request Jul 10, 2026
…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>
kubaflo added a commit that referenced this pull request Jul 15, 2026
…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>
kubaflo added a commit that referenced this pull request Jul 22, 2026
…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>
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 26, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-drawing Shapes, Borders, Shadows, Graphics, BoxView, custom drawing community ✨ Community Contribution partner/syncfusion Issues / PR's with Syncfusion collaboration platform/android platform/windows s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates s/agent-gate-passed AI verified tests catch the bug (fail without fix, pass with fix) s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improper Drawing Layout in MAUI GraphicsView Due to Extra dirtyRect Size

8 participants