Skip to content

[Android, Windows & iOS] Fix Downsize/ScaleImage to maintain aspect ratio and prevent upscaling#30808

Merged
kubaflo merged 8 commits intodotnet:inflight/currentfrom
SyedAbdulAzeemSF4852:Downsize
Mar 30, 2026
Merged

[Android, Windows & iOS] Fix Downsize/ScaleImage to maintain aspect ratio and prevent upscaling#30808
kubaflo merged 8 commits intodotnet:inflight/currentfrom
SyedAbdulAzeemSF4852:Downsize

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

  • When downsizing an image to a specific width and height, the aspect ratio is not maintained on Android and Windows. Instead, the image is resized to the exact dimensions provided.
  • In contrast, iOS and macOS preserve the aspect ratio during downsizing.

Root Cause

Android & Windows :

  • The original implementation did not maintain the image's aspect ratio when both maxWidth and maxHeight were provided. It directly scaled the image to those dimensions.

Description of Change

Android & Windows :

  • The updated implementation unifies the resizing logic by having the single-parameter overload delegate to the two-parameter version, reducing code duplication.
  • It calculates a proportional scaling factor based on both width and height constraints, ensuring the aspect ratio is preserved. Resizing is now only performed if the original image exceeds the provided size limits, improving efficiency and correctness.

iOS & MacOS :

  • The original implementation used a two-step scaling process—first scaling based on width, then correcting based on height if needed. The new implementation improves this by first checking whether resizing is necessary, then calculating scaling factors for both width and height, and applying the smaller factor to maintain the image's aspect ratio while fitting it within the specified maximum dimensions. If the image is already within bounds, it returns the original image without modification.

Issues Fixed

Fixes #30803

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

@dotnet-policy-service dotnet-policy-service Bot added the community ✨ Community Contribution label Jul 24, 2025
@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 Jul 24, 2025
@SyedAbdulAzeemSF4852 SyedAbdulAzeemSF4852 marked this pull request as ready for review July 24, 2025 12:01
Copilot AI review requested due to automatic review settings July 24, 2025 12:01
@SyedAbdulAzeemSF4852 SyedAbdulAzeemSF4852 requested a review from a team as a code owner July 24, 2025 12:01
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This pull request fixes a bug where the Downsize method in image processing was not maintaining aspect ratio when both maxWidth and maxHeight parameters were specified on Android and Windows platforms. The fix unifies the behavior across all platforms (Android, Windows, iOS, macOS) to properly preserve aspect ratios during image downsizing.

Key changes:

  • Updated downsizing algorithms to calculate proportional scaling factors using Math.Min(factorX, factorY) to maintain aspect ratio
  • Refactored single-parameter overloads to delegate to two-parameter versions, reducing code duplication
  • Added early return optimization when images are already within size bounds

Reviewed Changes

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

Show a summary per file
File Description
src/Graphics/src/Graphics/Platforms/iOS/UIImageExtensions.cs Simplified scaling logic to use minimum factor approach and early return
src/Graphics/src/Graphics/Platforms/Windows/PlatformImage.cs Fixed aspect ratio preservation and unified overload implementations
src/Graphics/src/Graphics/Platforms/Android/GraphicsExtensions.cs Updated downsizing to maintain aspect ratio using minimum scaling factor
src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue30803.cs Added automated UI test to verify aspect ratio preservation
src/Controls/tests/TestCases.HostApp/Issues/Issue30803.cs Created test UI page with downsize functionality validation

Comment thread src/Controls/tests/TestCases.HostApp/Issues/Issue30803.cs
Comment thread src/Controls/tests/TestCases.HostApp/Issues/Issue30803.cs
@jsuarezruiz
Copy link
Copy Markdown
Contributor

/azp run

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 3 pipeline(s).

@jsuarezruiz
Copy link
Copy Markdown
Contributor

/azp run

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 3 pipeline(s).

Comment thread src/Graphics/src/Graphics/Platforms/Android/GraphicsExtensions.cs
@jsuarezruiz
Copy link
Copy Markdown
Contributor

/azp run

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 3 pipeline(s).

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Dec 8, 2025

🚀 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://github.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 30808

Or

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

@rmarinho
Copy link
Copy Markdown
Member

rmarinho commented Feb 18, 2026

🤖 AI Summary

📊 Expand Full Review
🔍 Pre-Flight — Context & Validation
📝 Review SessionRefactor LoadImage to synchronous and clean up formatting · 10d03bc

Issue: #30803 - [Android & Windows] In GraphicsView, the aspect ratio is not maintained when Downsize is called with both maxWidth and maxHeight
PR: #30808 - [Android, Windows & iOS] Fix Downsize/ScaleImage to maintain aspect ratio and prevent upscaling
Platforms Affected: Android, Windows (iOS also refactored for consistency)
Files Changed: 3 implementation files, 2 test files, 2 snapshot files

Issue Summary

When calling Downsize(maxWidth, maxHeight) on Android and Windows, the image was being stretched to exact dimensions rather than maintaining its aspect ratio. iOS/macOS already preserved aspect ratio correctly.

Steps to reproduce: run sample on Android/Windows, tap "Downsize (10x10)", observe image stretched to exact 10x10 instead of preserving ratio (which should be 10x8 for an image with ~5:4 aspect ratio).

Reviewer Discussion

File:Line Reviewer Says Author Says Status
GraphicsExtensions.cs:445 Handle edge cases: zero/negative dimensions Added guard when maxWidth or maxHeight <= 0 ✅ Resolved
Issue30803.cs:70 Magic number hardcoded dimensions (10x8) should use constants Not addressed ⚠️ Open (Copilot suggestion)
Issue30803.cs:56 Magic downsize dimensions (10, 10) should be constants Not addressed ⚠️ Open (Copilot suggestion)

Edge Cases Noted

  • Zero/negative dimensions - handled (guard added per jsuarezruiz review)
  • Image already within maxWidth/maxHeight bounds - returns original (not explicitly tested)

File Classification

  • Fix files:
    • src/Graphics/src/Graphics/Platforms/Android/GraphicsExtensions.cs (+17/-25)
    • src/Graphics/src/Graphics/Platforms/Windows/PlatformImage.cs (+17/-7)
    • src/Graphics/src/Graphics/Platforms/iOS/UIImageExtensions.cs (+14/-11)
  • Test files:
    • src/Controls/tests/TestCases.HostApp/Issues/Issue30803.cs (+77)
    • src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue30803.cs (+25)
  • Snapshot files:
    • src/Controls/tests/TestCases.Android.Tests/snapshots/android/ImagePaintWithDownsize.png
    • src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ImagePaintWithDownsize.png
  • Test type: UI Tests

Fix Candidates

# Source Approach Test Result Files Changed Notes
PR PR #30808 Use Math.Min(factorX, factorY) to find minimum scaling factor; single-param overload delegates to two-param; iOS refactored to same unified approach; added guard for zero/negative values ⏳ PENDING (Gate) GraphicsExtensions.cs (+17/-25), PlatformImage.cs (+17/-7), UIImageExtensions.cs (+14/-11) Original PR

Prior Agent Review

A prior agent review was found (by rmarinho). Pre-Flight was completed, but Gate was marked ⏳ PENDING. Re-running full workflow.


🚦 Gate — Test Verification
📝 Review SessionRefactor LoadImage to synchronous and clean up formatting · 10d03bc

Result: ✅ PASSED
Platform: android
Mode: Full Verification

Check Expected Actual Result
Tests WITHOUT fix FAIL FAIL
Tests WITH fix PASS PASS
  • Tests FAIL without fix ✅ (bug is present - image is 10x10 instead of 10x8)
  • Tests PASS with fix ✅ (aspect ratio correctly preserved - image is 10x8)

Test: VerifyDownsizeMaintainsRatioWithMaxWidthHeight in Issue30803
Device: emulator-5554

Fix Candidates (Updated)

# Source Approach Test Result Files Changed Notes
PR PR #30808 Use Math.Min(factorX, factorY) scaling factor; single-param overload delegates to two-param ✅ PASS (Gate) GraphicsExtensions.cs (+17/-25), PlatformImage.cs (+17/-7), UIImageExtensions.cs (+14/-11) Original PR

🔧 Fix — Analysis & Comparison
📝 Review SessionRefactor LoadImage to synchronous and clean up formatting · 10d03bc

Fix Candidates

# Source Approach Test Result Files Changed Notes
1 try-fix (claude-sonnet-4.6) Aspect ratio comparison: compute imageAspect = W/H, boundsAspect = maxW/maxH; pick constraining dimension directly ✅ PASS GraphicsExtensions.cs (+16/-23) Mathematically equivalent to PR, different algorithmic path
2 try-fix (claude-opus-4.6) Try-width-first: compute proportional height assuming width constraint; fall back to height constraint if it overflows ✅ PASS GraphicsExtensions.cs (+30/-24) More branches, same correctness
3 try-fix (gpt-5.2) Android Matrix.SetRectToRect with ScaleToFit.Center delegates bounding-box fit to Android's matrix API ✅ PASS GraphicsExtensions.cs (+15/-1) Uses Android API but adds complexity; ScaleToFit.Center centers, may leave empty space
4 try-fix (gpt-5.3-codex) Delegate Downsize(maxWidth, maxHeight) to Downsize(Math.Min(maxWidth, maxHeight)) — reuse single-dimension overload ✅ PASS GraphicsExtensions.cs Only correct when bounds are square; incorrect if maxWidth ≠ maxHeight in extreme cases
5 try-fix (gemini) Matrix.PostScale(factor, factor) + Bitmap.CreateBitmap(..., matrix, true) where factor = Math.Min(factorX, factorY) ✅ PASS GraphicsExtensions.cs (+15/-10) Correct but adds matrix object overhead vs direct CreateScaledBitmap
6 try-fix (gpt-5.2 R2) Explicit targetW/targetH via float cross-multiplication + CreateScaledBitmap(targetW, targetH) ✅ PASS GraphicsExtensions.cs Very similar to PR; clean
7 try-fix (gpt-5.3-codex R2) Two-pass integer-floor: (srcH * maxW) / srcW, fall back if > maxH ❌ FAIL GraphicsExtensions.cs Floor gives 7 instead of 8 for 512×384 image; rounding matters
8 try-fix (gpt-5.2 R3) Rounding cross-multiplication with MidpointRounding.AwayFromZero; try-width-first, fall back to height ✅ PASS GraphicsExtensions.cs Correct but more verbose than PR
PR PR #30808 Math.Min(factorX, factorY) uniform scale factor; single-param delegates to two-param; guard for zero/negative bounds; early return if image within bounds ✅ PASS (Gate) GraphicsExtensions.cs (+17/-25), PlatformImage.cs (+17/-7), UIImageExtensions.cs (+14/-11) Original PR — cleanest, most readable, cross-platform unified

Cross-Pollination Summary

Round claude-sonnet-4.6 claude-opus-4.6 gpt-5.2 gpt-5.3-codex gemini
R2 NO NEW IDEAS NO NEW IDEAS NEW (→ Attempt 6) NEW (→ Attempt 7) NO NEW IDEAS
R3 NO NEW IDEAS NO NEW IDEAS NEW (→ Attempt 8) NO NEW IDEAS NEW (inSampleSize — skipped, power-of-2 only)
R4 NO NEW IDEAS NO NEW IDEAS NEW (area-maximization variant) NO NEW IDEAS NEW (Canvas.DrawBitmap variant)

Exhausted: Yes (past 3-round max; remaining ideas are incremental variations of already-explored approaches)

Selected Fix: PR's fix — the Math.Min(factorX, factorY) approach is the simplest, most readable, and most consistent across platforms. It unifies Android, Windows, and iOS with the same algorithm. 7 independent alternatives all confirmed this is the correct approach; none were simpler or more robust.

Root Cause

The original Downsize(maxWidth, maxHeight) on Android called Bitmap.CreateScaledBitmap(target, maxWidth, maxHeight, true) — directly stretching to exact dimensions with no aspect ratio preservation. On Windows, the same pattern existed. The fix computes a uniform scale factor Math.Min(factorX, factorY) that fits the image within both bounds while preserving aspect ratio.

Why Attempt 4 Has a Caveat

Delegating to Downsize(Math.Min(maxWidth, maxHeight)) only works correctly when the image is roughly square or the bounds are square. For example, Downsize(100, 50) on a 200×200 image would use maxSize=50, producing 50×50, but the correct result is 50×50 — coincidentally correct here. For a 400×100 image with Downsize(100, 50): maxSize=50, would produce 12×3 instead of the correct 50×12. This is a correctness bug in that approach.


📋 Report — Final Recommendation
📝 Review SessionRefactor LoadImage to synchronous and clean up formatting · 10d03bc

✅ Final Recommendation: APPROVE

Summary

PR #30808 correctly fixes the Downsize(maxWidth, maxHeight) bug on Android and Windows where images were stretched to exact dimensions instead of preserving aspect ratio. The fix unifies all three platforms (Android, Windows, iOS) to use a consistent Math.Min(factorX, factorY) scale-factor algorithm. Gate verified the tests fail without the fix and pass with it. 7 independent alternative implementations all converged on equivalent approaches, strongly validating the PR's solution.


Root Cause

The original Downsize(maxWidth, maxHeight) overload on Android called Bitmap.CreateScaledBitmap(target, maxWidth, maxHeight, true) — directly stretching to the exact requested dimensions with no aspect ratio preservation. On Windows, the same problem existed: ResizeInternal(maxWidth, maxHeight, ...) used the exact bounds as both canvas size and draw size. iOS already used a two-step correction approach but was refactored to the same unified algorithm.


Fix Quality

✅ Correct algorithm: Math.Min(factorX, factorY) finds the minimum scale factor that fits the image within both bounds while preserving aspect ratio.

✅ Edge cases handled:

  • Zero/negative bounds → early return (guard added per reviewer feedback)
  • Image already within bounds → early return, no allocation

✅ Code simplification: Single-param Downsize(maxSize) overload now delegates to the two-param version, eliminating code duplication (on Android and Windows).

✅ Cross-platform alignment: All three platforms (Android, Windows, iOS) now use the same algorithm.

✅ Tests: Correct UI test verifies Downsize(10, 10) on royals.png (512×384, ~4:3 ratio) produces 10×8 (aspect ratio preserved).


Key Insight from Try-Fix Exploration

7 independent models attempted alternative fixes — all 7 conceptually valid, 6 passing:

  • Multiple approaches (aspect-ratio comparison, try-width-first, Matrix APIs, delegation) all correctly implement the same behavior
  • Critical finding: Pure integer floor division (384 * 10) / 512 = 7 fails — rounding is essential (test expects 8). The PR's Math.Round is correct.
  • The Math.Min(factorX, factorY) approach (PR's choice) is the most readable and consistent with the codebase style.

PR Title & Description Review

Title: [Android, Windows & iOS] Fix Downsize/ScaleImage to maintain aspect ratio and prevent upscaling

  • ✅ Accurate and descriptive — matches implementation
  • 🟡 Minor: "prevent upscaling" refers to the no-op early-return when image is within bounds; the core fix is aspect ratio preservation. The title is acceptable as-is.

Description:


Code Review Findings

🟡 Minor suggestions (non-blocking):

  1. Magic numbers in test (Issue30803.cs:56,70): The values 10, 10 (downsize target) and 10, 8 (expected result) are hardcoded. Copilot reviewer already flagged this; not addressed. Since these numbers come from the known image dimensions of royals.png, extracting constants would improve clarity but isn't required.

  2. Snapshot files updated: ImagePaintWithDownsize.png snapshots were updated for both Android and Windows — appropriate since the image dimensions changed with the fix.

✅ No critical issues.


What NOT to Do (for future agents)

  • Don't use integer floor division for aspect ratio calculation: (srcH * maxW) / srcW produces 7 for a 512×384 image at maxW=10 (test expects 8). Always use Math.Round.
  • Don't delegate Downsize(maxWidth, maxHeight) to Downsize(Math.Min(maxWidth, maxHeight)): Only correct when bounds are square; produces wrong dimensions when maxWidth ≠ maxHeight significantly (e.g., Downsize(100, 50) on a 400×100 image gives wrong result).

📋 Expand PR Finalization Review
Title: ⚠️ Needs Update

Current: [Android & Windows] Fix for Downsize method not maintaining aspect ratio when both maxWidth and maxHeight are specified

Recommended: [Android, Windows & iOS] Fix Downsize/ScaleImage to maintain aspect ratio and prevent upscaling

Description: ✅ Good

Description needs updates. See details below.

✨ Suggested PR Description

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

When calling IImage.Downsize(maxWidth, maxHeight) with both a width and height constraint:

  • Android (GraphicsExtensions.Downsize): The image was scaled to the exact pixel dimensions provided, ignoring aspect ratio.
  • Windows (PlatformImage.Downsize): Same — the image was stretched to exactly maxWidth × maxHeight.
  • iOS/MacCatalyst (UIImageExtensions.ScaleImage): Had a separate bug: images that were already within bounds (smaller than maxWidth × maxHeight) were upscaled rather than returned as-is.

In contrast, the single-parameter Downsize(maxWidthOrHeight) overload correctly preserved aspect ratio on all platforms.

Root Cause

Android & Windows: The two-parameter Downsize(maxWidth, maxHeight) overload was implemented independently of the single-parameter overload and did not calculate a proportional scale factor. It passed maxWidth and maxHeight directly as target dimensions, resulting in stretching.

iOS: The ScaleImage(maxWidth, maxHeight) method used scale = originalWidth / maxWidth to compute an initial scale, which is < 1 when the image is narrower than maxWidth. This resulted in upscaling rather than returning the original. The method also always created a new scaled image even when no resizing was needed.

Description of Change

Android (GraphicsExtensions.cs):

  • The single-param Downsize(int maxSize) now delegates to Downsize(int maxWidth, int maxHeight) (code deduplication).
  • The two-param overload now computes factorX = maxWidth / width, factorY = maxHeight / height, and applies Math.Min(factorX, factorY) to preserve aspect ratio.
  • Added guard: returns original if maxWidth <= 0 || maxHeight <= 0.
  • Returns original if image is already within bounds (no unnecessary allocation).

Windows (PlatformImage.cs):

  • The single-param Downsize(float maxWidthOrHeight) now delegates to Downsize(float maxWidth, float maxHeight).
  • The two-param overload now uses the same Math.Min(factorX, factorY) pattern.
  • Added guard: returns this if maxWidth <= 0 || maxHeight <= 0.
  • Returns this if image is already within bounds.

iOS/MacCatalyst (UIImageExtensions.cs):

  • ScaleImage(maxWidth, maxHeight) simplified from a two-step correction algorithm to the same Math.Min(factorX, factorY) pattern — producing identical results when downscaling.
  • Fixed upscaling bug: now returns target when image is already within bounds.
  • Added guard: returns target if maxWidth <= 0 || maxHeight <= 0.

Behavioral change (all platforms): Downsize/ScaleImage previously always created a new image object (even for in-bounds images). After this fix, the original object is returned when no resizing is necessary. Code that relied on always receiving a newly allocated image may be affected.

Issues Fixed

Fixes #30803

Platforms Tested

  • Windows
  • Android
  • iOS
  • Mac
Code Review: ✅ Passed

Code Review — PR #30808

Code Review Findings


🔴 Title Understates Scope of iOS Fix

File: src/Graphics/src/Graphics/Platforms/iOS/UIImageExtensions.cs

Problem: The PR title says [Android & Windows] but iOS had a real bug that was also fixed: the old ScaleImage implementation upscaled images that were already within bounds. For example, a 5×8 image with maxWidth=10, maxHeight=10 — old code: scale = 5/10 = 0.5, so targetWidth = 5/0.5 = 10, targetHeight = 8/0.5 = 16, then height correction gives ~6×10 — the image is upscaled. New code correctly returns the original 5×8.

Recommendation: Update the PR title to include iOS: [Android, Windows & iOS] Fix Downsize/ScaleImage to maintain aspect ratio and prevent upscaling


🟡 Android: Parameter Variables Reused as Target Dimensions

File: src/Graphics/src/Graphics/Platforms/Android/GraphicsExtensions.cs

// After computing factor:
maxWidth = (int)Math.Round(factor * target.Width);   // ← reuses "max constraint" variable
maxHeight = (int)Math.Round(factor * target.Height); // ← reuses "max constraint" variable

var newImage = Bitmap.CreateScaledBitmap(target, maxWidth, maxHeight, true);

Problem: maxWidth and maxHeight are parameter names that semantically mean "maximum bounds". Reassigning them to hold target pixel dimensions is confusing and makes the code harder to read/maintain. The Windows version of the same fix correctly uses separate targetWidth/targetHeight variables:

// Windows (correct pattern):
var targetWidth = factor * Width;
var targetHeight = factor * Height;
return ResizeInternal(targetWidth, targetHeight, ...);

Recommendation: Use separate variables:

var targetWidth = (int)Math.Round(factor * target.Width);
var targetHeight = (int)Math.Round(factor * target.Height);
var newImage = Bitmap.CreateScaledBitmap(target, targetWidth, targetHeight, true);

🟡 Test: Hardcoded Pixel Dimensions Are Fragile

File: src/Controls/tests/TestCases.HostApp/Issues/Issue30803.cs

bool TryAccessImage(IImage downsizedImage)
{
    if (downsizedImage.Width == 10 && downsizedImage.Height == 8)
    {
        return true;
    }
    return false;
}

Problem: This hardcodes an expected output of 10×8 which assumes royals.png has a specific aspect ratio (5:4). If the test image is ever replaced or updated, this check will silently produce false negatives. It also tests absolute pixel values rather than the actual invariant (aspect ratio preservation).

Recommendation: Either:

  1. Load the original image dimensions and assert proportionality: Assert.That(result.Width / result.Height, Is.EqualTo(original.Width / original.Height).Within(...))
  2. Or document the expected royals.png dimensions as a constant with a comment explaining the dependency.

🟡 Test: Unnecessary Task.FromResult Wrapping Synchronous Code

File: src/Controls/tests/TestCases.HostApp/Issues/Issue30803.cs

async Task<IImage> LoadImageAsync()
{
    var assembly = GetType().GetTypeInfo().Assembly;
    using var stream = assembly.GetManifestResourceStream("...");
    return await Task.FromResult(PlatformImage.FromStream(stream));  // ← unnecessary
}

Problem: PlatformImage.FromStream(stream) is synchronous. Wrapping it in Task.FromResult and await-ing adds unnecessary overhead and misleads readers into thinking there's real async I/O happening.

Recommendation: Make the method synchronous, or if async is needed for interface reasons, simply return PlatformImage.FromStream(stream) without the Task.FromResult wrapper.


🟡 Minor: Missing Newline at End of File

Files:

  • src/Controls/tests/TestCases.HostApp/Issues/Issue30803.cs
  • src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue30803.cs

Both test files are missing a trailing newline (\ No newline at end of file in the diff). Most editors and linters expect files to end with a newline.


🟡 Minor: Trailing Whitespace

File: src/Graphics/src/Graphics/Platforms/Windows/PlatformImage.cs

There is a trailing whitespace on the blank line after if (maxWidth <= 0 || maxHeight <= 0) { return this; } in the new two-param Downsize overload (visible in the diff as + with trailing space).


✅ What Looks Good

  • Core algorithm is correct on all platforms: Using Math.Min(factorX, factorY) is the proper approach for aspect-ratio-preserving downscale with both width and height constraints.
  • Single-param overload refactoring is clean: Having Downsize(maxSize) delegate to Downsize(maxSize, maxSize) eliminates duplicate logic.
  • Input validation added: The maxWidth <= 0 || maxHeight <= 0 guard is a good defensive addition.
  • iOS algorithm simplification produces identical results: The old two-step correction and the new Math.Min approach are mathematically equivalent for the downscaling case.
  • Snapshot tests updated: The Android and Windows snapshot PNGs were updated to reflect the corrected aspect-ratio-preserving output.
  • Test is well-structured: AutomationId values are clear, and the test correctly taps a button and checks the resulting label text.

@rmarinho rmarinho added s/agent-approved AI agent recommends approval - PR fix is correct and optimal 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) labels Feb 18, 2026
@kubaflo kubaflo changed the title [Android & Windows] Fix for Downsize method not maintaining aspect ratio when both maxWidth and maxHeight are specified [Android, Windows & iOS] Fix Downsize/ScaleImage to maintain aspect ratio and prevent upscaling Feb 19, 2026
@kubaflo kubaflo added s/agent-fix-lose Author adopted the agent's fix and it turned out to be bad 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 s/agent-fix-lose Author adopted the agent's fix and it turned out to be bad labels Feb 20, 2026
@SyedAbdulAzeemSF4852
Copy link
Copy Markdown
Contributor Author

Addressed concerns raised in the AI summary.

@kubaflo
Copy link
Copy Markdown
Contributor

kubaflo commented Mar 11, 2026

📋 PR Finalization Review

Title: ✅ Good

Current: [Android, Windows & iOS] Fix Downsize/ScaleImage to maintain aspect ratio and prevent upscaling

Description: ✅ Good

Description needs updates. See details below.

Code Review: ✅ Passed

Code Review — PR #30808

PR: #30808 - [Android, Windows & iOS] Fix Downsize/ScaleImage to maintain aspect ratio and prevent upscaling


Code Review Findings

🟡 Suggestions

[1] Hardcoded expected dimensions in UI test

  • File: src/Controls/tests/TestCases.HostApp/Issues/Issue30803.cs
  • Lines: TryAccessImage method — downsizedImage.Width == 10 && downsizedImage.Height == 8
  • Problem: The test checks exact pixel dimensions (Width == 10 && Height == 8) which are dependent on the specific aspect ratio of royals.png. If the test image is ever replaced or the image processing pipeline changes rounding behavior, the test will fail with no explanation. Also, the expected dimensions (10×8) are not documented or explained anywhere in the test code.
  • Recommendation: Add an inline comment explaining the magic numbers and the image's native aspect ratio:
    // royals.png has ~5:4 aspect ratio; Downsize(10, 10) should produce 10×8 preserving ratio
    if (downsizedImage.Width == 10 && downsizedImage.Height == 8)
    Or consider testing Math.Abs(downsizedImage.Width / (float)downsizedImage.Height - originalAspectRatio) < tolerance for a more robust assertion.

[2] dispose flag bypass when maxWidth <= 0 || maxHeight <= 0 on Android

  • File: src/Graphics/src/Graphics/Platforms/Android/GraphicsExtensions.cs
  • Lines: Early-return guard block
  • Problem: When maxWidth <= 0 || maxHeight <= 0, the method returns target without respecting the dispose flag. If a caller passes dispose = true expecting the bitmap to be recycled after the call (as in fire-and-forget pattern), the original bitmap will not be disposed when invalid dimensions are given. This is consistent with what Windows does (return this; also bypasses disposal), but could be an unexpected behavior.
  • Recommendation: Consider documenting that disposal is skipped on invalid dimensions, or alternatively: recycle the target when dispose = true and return a new blank/error image — though that may be overkill for this guard. The current behavior matches Windows for consistency, so this is low priority. A comment would suffice:
    // Invalid dimensions: return original without resizing or disposing
    if (maxWidth <= 0 || maxHeight <= 0)
    {
        return target;
    }

✅ Looks Good

  • Unified algorithm: Using Math.Min(factorX, factorY) is the canonical way to fit an image within a bounding box while maintaining aspect ratio. This is correct and consistent across Android, Windows, and iOS.
  • Single-param delegates to two-param: Making Downsize(maxSize) call Downsize(maxSize, maxSize) reduces code duplication and ensures consistent behavior between overloads.
  • No-upscaling guard: The if (Width > maxWidth || Height > maxHeight) check now correctly skips resizing when the image is already within the target bounds — this applies consistently on all three platforms.
  • iOS improvement: The previous iOS two-step scaling correction (scale by width, then correct by height) was harder to follow and had an edge case where scale = originalWidth / maxWidth could produce scale < 1 when originalWidth < maxWidth, leading to upscaling. The new implementation is cleaner and correct.
  • Snapshot updates: Android and Windows snapshots are appropriately updated to reflect the corrected output (the downscaled image now preserves aspect ratio rather than being stretched).
  • Test placement: UI test is correctly split across TestCases.HostApp and TestCases.Shared.Tests with appropriate AutomationId wiring.
  • Test category: UITestCategories.GraphicsView is an appropriate category for this image operation test.

@MauiBot
Copy link
Copy Markdown
Collaborator

MauiBot commented Mar 29, 2026

🚦 Gate - Test Before and After Fix

📊 Expand Full Gate17f621e · Add clarifying comments for Downsize behavior

Gate Result: ✅ PASSED

Platform: ANDROID · Base: main · Merge base: 720a9d4a

Test Without Fix (expect FAIL) With Fix (expect PASS)
🖥️ Issue30803 Issue30803 ✅ FAIL — 786s ✅ PASS — 1859s
🔴 Without fix — 🖥️ Issue30803: FAIL ✅ · 786s
  Determining projects to restore...
  Restored /home/vsts/work/1/s/src/Graphics/src/Graphics/Graphics.csproj (in 762 ms).
  Restored /home/vsts/work/1/s/src/Essentials/src/Essentials.csproj (in 5.53 sec).
  Restored /home/vsts/work/1/s/src/Controls/src/Core/Controls.Core.csproj (in 8.05 sec).
  Restored /home/vsts/work/1/s/src/Core/src/Core.csproj (in 967 ms).
  Restored /home/vsts/work/1/s/src/Core/maps/src/Maps.csproj (in 585 ms).
  Restored /home/vsts/work/1/s/src/Controls/src/Xaml/Controls.Xaml.csproj (in 40 ms).
  Restored /home/vsts/work/1/s/src/Controls/Maps/src/Controls.Maps.csproj (in 32 ms).
  Restored /home/vsts/work/1/s/src/Controls/Foldable/src/Controls.Foldable.csproj (in 65 ms).
  Restored /home/vsts/work/1/s/src/BlazorWebView/src/Maui/Microsoft.AspNetCore.Components.WebView.Maui.csproj (in 433 ms).
  Restored /home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj (in 1.37 sec).
  1 of 11 projects are up-to-date for restore.
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13684045
  Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0-android36.0/Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13684045
  Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0-android36.0/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13684045
  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.60-ci+azdo.13684045
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13684045
  Maps -> /home/vsts/work/1/s/artifacts/bin/Maps/Debug/net10.0-android36.0/Microsoft.Maui.Maps.dll
  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.60-ci+azdo.13684045
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13684045
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13684045
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13684045
  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.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
  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.60-ci+azdo.13684045
  Graphics -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13684045
  Essentials -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13684045
  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.60-ci+azdo.13684045
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13684045
  Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Maps.dll
  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.60-ci+azdo.13684045
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13684045
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13684045
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13684045
  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
  Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Xaml.dll
  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:20.97
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
  Determining projects to restore...
  Restored /home/vsts/work/1/s/src/TestUtils/src/VisualTestUtils/VisualTestUtils.csproj (in 1.41 sec).
  Restored /home/vsts/work/1/s/src/TestUtils/src/UITest.NUnit/UITest.NUnit.csproj (in 860 ms).
  Restored /home/vsts/work/1/s/src/TestUtils/src/UITest.Core/UITest.Core.csproj (in 7 ms).
  Restored /home/vsts/work/1/s/src/TestUtils/src/UITest.Appium/UITest.Appium.csproj (in 1.56 sec).
  Restored /home/vsts/work/1/s/src/TestUtils/src/VisualTestUtils.MagickNet/VisualTestUtils.MagickNet.csproj (in 6.41 sec).
  Restored /home/vsts/work/1/s/src/TestUtils/src/UITest.Analyzers/UITest.Analyzers.csproj (in 4.17 sec).
  Restored /home/vsts/work/1/s/src/Controls/tests/CustomAttributes/Controls.CustomAttributes.csproj (in 21 ms).
  Restored /home/vsts/work/1/s/src/Controls/tests/TestCases.Android.Tests/Controls.TestCases.Android.Tests.csproj (in 1.9 sec).
  5 of 13 projects are up-to-date for restore.
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13684045
  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.60-ci+azdo.13684045
  Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13684045
  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.60-ci+azdo.13684045
  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.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
  VisualTestUtils.MagickNet -> /home/vsts/work/1/s/artifacts/bin/VisualTestUtils.MagickNet/Debug/netstandard2.0/VisualTestUtils.MagickNet.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.
/home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll
[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.21]   Discovering: Controls.TestCases.Android.Tests
[xUnit.net 00:00:00.77]   Discovered:  Controls.TestCases.Android.Tests
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
>>>>> 03/29/2026 19:53:17 FixtureSetup for Issue30803(Android)
>>>>> 03/29/2026 19:53:21 VerifyDownsizeMaintainsRatioWithMaxWidthHeight Start
>>>>> 03/29/2026 19:53:25 VerifyDownsizeMaintainsRatioWithMaxWidthHeight Stop
>>>>> 03/29/2026 19:53:25 Log types: logcat, bugreport, server
  Failed VerifyDownsizeMaintainsRatioWithMaxWidthHeight [5 s]
  Error Message:
     Assert.That(downsizedImageLabelText, Is.EqualTo("Success"))
  String lengths are both 7. Strings differ at index 0.
  Expected: "Success"
  But was:  "Failure"
  -----------^

  Stack Trace:
     at Microsoft.Maui.TestCases.Tests.Issues.Issue30803.VerifyDownsizeMaintainsRatioWithMaxWidthHeight() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue30803.cs:line 23

1)    at Microsoft.Maui.TestCases.Tests.Issues.Issue30803.VerifyDownsizeMaintainsRatioWithMaxWidthHeight() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue30803.cs:line 23


NUnit Adapter 4.5.0.0: Test execution complete

Total tests: 1
     Failed: 1
Test Run Failed.
 Total time: 1.4995 Minutes

🟢 With fix — 🖥️ Issue30803: PASS ✅ · 1859s

(truncated to last 15,000 chars)

s.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]

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: Failure calling service package: Broken pipe (32) [/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:11:36.30
* 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.60-ci+azdo.13684045
  Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0-android36.0/Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13684045
  Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0-android36.0/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13684045
  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.60-ci+azdo.13684045
  Maps -> /home/vsts/work/1/s/artifacts/bin/Maps/Debug/net10.0-android36.0/Microsoft.Maui.Maps.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13684045
  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.60-ci+azdo.13684045
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13684045
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13684045
  Controls.Foldable -> /home/vsts/work/1/s/artifacts/bin/Controls.Foldable/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Foldable.dll
  Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.Maps/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Maps.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13684045
  Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.Xaml/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Xaml.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.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.60-ci+azdo.13684045
  Graphics -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13684045
  Essentials -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13684045
  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.60-ci+azdo.13684045
  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.60-ci+azdo.13684045
  Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Maps.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13684045
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13684045
  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.60-ci+azdo.13684045
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13684045
  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

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:08:25.87
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
  Determining projects to restore...
  All projects are up-to-date for restore.
  Controls.CustomAttributes -> /home/vsts/work/1/s/artifacts/bin/Controls.CustomAttributes/Debug/net10.0/Controls.CustomAttributes.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13684045
  Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0/Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13684045
  Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13684045
  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.60-ci+azdo.13684045
  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
  UITest.NUnit -> /home/vsts/work/1/s/artifacts/bin/UITest.NUnit/Debug/net10.0/UITest.NUnit.dll
  UITest.Appium -> /home/vsts/work/1/s/artifacts/bin/UITest.Appium/Debug/net10.0/UITest.Appium.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.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.
/home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll
[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.15]   Discovering: Controls.TestCases.Android.Tests
[xUnit.net 00:00:00.40]   Discovered:  Controls.TestCases.Android.Tests
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
>>>>> 03/29/2026 20:15:44 FixtureSetup for Issue30803(Android)
>>>>> 03/29/2026 20:15:46 VerifyDownsizeMaintainsRatioWithMaxWidthHeight Start
>>>>> 03/29/2026 20:15:50 VerifyDownsizeMaintainsRatioWithMaxWidthHeight Stop
  Passed VerifyDownsizeMaintainsRatioWithMaxWidthHeight [4 s]
NUnit Adapter 4.5.0.0: Test execution complete

Test Run Successful.
Total tests: 1
     Passed: 1
 Total time: 24.3554 Seconds

📁 Fix files reverted (4 files)
  • eng/pipelines/ci-copilot.yml
  • src/Graphics/src/Graphics/Platforms/Android/GraphicsExtensions.cs
  • src/Graphics/src/Graphics/Platforms/Windows/PlatformImage.cs
  • src/Graphics/src/Graphics/Platforms/iOS/UIImageExtensions.cs

@MauiBot MauiBot added s/agent-review-incomplete AI agent could not complete all phases (blocker, timeout, error) and removed s/agent-approved AI agent recommends approval - PR fix is correct and optimal labels Mar 29, 2026
@MauiBot
Copy link
Copy Markdown
Collaborator

MauiBot commented Mar 29, 2026

🤖 AI Summary

📊 Expand Full Review17f621e · Add clarifying comments for Downsize behavior
🔍 Pre-Flight — Context & Validation

Issue: #30803 - [Android & Windows] In GraphicsView, the aspect ratio is not maintained when Downsize is called with both maxWidth and maxHeight
PR: #30808 - [Android, Windows & iOS] Fix Downsize/ScaleImage to maintain aspect ratio and prevent upscaling
Platforms Affected: Android, Windows (primary bugs); iOS/macOS (refactored for consistency)
Files Changed: 3 implementation files, 2 test files, 2 snapshot files

Key Findings

  • Downsize(maxWidth, maxHeight) on Android called Bitmap.CreateScaledBitmap(target, maxWidth, maxHeight, true) directly — stretching to exact dimensions, ignoring aspect ratio
  • Same bug on Windows: ResizeInternal(maxWidth, maxHeight, ...) passed raw max bounds as canvas dimensions instead of proportionally computed ones
  • iOS already handled aspect ratio via a 2-step scale-correction approach; PR refactors it to the same Math.Min(factorX, factorY) strategy for consistency
  • Single-param overload Downsize(maxSize) now delegates to two-param version on Android, reducing code duplication
  • Guard added for zero/negative dimensions (returns original); also adds early-return if image already fits within bounds
  • Test uses royals.png (512×384, 4:3 ratio): Downsize(10, 10) should produce 10×8
  • Snapshot files updated for Android and Windows to reflect corrected aspect-ratio output
  • All reviewer feedback addressed (zero/negative guard added per jsuarezruiz; constants for magic numbers declined with explanation)

Reviewer Discussion

File Reviewer Comment Status
GraphicsExtensions.cs:446 jsuarezruiz Handle edge cases: zero/negative dimensions ✅ Addressed
Issue30803.cs:56 Copilot Magic numbers (10,10) should be constants ⚠️ Declined with explanation
Issue30803.cs:71 Copilot Magic numbers (10×8) should be constants ⚠️ Declined, comment added explaining dimensions

Prior Agent Review (Incomplete)

Prior review ran 3/4 try-fix models; cross-pollination was incomplete; no report written. This session completes the review from scratch.

Fix Candidates

# Source Approach Test Result Files Changed Notes
PR PR #30808 Use Math.Min(factorX, factorY) scale factor; single-param delegates to two-param; guard for zero/negative bounds; early return if within bounds ✅ PASSED (Gate) GraphicsExtensions.cs, PlatformImage.cs, UIImageExtensions.cs Original PR

🔧 Fix — Analysis & Comparison

Fix Candidates

# Source Approach Test Result Files Changed Notes
1 try-fix (claude-opus-4.6) Cross-multiplication branching: (long)W * maxH > (long)H * maxW to pick constraining dimension; single float division for other dimension ✅ PASS GraphicsExtensions.cs Avoids dual factor computation; guards overflow with (long) cast
2 try-fix (claude-sonnet-4.6) Width-first fallback: compute targetHeight = Round(H * maxW / W); if >maxHeight, correct to height-constrained ✅ PASS GraphicsExtensions.cs Staged approach, avoids scale factors entirely
3 try-fix (gpt-5.3-codex) Aspect-ratio-driven: compute aspectRatio = W/H as double; derive targetHeight = Round(maxWidth/aspectRatio); if >maxHeight, switch to height-constrained ✅ PASS GraphicsExtensions.cs Uses raw aspect ratio double; different from scale-factor and cross-multiply approaches
4 try-fix (gpt-5.2) Delegate to single-param overload: call Downsize(maxWidth, dispose:false) and Downsize(maxHeight, dispose:false), pick the one that fits both bounds ✅ PASS GraphicsExtensions.cs Structurally different: creates two intermediate bitmaps, picks best fit; avoids any direct dimension math
PR PR #30808 Math.Min(factorX, factorY) uniform scale factor; single-param delegates to two-param; guard for zero/negative bounds ✅ PASSED (Gate) GraphicsExtensions.cs, PlatformImage.cs, UIImageExtensions.cs Original PR

Cross-Pollination

Model Round New Ideas? Details
claude-opus-4.6 2 No "Five approaches cover the full algorithmic design space for aspect-ratio-preserving 2D downscaling"
claude-sonnet-4.6 2 No Confirmed exhaustion: cross-multiply, fallback, ratio, decompose, min-scale-factor are all independent algorithmic classes

Exhausted: Yes
Selected Fix: PR #30808Math.Min(factorX, factorY) is idiomatic, applies consistently across Android + Windows + iOS in a single unified style, and is the minimal change that fully resolves the bug on all platforms.


📋 Report — Final Recommendation

✅ Final Recommendation: APPROVE

Phase Status

Phase Status Notes
Pre-Flight ✅ COMPLETE Issue #30803: Android & Windows Downsize(maxWidth, maxHeight) stretched to exact dimensions; iOS refactored for consistency
Gate ✅ PASSED Platform: Android
Try-Fix ✅ COMPLETE 4 attempts, all 4 passing; cross-pollination exhausted (2 models, NO NEW IDEAS)
Report ✅ COMPLETE

Summary

PR #30808 fixes a bug where Bitmap.Downsize(maxWidth, maxHeight) on Android and PlatformImage.Downsize(maxWidth, maxHeight) on Windows passed raw dimensions directly to the scaling API, stretching images to exact pixel bounds instead of preserving aspect ratio. The fix applies Math.Min(factorX, factorY) across all three platforms (Android, Windows, iOS) for a consistent, unified approach. Gate passed on Android. All 4 independent fix attempts also passed, confirming the algorithm space is well-covered. Selected Fix: PR's fix.

Root Cause

  • Android (GraphicsExtensions.cs): Bitmap.CreateScaledBitmap(target, maxWidth, maxHeight, true) used maxWidth/maxHeight as literal pixel dimensions — no aspect ratio math at all.
  • Windows (PlatformImage.cs): ResizeInternal(maxWidth, maxHeight, 0, 0, maxWidth, maxHeight, ...) passed the raw bounds as both canvas size and draw region — same issue.
  • iOS (UIImageExtensions.cs): Had a 2-step correction approach that worked but was more complex; refactored for consistency.
  • The single-param Downsize(maxSize) overloads on all platforms already handled aspect ratio correctly; only the two-param overloads were broken.

Fix Quality

The fix is clean, minimal, and correct:

  • Algorithm: factorX = maxWidth / Width, factorY = maxHeight / Height, factor = Math.Min(factorX, factorY) — standard "fit within box" scaling.
  • Unified: Same pattern applied to all three platforms, making the behavior consistent.
  • Single-param refactor: Android's Downsize(maxSize) now delegates to Downsize(maxSize, maxSize), reducing duplication.
  • Guard added: Returns early for maxWidth <= 0 || maxHeight <= 0 and when image already fits within bounds.
  • Test coverage: New UITest (Issue30803) verifies royals.png (512×384) → Downsize(10, 10)10×8 on Android. Snapshot files updated for Android and Windows.
  • Reviewer feedback addressed: Zero/negative guard added per reviewer request.

No concerns. The PR is ready to merge.


@MauiBot MauiBot added s/agent-approved AI agent recommends approval - PR fix is correct and optimal and removed s/agent-review-incomplete AI agent could not complete all phases (blocker, timeout, error) labels Mar 29, 2026
@dotnet dotnet deleted a comment from MauiBot Mar 30, 2026
@kubaflo kubaflo changed the base branch from main to inflight/current March 30, 2026 11:30
@kubaflo kubaflo merged commit 549871f into dotnet:inflight/current Mar 30, 2026
33 checks passed
PureWeen pushed a commit that referenced this pull request Apr 8, 2026
…atio and prevent upscaling (#30808)

<!-- 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 

- When downsizing an image to a specific width and height, the aspect
ratio is not maintained on Android and Windows. Instead, the image is
resized to the exact dimensions provided.
- In contrast, iOS and macOS preserve the aspect ratio during
downsizing.


### Root Cause
Android & Windows :

- The original implementation did not maintain the image's aspect ratio
when both maxWidth and maxHeight were provided. It directly scaled the
image to those dimensions.


### Description of Change
Android & Windows :

- The updated implementation unifies the resizing logic by having the
single-parameter overload delegate to the two-parameter version,
reducing code duplication.
- It calculates a proportional scaling factor based on both width and
height constraints, ensuring the aspect ratio is preserved. Resizing is
now only performed if the original image exceeds the provided size
limits, improving efficiency and correctness.

iOS & MacOS :

- The original implementation used a two-step scaling process—first
scaling based on width, then correcting based on height if needed. The
new implementation improves this by first checking whether resizing is
necessary, then calculating scaling factors for both width and height,
and applying the smaller factor to maintain the image's aspect ratio
while fitting it within the specified maximum dimensions. If the image
is already within bounds, it returns the original image without
modification.

### Issues Fixed
Fixes #30803 

### Validated the behaviour in the following platforms

- [x] Windows
- [x] Android
- [x] iOS
- [x] Mac

### Output
| Platform | Before | After|
|----------|----------|----------|
| Android | <video
src="https://github.com/user-attachments/assets/42b64c09-c470-422f-ae81-1d5c1e5a7c49">
| <video
src="https://github.com/user-attachments/assets/43def17f-5f74-4506-ac1d-584a23a3d4de">
|
| Windows | <video
src="https://github.com/user-attachments/assets/ada46668-299b-45b7-804a-ee79f16e0c94">
| <video
src="https://github.com/user-attachments/assets/a0ef9621-3612-4232-abb1-e8b276fa7264">
|
devanathan-vaithiyanathan pushed a commit to devanathan-vaithiyanathan/maui that referenced this pull request Apr 9, 2026
…atio and prevent upscaling (dotnet#30808)

<!-- 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 

- When downsizing an image to a specific width and height, the aspect
ratio is not maintained on Android and Windows. Instead, the image is
resized to the exact dimensions provided.
- In contrast, iOS and macOS preserve the aspect ratio during
downsizing.


### Root Cause
Android & Windows :

- The original implementation did not maintain the image's aspect ratio
when both maxWidth and maxHeight were provided. It directly scaled the
image to those dimensions.


### Description of Change
Android & Windows :

- The updated implementation unifies the resizing logic by having the
single-parameter overload delegate to the two-parameter version,
reducing code duplication.
- It calculates a proportional scaling factor based on both width and
height constraints, ensuring the aspect ratio is preserved. Resizing is
now only performed if the original image exceeds the provided size
limits, improving efficiency and correctness.

iOS & MacOS :

- The original implementation used a two-step scaling process—first
scaling based on width, then correcting based on height if needed. The
new implementation improves this by first checking whether resizing is
necessary, then calculating scaling factors for both width and height,
and applying the smaller factor to maintain the image's aspect ratio
while fitting it within the specified maximum dimensions. If the image
is already within bounds, it returns the original image without
modification.

### Issues Fixed
Fixes dotnet#30803 

### Validated the behaviour in the following platforms

- [x] Windows
- [x] Android
- [x] iOS
- [x] Mac

### Output
| Platform | Before | After|
|----------|----------|----------|
| Android | <video
src="https://github.com/user-attachments/assets/42b64c09-c470-422f-ae81-1d5c1e5a7c49">
| <video
src="https://github.com/user-attachments/assets/43def17f-5f74-4506-ac1d-584a23a3d4de">
|
| Windows | <video
src="https://github.com/user-attachments/assets/ada46668-299b-45b7-804a-ee79f16e0c94">
| <video
src="https://github.com/user-attachments/assets/a0ef9621-3612-4232-abb1-e8b276fa7264">
|
PureWeen pushed a commit that referenced this pull request Apr 14, 2026
…atio and prevent upscaling (#30808)

<!-- 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 

- When downsizing an image to a specific width and height, the aspect
ratio is not maintained on Android and Windows. Instead, the image is
resized to the exact dimensions provided.
- In contrast, iOS and macOS preserve the aspect ratio during
downsizing.


### Root Cause
Android & Windows :

- The original implementation did not maintain the image's aspect ratio
when both maxWidth and maxHeight were provided. It directly scaled the
image to those dimensions.


### Description of Change
Android & Windows :

- The updated implementation unifies the resizing logic by having the
single-parameter overload delegate to the two-parameter version,
reducing code duplication.
- It calculates a proportional scaling factor based on both width and
height constraints, ensuring the aspect ratio is preserved. Resizing is
now only performed if the original image exceeds the provided size
limits, improving efficiency and correctness.

iOS & MacOS :

- The original implementation used a two-step scaling process—first
scaling based on width, then correcting based on height if needed. The
new implementation improves this by first checking whether resizing is
necessary, then calculating scaling factors for both width and height,
and applying the smaller factor to maintain the image's aspect ratio
while fitting it within the specified maximum dimensions. If the image
is already within bounds, it returns the original image without
modification.

### Issues Fixed
Fixes #30803 

### Validated the behaviour in the following platforms

- [x] Windows
- [x] Android
- [x] iOS
- [x] Mac

### Output
| Platform | Before | After|
|----------|----------|----------|
| Android | <video
src="https://github.com/user-attachments/assets/42b64c09-c470-422f-ae81-1d5c1e5a7c49">
| <video
src="https://github.com/user-attachments/assets/43def17f-5f74-4506-ac1d-584a23a3d4de">
|
| Windows | <video
src="https://github.com/user-attachments/assets/ada46668-299b-45b7-804a-ee79f16e0c94">
| <video
src="https://github.com/user-attachments/assets/a0ef9621-3612-4232-abb1-e8b276fa7264">
|
@PureWeen PureWeen mentioned this pull request Apr 14, 2026
devanathan-vaithiyanathan pushed a commit to Tamilarasan-Paranthaman/maui that referenced this pull request Apr 21, 2026
…atio and prevent upscaling (dotnet#30808)

<!-- 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 

- When downsizing an image to a specific width and height, the aspect
ratio is not maintained on Android and Windows. Instead, the image is
resized to the exact dimensions provided.
- In contrast, iOS and macOS preserve the aspect ratio during
downsizing.


### Root Cause
Android & Windows :

- The original implementation did not maintain the image's aspect ratio
when both maxWidth and maxHeight were provided. It directly scaled the
image to those dimensions.


### Description of Change
Android & Windows :

- The updated implementation unifies the resizing logic by having the
single-parameter overload delegate to the two-parameter version,
reducing code duplication.
- It calculates a proportional scaling factor based on both width and
height constraints, ensuring the aspect ratio is preserved. Resizing is
now only performed if the original image exceeds the provided size
limits, improving efficiency and correctness.

iOS & MacOS :

- The original implementation used a two-step scaling process—first
scaling based on width, then correcting based on height if needed. The
new implementation improves this by first checking whether resizing is
necessary, then calculating scaling factors for both width and height,
and applying the smaller factor to maintain the image's aspect ratio
while fitting it within the specified maximum dimensions. If the image
is already within bounds, it returns the original image without
modification.

### Issues Fixed
Fixes dotnet#30803 

### Validated the behaviour in the following platforms

- [x] Windows
- [x] Android
- [x] iOS
- [x] Mac

### Output
| Platform | Before | After|
|----------|----------|----------|
| Android | <video
src="https://github.com/user-attachments/assets/42b64c09-c470-422f-ae81-1d5c1e5a7c49">
| <video
src="https://github.com/user-attachments/assets/43def17f-5f74-4506-ac1d-584a23a3d4de">
|
| Windows | <video
src="https://github.com/user-attachments/assets/ada46668-299b-45b7-804a-ee79f16e0c94">
| <video
src="https://github.com/user-attachments/assets/a0ef9621-3612-4232-abb1-e8b276fa7264">
|
Ahamed-Ali pushed a commit that referenced this pull request Apr 22, 2026
…atio and prevent upscaling (#30808)

<!-- 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 

- When downsizing an image to a specific width and height, the aspect
ratio is not maintained on Android and Windows. Instead, the image is
resized to the exact dimensions provided.
- In contrast, iOS and macOS preserve the aspect ratio during
downsizing.


### Root Cause
Android & Windows :

- The original implementation did not maintain the image's aspect ratio
when both maxWidth and maxHeight were provided. It directly scaled the
image to those dimensions.


### Description of Change
Android & Windows :

- The updated implementation unifies the resizing logic by having the
single-parameter overload delegate to the two-parameter version,
reducing code duplication.
- It calculates a proportional scaling factor based on both width and
height constraints, ensuring the aspect ratio is preserved. Resizing is
now only performed if the original image exceeds the provided size
limits, improving efficiency and correctness.

iOS & MacOS :

- The original implementation used a two-step scaling process—first
scaling based on width, then correcting based on height if needed. The
new implementation improves this by first checking whether resizing is
necessary, then calculating scaling factors for both width and height,
and applying the smaller factor to maintain the image's aspect ratio
while fitting it within the specified maximum dimensions. If the image
is already within bounds, it returns the original image without
modification.

### Issues Fixed
Fixes #30803 

### Validated the behaviour in the following platforms

- [x] Windows
- [x] Android
- [x] iOS
- [x] Mac

### Output
| Platform | Before | After|
|----------|----------|----------|
| Android | <video
src="https://github.com/user-attachments/assets/42b64c09-c470-422f-ae81-1d5c1e5a7c49">
| <video
src="https://github.com/user-attachments/assets/43def17f-5f74-4506-ac1d-584a23a3d4de">
|
| Windows | <video
src="https://github.com/user-attachments/assets/ada46668-299b-45b7-804a-ee79f16e0c94">
| <video
src="https://github.com/user-attachments/assets/a0ef9621-3612-4232-abb1-e8b276fa7264">
|
PureWeen pushed a commit that referenced this pull request Apr 22, 2026
…atio and prevent upscaling (#30808)

<!-- 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 

- When downsizing an image to a specific width and height, the aspect
ratio is not maintained on Android and Windows. Instead, the image is
resized to the exact dimensions provided.
- In contrast, iOS and macOS preserve the aspect ratio during
downsizing.


### Root Cause
Android & Windows :

- The original implementation did not maintain the image's aspect ratio
when both maxWidth and maxHeight were provided. It directly scaled the
image to those dimensions.


### Description of Change
Android & Windows :

- The updated implementation unifies the resizing logic by having the
single-parameter overload delegate to the two-parameter version,
reducing code duplication.
- It calculates a proportional scaling factor based on both width and
height constraints, ensuring the aspect ratio is preserved. Resizing is
now only performed if the original image exceeds the provided size
limits, improving efficiency and correctness.

iOS & MacOS :

- The original implementation used a two-step scaling process—first
scaling based on width, then correcting based on height if needed. The
new implementation improves this by first checking whether resizing is
necessary, then calculating scaling factors for both width and height,
and applying the smaller factor to maintain the image's aspect ratio
while fitting it within the specified maximum dimensions. If the image
is already within bounds, it returns the original image without
modification.

### Issues Fixed
Fixes #30803 

### Validated the behaviour in the following platforms

- [x] Windows
- [x] Android
- [x] iOS
- [x] Mac

### Output
| Platform | Before | After|
|----------|----------|----------|
| Android | <video
src="https://github.com/user-attachments/assets/42b64c09-c470-422f-ae81-1d5c1e5a7c49">
| <video
src="https://github.com/user-attachments/assets/43def17f-5f74-4506-ac1d-584a23a3d4de">
|
| Windows | <video
src="https://github.com/user-attachments/assets/ada46668-299b-45b7-804a-ee79f16e0c94">
| <video
src="https://github.com/user-attachments/assets/a0ef9621-3612-4232-abb1-e8b276fa7264">
|
PureWeen pushed a commit that referenced this pull request Apr 28, 2026
…atio and prevent upscaling (#30808)

<!-- 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 

- When downsizing an image to a specific width and height, the aspect
ratio is not maintained on Android and Windows. Instead, the image is
resized to the exact dimensions provided.
- In contrast, iOS and macOS preserve the aspect ratio during
downsizing.


### Root Cause
Android & Windows :

- The original implementation did not maintain the image's aspect ratio
when both maxWidth and maxHeight were provided. It directly scaled the
image to those dimensions.


### Description of Change
Android & Windows :

- The updated implementation unifies the resizing logic by having the
single-parameter overload delegate to the two-parameter version,
reducing code duplication.
- It calculates a proportional scaling factor based on both width and
height constraints, ensuring the aspect ratio is preserved. Resizing is
now only performed if the original image exceeds the provided size
limits, improving efficiency and correctness.

iOS & MacOS :

- The original implementation used a two-step scaling process—first
scaling based on width, then correcting based on height if needed. The
new implementation improves this by first checking whether resizing is
necessary, then calculating scaling factors for both width and height,
and applying the smaller factor to maintain the image's aspect ratio
while fitting it within the specified maximum dimensions. If the image
is already within bounds, it returns the original image without
modification.

### Issues Fixed
Fixes #30803 

### Validated the behaviour in the following platforms

- [x] Windows
- [x] Android
- [x] iOS
- [x] Mac

### Output
| Platform | Before | After|
|----------|----------|----------|
| Android | <video
src="https://github.com/user-attachments/assets/42b64c09-c470-422f-ae81-1d5c1e5a7c49">
| <video
src="https://github.com/user-attachments/assets/43def17f-5f74-4506-ac1d-584a23a3d4de">
|
| Windows | <video
src="https://github.com/user-attachments/assets/ada46668-299b-45b7-804a-ee79f16e0c94">
| <video
src="https://github.com/user-attachments/assets/a0ef9621-3612-4232-abb1-e8b276fa7264">
|
PureWeen added a commit that referenced this pull request Apr 29, 2026
## Blazor
- Fix: Filter precompressed RCL assets from MAUI Blazor Hybrid APKs by
@mattleibow in #33917
  <details>
  <summary>🔧 Fixes</summary>

- [.NET MAUI Blazor Hybrid App should not precompress
assets](#33773)
  </details>

- [Windows] Fix for Runtime error when closing external window with WPF
Webview Control by @BagavathiPerumal in
#34006
  <details>
  <summary>🔧 Fixes</summary>

- [Runtime error when closing external window with WPF Webview
Control](#32944)
  </details>

## Button
- [Android] ImageButton CornerRadius not being applied - fix by @kubaflo
in #30074
  <details>
  <summary>🔧 Fixes</summary>

- [ImageButton CornerRadius not being applied on
Android](#23854)
  </details>

- Fix Disabled visual state ignored when Button has locally-set
BackgroundColor/TextColor by @Dhivya-SF4094 in
#34444
  <details>
  <summary>🔧 Fixes</summary>

- [[regression/9.0] VisualState "Disabled" is not properly applied for
Button with custom
appearance](#34363)
  </details>

## CollectionView
- Fix CollectionView grid spacing updates for first row and column by
@KarthikRajaKalaimani in #34527
  <details>
  <summary>🔧 Fixes</summary>

- [[MAUI] I2_Vertical grid for horizontal Item Spacing and Vertical Item
Spacing - horizontally updating the spacing only applies to the second
column](#34257)
  </details>

- Fix CollectionView record struct selection on Windows by
@jeremy-visionaid in #33488

- [Android] Ensure disconnected ItemsViewHandler doesn't hold onto the
items source by @filipnavara in
#24610
  <details>
  <summary>🔧 Fixes</summary>

- [Crash on NullReferenceException with measurement cells in
CollectionView](#24304)
  </details>

- [Windows] Fixed VisualState Setters not working properly for
CollectionView by @Dhivya-SF4094 in
#27230
  <details>
  <summary>🔧 Fixes</summary>

- [VisualState Setters not working properly on Windows for a
CollectionView](#27086)
- [[regression/8.0.3] [Windows][CollectionView]Label Disappear when set
Style in
ContentPage.Resources](#19209)
- [[Windows] Label style defined as ContentPage Resource doesn't
propagate to
CollectionView](#18701)
  </details>

- [Windows] Fixed Margin doesn't work inside CollectionView EmptyView by
@Dhivya-SF4094 in #29897
  <details>
  <summary>🔧 Fixes</summary>

- [Margin doesn't work inside CollectionView
EmptyView](#8494)
  </details>

- [Android, Windows] Fix CarouselView PreviousPosition/PreviousItem
incorrect during animated ScrollTo() by @praveenkumarkarunanithi in
#34570
  <details>
  <summary>🔧 Fixes</summary>

- [[Android] CurrentItemChangedEventArgs.PreviousItem and
PositionChangedEventArgs.PreviousPosition Not Updating Correctly When
Using ScrollTo or Setting
Position](#29544)
  </details>

- [iOS] CarouselView2: Update internal scroll indicators for
compositional layout by @SubhikshaSf4851 in
#33639
  <details>
  <summary>🔧 Fixes</summary>

- [[iOS] Horizontal Scroll Bar Not Visible on CarouselView
(CV2)](#29390)
  </details>

- [CarouselViewHandler2] Fir fox CurrentItem does not work when
ItemSpacing is set by @SyedAbdulAzeemSF4852 in
#32135
  <details>
  <summary>🔧 Fixes</summary>

- [[CarouselViewHandler2] CurrentItem does not work when ItemSpacing is
set](#32048)
  </details>

- [iOS] Fix for Incorrect Scroll in Loop Mode When CurrentItem Is Not
Found in ItemsSource by @SyedAbdulAzeemSF4852 in
#32141
  <details>
  <summary>🔧 Fixes</summary>

- [[Android & iOS] Setting an invalid CurrentItem causes scroll to last
item in looped
CarouselView](#32139)
  </details>

- [Android] IndicatorView: Add TalkBack accessibility descriptions for
indicators by @praveenkumarkarunanithi in
#31775
  <details>
  <summary>🔧 Fixes</summary>

- [[Android] IndicatorView does not convey correct accessibility
information](#31446)
  </details>

- [iOS, macOS] Fixed CollectionView KeepLastItemInView Not Updating
Correctly When Items Are Added Dynamically by @NanthiniMahalingam in
#32191
  <details>
  <summary>🔧 Fixes</summary>

- [[.NET10] I9 - Scroll_Position - "KeepLastItemInView" does not keep
the last item at the end of the displayed list when adding new
items.](#31825)
  </details>

- [Windows, Android] Resolved issue with dynamic Header/Footer
reassignment in CollectionView. by @prakashKannanSf3972 in
#28403
  <details>
  <summary>🔧 Fixes</summary>

- [[Windows, Android] Toggling Header/Footer in CollectionView
Dynamically is not working](#27959)
- [CollectionView HeaderTemplate and FooterTemplate are not displayed
when ItemsSource is initially set to
null](#28337)
- [[Android] Header and Footer Not Visible in CollectionView When
EmptyView is Selected
First](#28351)
  </details>

- [Android] Fix CollectionView inside disabled RefreshView blocks scroll
by @Vignesh-SF3580 in #34702
  <details>
  <summary>🔧 Fixes</summary>

- [C6-The C6 page cannot scroll on Windows and Android
platforms.](#34666)
  </details>

- [Android] CollectionView: Fix SelectedItem visual state not applying
when re-selecting same item by @KarthikRajaKalaimani in
#31591
  <details>
  <summary>🔧 Fixes</summary>

- [CollectionView - SelectedItem visual state manager not
working](#20062)
  </details>

- [Windows] Fixed CollectionView.EmptyView can not be removed by setting
it to Null by @Dhivya-SF4094 in
#29487
  <details>
  <summary>🔧 Fixes</summary>

- [[Windows] CollectionView.EmptyView can not be removed by setting it
to Null](#18657)
- [[Windows] EmptyViewTemplate Not Working in
CarouselView](#29463)
- [EmptyViewTemplate does not do
anything](#18551)
- [[MAUI] I5_EmptyView - The data template selector cannot display the
correct string.](#23330)
  </details>

- [iOS] Support for IsSwipeEnabled on CarouselView2 by @kubaflo in
#29996
  <details>
  <summary>🔧 Fixes</summary>

- [[iOS] IsSwipeEnabled Not Working on CarouselView
(CV2)](#29391)
  </details>

- [iOS, MacOS] Fixed FlowDirection not working on Header/Footer in
CollectionView by @Dhivya-SF4094 in
#32775
  <details>
  <summary>🔧 Fixes</summary>

- [[iOS, MacOS] FlowDirection not working on Header/Footer in
CollectionView](#32771)
  </details>

- [iOS] CollectionView: Fix drag-and-drop reordering into empty groups
by @SuthiYuvaraj in #34151
  <details>
  <summary>🔧 Fixes</summary>

- [CollectionView Drag and Drop Reordering Can't Drop in Empty
Group](#12008)
  </details>

- [Android] CollectionView: Fix drag-and-drop reordering into empty
groups by @SuthiYuvaraj in #31867
  <details>
  <summary>🔧 Fixes</summary>

- [CollectionView Drag and Drop Reordering Can't Drop in Empty
Group](#12008)
  </details>

- [iOS] Fix vertical CarouselView MandatorySingle snapping on iOS by
@Vignesh-SF3580 in #34700
  <details>
  <summary>🔧 Fixes</summary>

- [CarouselView vertical snap points ignored on iOS with
Microsoft.Maui.Controls v10.0.20 (regression from
v9.0.120)](#33308)
  </details>

- [iOS26] Fix CarouselView scrolling to wrong item when navigating to
last item by @Vignesh-SF3580 in
#34013
  <details>
  <summary>🔧 Fixes</summary>

- [[iOS 26] CarouselView does not scroll to the correct last
item](#33770)
  </details>

- Fixed the OnPlatform does not work for header property in Collection
view by @NanthiniMahalingam in #28935
  <details>
  <summary>🔧 Fixes</summary>

- [OnPlatform does not work in Header of
CollectionView](#25124)
  </details>

- [Android] [Candidate branch] Fix
VerifySelectedItemClearsOnNullAssignment,
CollectionViewSelectionShouldClear, SelectedItemVisualIsCleared UI test
failure on Android by @KarthikRajaKalaimani in
#34928

## DateTimePicker
- [iOS] Fix for DatePicker FlowDirection Not Working on iOS by
@SyedAbdulAzeemSF4852 in #30193
  <details>
  <summary>🔧 Fixes</summary>

- [[iOS] DatePicker FlowDirection Not Working on
iOS](#30065)
  </details>

## Drawing
- [Shapes] Line: Fix asymmetric Stretch.None path translation when
right/bottom edge overflows by @NirmalKumarYuvaraj in
#34385
  <details>
  <summary>🔧 Fixes</summary>

- [Line coordinates not computed
correctly](#11404)
- [Lines not drawing
correctly](#26961)
  </details>

- [Android] Fixed GraphicsView drawable is visible outside the canvas by
@NirmalKumarYuvaraj in #28353
  <details>
  <summary>🔧 Fixes</summary>

- [[Android] GraphicsView, The drawn image can also be visible outside
the canvas](#20834)
  </details>

- Fixed Custom Drawable does not support binding by @NirmalKumarYuvaraj
in #29442
  <details>
  <summary>🔧 Fixes</summary>

- [Custom IDrawable control does not databind to a model property when
used inside a CollectionView
ItemTemplate](#20991)
  </details>

- Added a support for GradientBrushes on Shape.Stroke by @kubaflo in
#22208
  <details>
  <summary>🔧 Fixes</summary>

- [GradientBrushes are not supported on
Shape.Stroke](#21983)
  </details>

## Editor
- Fixed Editor HorizontalTextAlignment does not update at run time by
@NirmalKumarYuvaraj in #25129
  <details>
  <summary>🔧 Fixes</summary>

- [Editor HorizontalTextAlignment Does not
Works.](#10987)
- [[iOS/MacOs] Right-To-Left (RTL) alignment is not applied to Editor
placeholder](#30052)
  </details>

- [Windows] Fixed Entry Editor placeholder Text CharacterSpacing by
@SubhikshaSf4851 in #30324
  <details>
  <summary>🔧 Fixes</summary>

- [[Windows] CharacterSpacing not applied to Placeholder text in Entry
and Editor controls](#30071)
  </details>

## Entry
- [Windows] Fix fo setting an Entry's Keyboard to Date causes it to be
interpreted as a password input by @SyedAbdulAzeemSF4852 in
#29344
  <details>
  <summary>🔧 Fixes</summary>

- [[Windows] Entry Keyboad-Type "Date" results in
Password-Entry](#28975)
  </details>

- [Android] Exception thrown when give more than 5000 characters to the
Text property of Entry. by @KarthikRajaKalaimani in
#30242
  <details>
  <summary>🔧 Fixes</summary>

- [Android crash when Entry has >5000
characters](#30144)
  </details>

## Essentials
- Bump MonoApiToolsMSBuildTasksPackageVersion to 0.5.0 and ship
Essentials.AI public APIs by @mattleibow via @Copilot in
#34574

- [Mac] DeviceDisplay.KeepScreenOn not being respected on Mac OS by
@HarishwaranVijayakumar in #32708
  <details>
  <summary>🔧 Fixes</summary>

- [[Mac Catalyst] DeviceDisplay.KeepScreenOn not being respected on Mac
OS](#26059)
  </details>

## Flyoutpage
- [Windows] FlyoutPage: update CollapseStyle at runtime by
@devanathan-vaithiyanathan in #29927
  <details>
  <summary>🔧 Fixes</summary>

- [Flyout Page SetCollapseStyle doesn't have any
change](#18200)
  </details>

## Gestures
- [Android] Fix for TapGestureRecognizer doesn't fire by
@HarishwaranVijayakumar in #34497
  <details>
  <summary>🔧 Fixes</summary>

- [[Android] TapGestureRecognizer doesn't
fire](#5825)
  </details>

## Image
- [Android] Fix Share.RequestAsync SecurityException on Android 10+
caused by missing ClipData by @HarishwaranVijayakumar in
#34417
  <details>
  <summary>🔧 Fixes</summary>

- [[Bug] Share.RequestAsync throws java.lang.SecurityException
(uid=1000) on Android 10+ due to missing
intent.ClipData](#34370)
  </details>

- [Windows]Fixed the MauiImage with logical name containing path issue
by @sheiksyedm in #32864
  <details>
  <summary>🔧 Fixes</summary>

- [MauiImage with LogicalName containing path - is not working on
Windows](#32356)
  </details>

- [Android, Windows & iOS] Fix Downsize/ScaleImage to maintain aspect
ratio and prevent upscaling by @SyedAbdulAzeemSF4852 in
#30808
  <details>
  <summary>🔧 Fixes</summary>

- [[Android & Windows] In GraphicsView, the aspect ratio is not
maintained when Downsize is called with both maxWidth and
maxHeight](#30803)
  </details>

## Label
- [iOS , macOS] Fixed Label text cropping when a width request is
specified on the label inside a VerticalStackLayout with specified width
request by @NanthiniMahalingam in
#29166
  <details>
  <summary>🔧 Fixes</summary>

- [Label text gets cropped when a width request is specified on the
label inside a
VerticalStackLayout](#28660)
- [[iOS] Label with a fixed WidthRequest has wrong
height](#26644)
  </details>

- [Android] Fix Label word wrapping clips text depending on alignment
and layout options by @Dhivya-SF4094 in
#34533
  <details>
  <summary>🔧 Fixes</summary>

- [Bug: Android Label word wrapping clips text depending on alignment
and layout options](#34459)
  </details>

- LineHeight and decorations for HTML Label - fix by @kubaflo in
#31202
  <details>
  <summary>🔧 Fixes</summary>

- [LineHeight with HTML Label not
working](#22193)
  - [lineheight is broken ](#22197)
  </details>

- [iOS] Fix Label with TailTruncation not rendering after
empty-to-non-empty text transition by @kubaflo in
#34812
  <details>
  <summary>🔧 Fixes</summary>

- [Label with LineBreakMode="TailTruncation" does not render text if
initial Text is null or empty on first render
(iOS)](#34591)
  </details>

## Layout
- [Android] Fix overflowing children clipped when parent Opacity < 1 by
@SyedAbdulAzeemSF4852 in #34565
  <details>
  <summary>🔧 Fixes</summary>

- [Maui Android parent view inappropriately creates clipping mask when
its opacity is less than 1, cropping out
children](#22038)
  </details>

- Fixed the FlexLayout reverse issue with the AlignContent by
@Ahamed-Ali in #32134
  <details>
  <summary>🔧 Fixes</summary>

- [FlexLayout alignment issue when Wrap is set to Reverse and
AlignContent is set to SpaceAround, SpaceBetween or
SpaceEvenly](#31565)
  </details>

- [iOS/Mac] Fixed BoxView in AbsoluteLayout did not return to its
default AutoSize for Height and Width after reset by @Dhivya-SF4094 in
#31648
  <details>
  <summary>🔧 Fixes</summary>

- [[iOS, Catalyst] BoxView in AbsoluteLayout does not return to default
AutoSize for Height/Width after
reset](#31496)
  </details>

## Map
- [Windows] Implement WinUI 3 MapControl handler using Azure Maps by
@jfversluis in #34138

## Modal
- [Android] PopToRootAsync for modal pages - improvements by @kubaflo in
#26851
  <details>
  <summary>🔧 Fixes</summary>

- [Shell PopToRootAsync doesn't happen instantly - previous pages flash
quickly. Only happens in NET
9](#26846)
  </details>

- [Android] Fix HideSoftInputOnTapped doesn't work on Modal Pages by
@HarishwaranVijayakumar in #34770
  <details>
  <summary>🔧 Fixes</summary>

- [HideSoftInputOnTapped doesn't work on Modal
Pages](#34730)
  </details>

## Navigation
- [iOS] Alert popup may be displayed on wrong window when modal page
navigation is in progress - fix by @kubaflo in
#31016
  <details>
  <summary>🔧 Fixes</summary>

- [Alert popup may be displayed on wrong window when modal page
navigation is in progress on
iOS/MacOS](#30970)
  </details>

- [Android] Page: Fix OnNavigatedTo called twice when NavigationPage is
FlyoutPage Detail by @KarthikRajaKalaimani in
#31931
  <details>
  <summary>🔧 Fixes</summary>

- [NavigationPage and FlyoutPage both call OnNavigatedTo, so it is
called twice](#23902)
  </details>

## Picker
- Fixed the Picker didn't dismiss it when tapping outside on iOS and
MacCatalyst platform. by @KarthikRajaKalaimani in
#30067
  <details>
  <summary>🔧 Fixes</summary>

- [[regression/8.0.3] iOS Picker dismiss does not work when clicking
outside of the Picker](#19168)
  </details>

- [Windows] Fixed Picker items width wont resize back by
@SubhikshaSf4851 in #33042
  <details>
  <summary>🔧 Fixes</summary>

- [Picker items width won't resize back when its container window gets
resized down.](#32984)
  </details>

## RadioButton
- Fix TalkBack not correctly narrating RadioButtons with Content by
@SubhikshaSf4851 in #34521
  <details>
  <summary>🔧 Fixes</summary>

- [[Android] TalkBack does not correctly narrate RadioButtons with
Content](#34322)
  </details>

## SafeArea
- [Android] Fix SafeAreaShouldWorkOnAllShellTabs test failure on API 36
by @praveenkumarkarunanithi in #34239

## ScrollView
- [iOS] Preserve ScrollView offsets when Orientation changes to Neither
by @Vignesh-SF3580 in #34672
  <details>
  <summary>🔧 Fixes</summary>

- [Incorrect implementation of
ScrollView.Orientation](#34583)
  </details>

## Searchbar
- [Android] Fix SearchBar text bleeding between instances after
navigation by @SyedAbdulAzeemSF4852 in
#34703
  <details>
  <summary>🔧 Fixes</summary>

- [MAUI Android: SearchBar copies content from one to the
other](#20348)
  </details>

- Fixed SearchBar CursorPosition and SelectionLength not updating when
typing by @Dhivya-SF4094 in #34347
  <details>
  <summary>🔧 Fixes</summary>

- [SearchBar - CursorPosition and SelectionLength are not updated when
the user types](#30779)
  </details>

## SearchBar
- [Windows] Fixed SearchHandler issues by @Tamilarasan-Paranthaman in
#29520
  <details>
  <summary>🔧 Fixes</summary>

- [[Windows] SearchHandler APIs are not functioning
properly](#29493)
  </details>

## Shell
- [iOS, Mac] Fix for Background set to Transparent doesn't have the same
behavior as BackgroundColor Transparent by @HarishwaranVijayakumar in
#32245
  <details>
  <summary>🔧 Fixes</summary>

- [Background set to Transparent doesn't have the same behavior as
BackgroundColor =
Transparent](#22769)
  </details>

- [iOS] Fix App crash with NullReferenceException in
ShellSectionRenderer by @devanathan-vaithiyanathan in
#32109
  <details>
  <summary>🔧 Fixes</summary>

- [[iOS] App crash with NullReferenceException in
ShellSectionRenderer](#31961)
  </details>

- [Android] Fixed back button icon selection logic in
ShellToolbarTracker by @kubaflo in
#32080
  <details>
  <summary>🔧 Fixes</summary>

- [IconOverride in Shell.BackButtonBehavior does not
work.](#32050)
  </details>

- Fix TabBarIsVisible Not Updating Dynamically When Set on ShellContent
by @Vignesh-SF3580 in #33090
  <details>
  <summary>🔧 Fixes</summary>

- [Shell.TabBarIsVisible is not updated dynamically at
runtime](#32994)
  </details>

- [iOS, macOS] Shell: Fix RTL flow direction for flyout, menu cells, tab
bar, and Locked flyout position by @NanthiniMahalingam in
#32701
  <details>
  <summary>🔧 Fixes</summary>

- [[iOS, Mac Catalyst] Shell Flyout and Content Do Not Fully Support
RightToLeft (RTL)](#32419)
  </details>

- [IOS] Inconsistent Resize Behavior for Header/Footer - fix by @kubaflo
in #28713
  <details>
  <summary>🔧 Fixes</summary>

- [[IOS, Mac] Inconsistent Resize Behavior for
Header/Footer](#26397)
- [Enable Shell Flyout Header/Footer resize tests on
iOS/Catalyst](#33501)
  </details>

- [Android] Fix for SearchHandler retaining previous page SearchView
data in pages within Shell sections by @BagavathiPerumal in
#29545
  <details>
  <summary>🔧 Fixes</summary>

- [[Shell][Android] The truth is out there...but not on top tab search
handlers](#8716)
  </details>

- [Android] Fix empty space above TabBar after navigating back when
TabBar visibility is toggled by @praveenkumarkarunanithi in
#34324
  <details>
  <summary>🔧 Fixes</summary>

- [Empty space appears above TabBar after navigating back when TabBar
visibility is toggled](#33703)
- [Grid with SafeAreaEdges=Container has incorrect size when tab bar
appears](#34256)
  </details>

## SwipeView
- [Android] SwipeView: Use MeasureSpecMode.Exactly for SwipeItem layout
to fix text visibility by @Ahamed-Ali in
#27399
  <details>
  <summary>🔧 Fixes</summary>

- [[Android] Right SwipeView items are not visible in the
SwipeView.](#27367)
  </details>

- [Android] Prevent the tap that closes an open SwipeView from being
propagated to children by @sjordanGSS in
#24275
  <details>
  <summary>🔧 Fixes</summary>

- [Tapping to close a SwipeView will activate TapGestureRecognizers on
.Content](#23921)
  </details>

## Switch
- [iOS & Mac] Fix for SearchHandler retains previous page state when
switching top tabs by @BagavathiPerumal in
#34735
  <details>
  <summary>🔧 Fixes</summary>

- [[Shell] [iOS & Mac] SearchHandler retains previous page state when
switching top tabs](#34693)
  </details>

## TabbedPage
- [Android] Fixed NullReferenceException in app with TabBar after
returning from minimized state by @NirmalKumarYuvaraj in
#34779
  <details>
  <summary>🔧 Fixes</summary>

- [NullReferenceException in app with TabBar after returning from
minimized state](#34720)
  </details>

## Titlebar
- Fixed BindingContext of the Window TitleBar is not being passed on to
its child content. by @NirmalKumarYuvaraj in
#30080
  <details>
  <summary>🔧 Fixes</summary>

- [The BindingContext of the Window TitleBar is not being passed on to
its child content.](#24831)
  </details>

- [Windows/Mac] Fix RTL FlowDirection causes overlap with native window
control buttons in TitleBar by @devanathan-vaithiyanathan in
#30400
  <details>
  <summary>🔧 Fixes</summary>

- [[Windows, Mac] RTL FlowDirection causes overlap with native window
control buttons in
TitleBar](#30399)
  </details>

## WebView
- [Windows] Fix WebView background color not being applied by
@SubhikshaSf4851 in #34599
  <details>
  <summary>🔧 Fixes</summary>

- [WebView background color has changed after update, can't
override.](#34518)
  </details>

- [Android] Fix for WebView/HybridWebView briefly flashes full screen
before layout completes by @praveenkumarkarunanithi in
#33207
  <details>
  <summary>🔧 Fixes</summary>

- [[Android] HybridWebView briefly resizes to full screen when page is
opened before snapping back to correct
size](#31475)
  </details>

## Xaml
- Improved style inheritance by @kubaflo in
#31317
  <details>
  <summary>🔧 Fixes</summary>

- [Styles based on a style that is based on another style that uses
AppThemeBinding do not inherit properties
correctly.](#31280)
  </details>

- Fix for VisualStateManager Setter.TargetName failing when
ControlTemplate is applied by @BagavathiPerumal in
#33208
  <details>
  <summary>🔧 Fixes</summary>

- [Setter.TargetName + ControlTemplate
crash](#26977)
  </details>


<details>
<summary>🧪 Testing (4)</summary>

- [Testing] Additional Feature Matrix Event Test Cases for Slider and
ScrollView by @nivetha-nagalingam in
#34352
- [Testing] Fixed Build error on inflight/ candidate PR 34885 by
@NafeelaNazhir in #34891
- [Testing] Fixed UI test image failure in PR 34885 - [13/4/2026] by
@NafeelaNazhir in #34933
- Fixed test failure - CursorPositionUpdatesWhenSearchBarGainsFocus by
@Dhivya-SF4094 in #34938

</details>

<details>
<summary>📦 Other (3)</summary>

- Fix Loaded event not called for MAUI View added to native View by
@NirmalKumarYuvaraj in #34345
  <details>
  <summary>🔧 Fixes</summary>

- [Loaded event not called for MAUI View added to native
View](#34310)
  </details>
- Add public IAlertManager and IAlertManagerSubscription interfaces by
@Redth in #34228
  <details>
  <summary>🔧 Fixes</summary>

- [Alert/Dialog system (`DisplayAlert`, `DisplayActionSheet`,
`DisplayPromptAsync`) needs a public extensibility
point](#34104)
  </details>
- Fix crash when displaying alerts on unloaded pages by @kubaflo in
#33288

</details>

<details>
<summary>📝 Issue References</summary>

Fixes #5825, Fixes #8494, Fixes #8716, Fixes #10987, Fixes #11404, Fixes
#12008, Fixes #18200, Fixes #18551, Fixes #18657, Fixes #18701, Fixes
#19168, Fixes #19209, Fixes #20062, Fixes #20348, Fixes #20834, Fixes
#20991, Fixes #21983, Fixes #22038, Fixes #22193, Fixes #22197, Fixes
#22769, Fixes #23330, Fixes #23854, Fixes #23902, Fixes #23921, Fixes
#24304, Fixes #24831, Fixes #25124, Fixes #26059, Fixes #26397, Fixes
#26644, Fixes #26846, Fixes #26961, Fixes #26977, Fixes #27086, Fixes
#27367, Fixes #27959, Fixes #28337, Fixes #28351, Fixes #28660, Fixes
#28975, Fixes #29390, Fixes #29391, Fixes #29463, Fixes #29493, Fixes
#29544, Fixes #30052, Fixes #30065, Fixes #30071, Fixes #30144, Fixes
#30399, Fixes #30779, Fixes #30803, Fixes #30970, Fixes #31280, Fixes
#31446, Fixes #31475, Fixes #31496, Fixes #31565, Fixes #31825, Fixes
#31961, Fixes #32048, Fixes #32050, Fixes #32139, Fixes #32356, Fixes
#32419, Fixes #32771, Fixes #32944, Fixes #32984, Fixes #32994, Fixes
#33308, Fixes #33501, Fixes #33703, Fixes #33770, Fixes #33773, Fixes
#34104, Fixes #34256, Fixes #34257, Fixes #34310, Fixes #34322, Fixes
#34363, Fixes #34370, Fixes #34459, Fixes #34518, Fixes #34583, Fixes
#34591, Fixes #34666, Fixes #34693, Fixes #34720, Fixes #34730

</details>

**Full Changelog**:
main...inflight/candidate
@github-actions github-actions Bot locked and limited conversation to collaborators Apr 30, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

community ✨ Community Contribution partner/syncfusion Issues / PR's with Syncfusion collaboration platform/android platform/windows s/agent-approved AI agent recommends approval - PR fix is correct and optimal s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates 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.

[Android & Windows] In GraphicsView, the aspect ratio is not maintained when Downsize is called with both maxWidth and maxHeight

8 participants