Skip to content

Conversation

jonathanpeppers
Copy link
Member

Context: https://github.com/davidortinau/AllTheLists

Profiling @davidortinau's app, I noticed the "Check-ins" sample felt the slowest on Android.

One thing I noticed while scrolling:

98.75ms (0.90%) Microsoft.Maui!Microsoft.Maui.Handlers.ImageHandler.PlatformArrange(Microsoft.Maui.Graphics.Rect)
67.11ms (0.61%) Mono.Android!Android.Widget.ImageView.ScaleType.get_CenterCrop()

In this case, PlatformArrange() is called a lot for every <Image/>:

if (PlatformView.GetScaleType() == ImageView.ScaleType.CenterCrop)
{
    var (left, top, right, bottom) = PlatformView.Context!.ToPixels(frame);
    var clipRect = new Android.Graphics.Rect(0, 0, right - left, bottom - top);
    PlatformView.ClipBounds = clipRect;
}

ImageView.ScaleType is a class, and so and some bookkeeping is done to lookup the same C# instance for a Java object. We can make this a bit better by writing a new Java method:

public static boolean isImageViewCenterCrop(@NonNull ImageView imageView) {
    return imageView.getScaleType() == ImageView.ScaleType.CENTER_CROP;
}

Next, let's make a PlatformView.ToPixels() extension method that can avoid calling View.Context for the same reason.

Lastly, we can make a PlatformInterop.SetClipBounds() method to avoid creating a Android.Graphics.Rect object in C#.

With these changes, I can only see the topmost PlatformArrange() method now:

2.93ms (0.03%) Microsoft.Maui!Microsoft.Maui.Handlers.ImageHandler.PlatformArrange(Microsoft.Maui.Graphics.Rect)

This should improve the layout performance of all .NET MAUI <Image/> on Android. I also "banned" GetScaleType() in eng/BannedSymbols.txt.

@jonathanpeppers jonathanpeppers added platform/android perf/general The issue affects performance (runtime speed, memory usage, startup time, etc.) (sub: perf) labels Jul 17, 2024
@jonathanpeppers

This comment was marked as outdated.

@Eilon Eilon added the area-controls-image Image control label Jul 19, 2024
@jonathanpeppers

This comment was marked as outdated.

@jonathanpeppers jonathanpeppers force-pushed the ImageView.GetScaleType branch from 8dcd951 to d52ca17 Compare July 31, 2024 16:28
@bcaceiro
Copy link

Is this going into next SR? Seems like a great adition!

@bcaceiro
Copy link

Any news on this?

@bcaceiro
Copy link

Sorry to bump this, but feels like this is an important MR to improve performance overall in a basic element like Image in all applications

Context: https://github.com/davidortinau/AllTheLists

Profiling @davidortinau's app, I noticed the "Check-ins" sample felt
the slowest on Android.

One thing I noticed while scrolling:

    98.75ms (0.90%) Microsoft.Maui!Microsoft.Maui.Handlers.ImageHandler.PlatformArrange(Microsoft.Maui.Graphics.Rect)
    67.11ms (0.61%) Mono.Android!Android.Widget.ImageView.ScaleType.get_CenterCrop()

In this case, `PlatformArrange()` is called a lot for every `<Image/>`:

    if (PlatformView.GetScaleType() == ImageView.ScaleType.CenterCrop)
    {
        var (left, top, right, bottom) = PlatformView.Context!.ToPixels(frame);
        var clipRect = new Android.Graphics.Rect(0, 0, right - left, bottom - top);
        PlatformView.ClipBounds = clipRect;
    }

`ImageView.ScaleType` is a class, and so and some bookkeeping is done
to lookup *the same* C# instance for a Java object. We can make this a
bit better by writing a new Java method:

    public static boolean isImageViewCenterCrop(@nonnull ImageView imageView) {
        return imageView.getScaleType() == ImageView.ScaleType.CENTER_CROP;
    }

Next, let's make a `PlatformView.ToPixels()` extension method that can
avoid calling `View.Context` for the same reason.

Lastly, we can make a `PlatformInterop.SetClipBounds()` method to avoid
creating a `Android.Graphics.Rect` object in C#.

With these changes, I can only see the topmost `PlatformArrange()`
method now:

    2.93ms (0.03%) Microsoft.Maui!Microsoft.Maui.Handlers.ImageHandler.PlatformArrange(Microsoft.Maui.Graphics.Rect)

This should improve the layout performance of all .NET MAUI `<Image/>`
on Android. I also "banned" `GetScaleType()` in `eng/BannedSymbols.txt`.
@jonathanpeppers jonathanpeppers force-pushed the ImageView.GetScaleType branch from d52ca17 to 0ed8055 Compare August 7, 2025 13:22
@jonathanpeppers
Copy link
Member Author

@bcaceiro I originally held off on this because it could be fixed here:

We'll try to merge it now, as those changes didn't land.

@jonathanpeppers jonathanpeppers marked this pull request as ready for review August 7, 2025 15:59
@Copilot Copilot AI review requested due to automatic review settings August 7, 2025 15:59
@jonathanpeppers jonathanpeppers requested a review from a team as a code owner August 7, 2025 15:59
Copy link
Contributor

@Copilot 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 PR improves the performance of ImageHandler.PlatformArrange() on Android by optimizing several expensive operations during image layout. The changes focus on reducing object allocations and method call overhead during frequent layout operations.

Key changes:

  • Replaces expensive GetScaleType() calls with a native Java method that avoids C# object creation
  • Introduces extension methods to avoid unnecessary View.Context property access
  • Adds native Java methods to set clip bounds without creating Android.Graphics.Rect objects in C#

Reviewed Changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/Core/src/Handlers/Image/ImageHandler.Android.cs Updates PlatformArrange() to use new optimized methods for scale type checking and clip bounds setting
src/Core/AndroidNative/maui/src/main/java/com/microsoft/maui/PlatformInterop.java Adds native Java methods isImageViewCenterCrop() and setClipBounds() to avoid object allocation overhead
src/Core/src/Platform/Android/ContextExtensions.cs Adds ToPixels() extension method for View to avoid accessing Context property
src/Compatibility/Core/src/Android/FastRenderers/ImageElementManager.cs Updates compatibility layer to use new optimized scale type checking method
eng/BannedSymbols.txt Bans GetScaleType() method to prevent future performance regressions
src/Core/tests/DeviceTests/Core.DeviceTests.csproj Adds IsTestProject property for proper test project identification
src/Controls/tests/DeviceTests/Controls.DeviceTests.csproj Adds IsTestProject property for proper test project identification

@@ -122,6 +122,17 @@ static float ToPixelsUsingMetrics(double dp)
return (float)Math.Ceiling((dp * s_displayDensity) - GeometryUtil.Epsilon);
}

Copy link
Preview

Copilot AI Aug 7, 2025

Choose a reason for hiding this comment

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

The new ToPixels extension method for View lacks XML documentation. Consider adding documentation explaining its purpose and how it differs from the Context.ToPixels method.

Suggested change
/// <summary>
/// Converts a <see cref="Graphics.Rect"/> from device-independent pixels (DP) to physical pixels using the display metrics associated with the specified <see cref="View"/>.
/// This differs from <see cref="Context.ToPixels(Graphics.Rect)"/> in that it uses the metrics of the <see cref="View"/>, which may be different from those of the <see cref="Context"/>.
/// </summary>
/// <param name="view">The <see cref="View"/> whose display metrics are used for conversion.</param>
/// <param name="rectangle">The rectangle in device-independent pixels (DP) to convert.</param>
/// <returns>
/// A tuple containing the left, top, right, and bottom coordinates of the rectangle in physical pixels.
/// </returns>

Copilot uses AI. Check for mistakes.

Copy link
Member Author

Choose a reason for hiding this comment

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

It's internal, so I didn't document it yet. Its purpose is to avoid calling View.Context in the first place.

@PureWeen PureWeen added this to the .NET 9 SR11 milestone Aug 7, 2025
@PureWeen PureWeen moved this from Todo to Ready To Review in MAUI SDK Ongoing Aug 7, 2025
@github-project-automation github-project-automation bot moved this from Ready To Review to Approved in MAUI SDK Ongoing Aug 7, 2025
@PureWeen PureWeen changed the base branch from main to inflight/current August 7, 2025 18:22
@PureWeen PureWeen merged commit 9b2f71f into dotnet:inflight/current Aug 7, 2025
127 of 130 checks passed
@github-project-automation github-project-automation bot moved this from Approved to Done in MAUI SDK Ongoing Aug 7, 2025
PureWeen pushed a commit that referenced this pull request Aug 8, 2025
…3665)

Context: https://github.com/davidortinau/AllTheLists

Profiling @davidortinau's app, I noticed the "Check-ins" sample felt
the slowest on Android.

One thing I noticed while scrolling:

    98.75ms (0.90%) Microsoft.Maui!Microsoft.Maui.Handlers.ImageHandler.PlatformArrange(Microsoft.Maui.Graphics.Rect)
    67.11ms (0.61%) Mono.Android!Android.Widget.ImageView.ScaleType.get_CenterCrop()

In this case, `PlatformArrange()` is called a lot for every `<Image/>`:

    if (PlatformView.GetScaleType() == ImageView.ScaleType.CenterCrop)
    {
        var (left, top, right, bottom) = PlatformView.Context!.ToPixels(frame);
        var clipRect = new Android.Graphics.Rect(0, 0, right - left, bottom - top);
        PlatformView.ClipBounds = clipRect;
    }

`ImageView.ScaleType` is a class, and so and some bookkeeping is done
to lookup *the same* C# instance for a Java object. We can make this a
bit better by writing a new Java method:

    public static boolean isImageViewCenterCrop(@nonnull ImageView imageView) {
        return imageView.getScaleType() == ImageView.ScaleType.CENTER_CROP;
    }

Next, let's make a `PlatformView.ToPixels()` extension method that can
avoid calling `View.Context` for the same reason.

Lastly, we can make a `PlatformInterop.SetClipBounds()` method to avoid
creating a `Android.Graphics.Rect` object in C#.

With these changes, I can only see the topmost `PlatformArrange()`
method now:

    2.93ms (0.03%) Microsoft.Maui!Microsoft.Maui.Handlers.ImageHandler.PlatformArrange(Microsoft.Maui.Graphics.Rect)

This should improve the layout performance of all .NET MAUI `<Image/>`
on Android. I also "banned" `GetScaleType()` in `eng/BannedSymbols.txt`.
github-actions bot pushed a commit that referenced this pull request Aug 11, 2025
…3665)

Context: https://github.com/davidortinau/AllTheLists

Profiling @davidortinau's app, I noticed the "Check-ins" sample felt
the slowest on Android.

One thing I noticed while scrolling:

    98.75ms (0.90%) Microsoft.Maui!Microsoft.Maui.Handlers.ImageHandler.PlatformArrange(Microsoft.Maui.Graphics.Rect)
    67.11ms (0.61%) Mono.Android!Android.Widget.ImageView.ScaleType.get_CenterCrop()

In this case, `PlatformArrange()` is called a lot for every `<Image/>`:

    if (PlatformView.GetScaleType() == ImageView.ScaleType.CenterCrop)
    {
        var (left, top, right, bottom) = PlatformView.Context!.ToPixels(frame);
        var clipRect = new Android.Graphics.Rect(0, 0, right - left, bottom - top);
        PlatformView.ClipBounds = clipRect;
    }

`ImageView.ScaleType` is a class, and so and some bookkeeping is done
to lookup *the same* C# instance for a Java object. We can make this a
bit better by writing a new Java method:

    public static boolean isImageViewCenterCrop(@nonnull ImageView imageView) {
        return imageView.getScaleType() == ImageView.ScaleType.CENTER_CROP;
    }

Next, let's make a `PlatformView.ToPixels()` extension method that can
avoid calling `View.Context` for the same reason.

Lastly, we can make a `PlatformInterop.SetClipBounds()` method to avoid
creating a `Android.Graphics.Rect` object in C#.

With these changes, I can only see the topmost `PlatformArrange()`
method now:

    2.93ms (0.03%) Microsoft.Maui!Microsoft.Maui.Handlers.ImageHandler.PlatformArrange(Microsoft.Maui.Graphics.Rect)

This should improve the layout performance of all .NET MAUI `<Image/>`
on Android. I also "banned" `GetScaleType()` in `eng/BannedSymbols.txt`.
github-actions bot pushed a commit that referenced this pull request Aug 15, 2025
…3665)

Context: https://github.com/davidortinau/AllTheLists

Profiling @davidortinau's app, I noticed the "Check-ins" sample felt
the slowest on Android.

One thing I noticed while scrolling:

    98.75ms (0.90%) Microsoft.Maui!Microsoft.Maui.Handlers.ImageHandler.PlatformArrange(Microsoft.Maui.Graphics.Rect)
    67.11ms (0.61%) Mono.Android!Android.Widget.ImageView.ScaleType.get_CenterCrop()

In this case, `PlatformArrange()` is called a lot for every `<Image/>`:

    if (PlatformView.GetScaleType() == ImageView.ScaleType.CenterCrop)
    {
        var (left, top, right, bottom) = PlatformView.Context!.ToPixels(frame);
        var clipRect = new Android.Graphics.Rect(0, 0, right - left, bottom - top);
        PlatformView.ClipBounds = clipRect;
    }

`ImageView.ScaleType` is a class, and so and some bookkeeping is done
to lookup *the same* C# instance for a Java object. We can make this a
bit better by writing a new Java method:

    public static boolean isImageViewCenterCrop(@nonnull ImageView imageView) {
        return imageView.getScaleType() == ImageView.ScaleType.CENTER_CROP;
    }

Next, let's make a `PlatformView.ToPixels()` extension method that can
avoid calling `View.Context` for the same reason.

Lastly, we can make a `PlatformInterop.SetClipBounds()` method to avoid
creating a `Android.Graphics.Rect` object in C#.

With these changes, I can only see the topmost `PlatformArrange()`
method now:

    2.93ms (0.03%) Microsoft.Maui!Microsoft.Maui.Handlers.ImageHandler.PlatformArrange(Microsoft.Maui.Graphics.Rect)

This should improve the layout performance of all .NET MAUI `<Image/>`
on Android. I also "banned" `GetScaleType()` in `eng/BannedSymbols.txt`.
github-actions bot pushed a commit that referenced this pull request Aug 15, 2025
…3665)

Context: https://github.com/davidortinau/AllTheLists

Profiling @davidortinau's app, I noticed the "Check-ins" sample felt
the slowest on Android.

One thing I noticed while scrolling:

    98.75ms (0.90%) Microsoft.Maui!Microsoft.Maui.Handlers.ImageHandler.PlatformArrange(Microsoft.Maui.Graphics.Rect)
    67.11ms (0.61%) Mono.Android!Android.Widget.ImageView.ScaleType.get_CenterCrop()

In this case, `PlatformArrange()` is called a lot for every `<Image/>`:

    if (PlatformView.GetScaleType() == ImageView.ScaleType.CenterCrop)
    {
        var (left, top, right, bottom) = PlatformView.Context!.ToPixels(frame);
        var clipRect = new Android.Graphics.Rect(0, 0, right - left, bottom - top);
        PlatformView.ClipBounds = clipRect;
    }

`ImageView.ScaleType` is a class, and so and some bookkeeping is done
to lookup *the same* C# instance for a Java object. We can make this a
bit better by writing a new Java method:

    public static boolean isImageViewCenterCrop(@nonnull ImageView imageView) {
        return imageView.getScaleType() == ImageView.ScaleType.CENTER_CROP;
    }

Next, let's make a `PlatformView.ToPixels()` extension method that can
avoid calling `View.Context` for the same reason.

Lastly, we can make a `PlatformInterop.SetClipBounds()` method to avoid
creating a `Android.Graphics.Rect` object in C#.

With these changes, I can only see the topmost `PlatformArrange()`
method now:

    2.93ms (0.03%) Microsoft.Maui!Microsoft.Maui.Handlers.ImageHandler.PlatformArrange(Microsoft.Maui.Graphics.Rect)

This should improve the layout performance of all .NET MAUI `<Image/>`
on Android. I also "banned" `GetScaleType()` in `eng/BannedSymbols.txt`.
github-actions bot pushed a commit that referenced this pull request Aug 19, 2025
…3665)

Context: https://github.com/davidortinau/AllTheLists

Profiling @davidortinau's app, I noticed the "Check-ins" sample felt
the slowest on Android.

One thing I noticed while scrolling:

    98.75ms (0.90%) Microsoft.Maui!Microsoft.Maui.Handlers.ImageHandler.PlatformArrange(Microsoft.Maui.Graphics.Rect)
    67.11ms (0.61%) Mono.Android!Android.Widget.ImageView.ScaleType.get_CenterCrop()

In this case, `PlatformArrange()` is called a lot for every `<Image/>`:

    if (PlatformView.GetScaleType() == ImageView.ScaleType.CenterCrop)
    {
        var (left, top, right, bottom) = PlatformView.Context!.ToPixels(frame);
        var clipRect = new Android.Graphics.Rect(0, 0, right - left, bottom - top);
        PlatformView.ClipBounds = clipRect;
    }

`ImageView.ScaleType` is a class, and so and some bookkeeping is done
to lookup *the same* C# instance for a Java object. We can make this a
bit better by writing a new Java method:

    public static boolean isImageViewCenterCrop(@nonnull ImageView imageView) {
        return imageView.getScaleType() == ImageView.ScaleType.CENTER_CROP;
    }

Next, let's make a `PlatformView.ToPixels()` extension method that can
avoid calling `View.Context` for the same reason.

Lastly, we can make a `PlatformInterop.SetClipBounds()` method to avoid
creating a `Android.Graphics.Rect` object in C#.

With these changes, I can only see the topmost `PlatformArrange()`
method now:

    2.93ms (0.03%) Microsoft.Maui!Microsoft.Maui.Handlers.ImageHandler.PlatformArrange(Microsoft.Maui.Graphics.Rect)

This should improve the layout performance of all .NET MAUI `<Image/>`
on Android. I also "banned" `GetScaleType()` in `eng/BannedSymbols.txt`.
github-actions bot pushed a commit that referenced this pull request Aug 22, 2025
…3665)

Context: https://github.com/davidortinau/AllTheLists

Profiling @davidortinau's app, I noticed the "Check-ins" sample felt
the slowest on Android.

One thing I noticed while scrolling:

    98.75ms (0.90%) Microsoft.Maui!Microsoft.Maui.Handlers.ImageHandler.PlatformArrange(Microsoft.Maui.Graphics.Rect)
    67.11ms (0.61%) Mono.Android!Android.Widget.ImageView.ScaleType.get_CenterCrop()

In this case, `PlatformArrange()` is called a lot for every `<Image/>`:

    if (PlatformView.GetScaleType() == ImageView.ScaleType.CenterCrop)
    {
        var (left, top, right, bottom) = PlatformView.Context!.ToPixels(frame);
        var clipRect = new Android.Graphics.Rect(0, 0, right - left, bottom - top);
        PlatformView.ClipBounds = clipRect;
    }

`ImageView.ScaleType` is a class, and so and some bookkeeping is done
to lookup *the same* C# instance for a Java object. We can make this a
bit better by writing a new Java method:

    public static boolean isImageViewCenterCrop(@nonnull ImageView imageView) {
        return imageView.getScaleType() == ImageView.ScaleType.CENTER_CROP;
    }

Next, let's make a `PlatformView.ToPixels()` extension method that can
avoid calling `View.Context` for the same reason.

Lastly, we can make a `PlatformInterop.SetClipBounds()` method to avoid
creating a `Android.Graphics.Rect` object in C#.

With these changes, I can only see the topmost `PlatformArrange()`
method now:

    2.93ms (0.03%) Microsoft.Maui!Microsoft.Maui.Handlers.ImageHandler.PlatformArrange(Microsoft.Maui.Graphics.Rect)

This should improve the layout performance of all .NET MAUI `<Image/>`
on Android. I also "banned" `GetScaleType()` in `eng/BannedSymbols.txt`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-controls-image Image control perf/general The issue affects performance (runtime speed, memory usage, startup time, etc.) (sub: perf) platform/android
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

4 participants