Skip to content

Implement Bitmap.Save with a format - #21455

Merged
MrJul merged 6 commits into
AvaloniaUI:masterfrom
MrJul:feature/bitmap-save-format
Jul 7, 2026
Merged

MrJul merged 6 commits into
AvaloniaUI:masterfrom
MrJul:feature/bitmap-save-format

Conversation

@MrJul

@MrJul MrJul commented May 28, 2026 •

Copy link
Copy Markdown
Member

What does the pull request do?

This PR implements new overloads for Bitmap.Save() that accept bitmap options (PNG/JPEG).

What is the current behavior?

Bitmap.Save() always saves using PNG.

What is the updated/expected behavior with this PR?

The user can pass desired options to save the bitmap in PNG and JPEG formats.

Notes

PNG, and JPEG options are exposed as those are the most commonly used. PNG options provide a compression level and JPEG options a quality.

I've kept only a single Save overload on IBitmap and IBitmapImpl (private APIs) to avoid bloating the interfaces. The public API on Bitmap has 4 overloads. The old overloads forcing PNG have been marked Obsolete.

API diff

Avalonia.Base (net10.0, net8.0)

  namespace Avalonia.Media.Imaging
  {
      public class Bitmap : IImage, IImageBrushSource
      {
+         public void Save(Stream stream, BitmapEncoderOptions options);
+         public void Save(string fileName, BitmapEncoderOptions options);
      }
+     public abstract class BitmapEncoderOptions
+     {
+     }
+     public sealed class JpegBitmapEncoderOptions : BitmapEncoderOptions
+     {
+         public JpegBitmapEncoderOptions();
+         public static JpegBitmapEncoderOptions Default { get; }
+         public int Quality { get; init; }
+     }
+     public sealed class PngBitmapEncoderOptions : BitmapEncoderOptions
+     {
+         public PngBitmapEncoderOptions();
+         public Compression.CompressionLevel CompressionLevel { get; init; }
+         public static PngBitmapEncoderOptions Default { get; }
+     }
  }
  namespace Avalonia.Platform
  {
      public interface IBitmapImpl
      {
-         void Save(Stream stream, int? quality = null);
-         void Save(string fileName, int? quality = null);
+         void Save(Stream stream, BitmapEncoderOptions options);
      }
  }

Avalonia.Skia (net10.0, net8.0)

  namespace Avalonia.Skia.Helpers
  {
      public static class ImageSavingHelper
      {
+         public static void SaveImage(SKImage image, Stream stream, BitmapEncoderOptions options);
+         public static void SaveImage(SKImage image, string fileName, BitmapEncoderOptions options);
      }
  }

@MrJul MrJul added feature customer-priority Issue reported by a customer with a support agreement. api-needs-review The PR adds new public APIs that should be reviewed. labels May 28, 2026
@avaloniaui-bot

Copy link
Copy Markdown

You can test this PR using the following package version. 12.1.999-cibuild0065887-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

@Gillibald

Gillibald commented May 29, 2026 •

Copy link
Copy Markdown
Contributor

I find it confusing to have a quality parameter that only works with JPEG, in my opinion. BitmapEncoderOptions or similar would be the better solution here. Plus, this will still work in the future when we have our own encoder/decoder infrastructure.

@timunie

timunie commented May 29, 2026

Copy link
Copy Markdown
Collaborator

I also vote for EncoderOptions. The base class could could have static fields for PNG, BMP, JPG...

@robloo

robloo commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Note that WPF goes an even more powerful route: https://learn.microsoft.com/en-us/dotnet/desktop/wpf/graphics-multimedia/how-to-encode-a-visual-to-an-image-file

There are specific encoders for bitmap formats. This keeps the options and format logic all self-contained.

IMO we should follow the WPF API entirely here... at least long-term. It is better designed IMO.

@MrJul

MrJul commented Jun 3, 2026

Copy link
Copy Markdown
Member Author

I find it confusing to have a quality parameter that only works with JPEG

Note that, counter-intuitively, it works with PNG, in fact it was specifically added for it in #9106. Skia uses it as a reverse compression level. 100 = fastest, lower that number to try more compression algorithms (slower).

@MrJul

MrJul commented Jun 3, 2026

Copy link
Copy Markdown
Member Author

This keeps the options and format logic all self-contained.

The goal of this PR is not to introduce our own encoders; we're still completely dependent on the underlying backend (Skia) here. Having our own encoders is probably something that should happen in the future, but it isn't the goal here.

That said, the consensus seems to be to at least have our own options class per format, which could be reused in an encoder infrastructure later. I'll make the changes.

@MrJul
MrJul marked this pull request as draft June 3, 2026 07:56
@MrJul
MrJul force-pushed the feature/bitmap-save-format branch from a3abc65 to 25fcb7d Compare June 10, 2026 15:10
@MrJul
MrJul marked this pull request as ready for review June 10, 2026 15:15
@MrJul

MrJul commented Jun 10, 2026

Copy link
Copy Markdown
Member Author

I've rewritten the PR with proper BitmapEncoderOptions classes. The OP has been updated with the new design.

@robloo

robloo commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

The goal of this PR is not to introduce our own encoders; we're still completely dependent on the underlying backend (Skia) here. Having our own encoders is probably something that should happen in the future, but it isn't the goal here.

Note that you could still follow the WPF API shape and implement it as a wrapper around the SKIA backend. I never said we should write our own encoders -- just expose the encoder + options together like WPF does. This future proofs the design and follows existing conventions in the XAML space.

Comment thread src/Avalonia.Base/Media/Imaging/BitmapEncoderOptions.cs
@avaloniaui-bot

Copy link
Copy Markdown

You can test this PR using the following package version. 12.1.999-cibuild0066227-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

Comment on lines +9 to +14
public abstract class BitmapEncoderOptions
{
internal BitmapEncoderOptions()
{
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Alternative API I can think of is:

public abstract class BitmapEncoderOptions
{
     public static BitmapEncoderOptions Jpeg { get; }
     public static BitmapEncoderOptions Png { get; }

     public static BitmapEncoderOptions JpegWithQuality(int quality);
     public static BitmapEncoderOptions PngWithCompression(CompressionLevel compressionLevel);
}

Specific implementation might or might not remain public.
I don't think this is a better API, but I previously thought of a similar one, and we can discuss it later.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I don't really like the methods: it means that to add new options, we would need new overloads. This can become a mess after several iterations (since we can't add parameters, it's a breaking change), whereas with the types we can just add new properties as needed.

maxkatz6
maxkatz6 previously approved these changes Jun 14, 2026

@maxkatz6 maxkatz6 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Implementation is LGTM

MrJul added 2 commits July 3, 2026 09:42
# Conflicts:
#	api/Avalonia.nupkg.xml
#	src/Avalonia.X11/Clipboard/X11Clipboard.cs
@MrJul MrJul added api-approved The new public APIs have been approved. and removed api-needs-review The PR adds new public APIs that should be reviewed. labels Jul 3, 2026
@avaloniaui-bot

Copy link
Copy Markdown

You can test this PR using the following package version. 12.1.999-cibuild0067115-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

# Conflicts:
#	api/Avalonia.nupkg.xml
@avaloniaui-bot

Copy link
Copy Markdown

You can test this PR using the following package version. 12.1.999-cibuild0067267-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

@MrJul
MrJul merged commit 6ef2d0b into AvaloniaUI:master Jul 7, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api-approved The new public APIs have been approved. customer-priority Issue reported by a customer with a support agreement. feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants