Skip to content

Commit 6ef2d0b

Browse files
authored
Implement Bitmap.Save with a format (#21455)
* Implement Bitmap.Save with format options * Rename BitmapEncoderOptions.cs to match its type * Update API suppressions
1 parent a0e17b6 commit 6ef2d0b

31 files changed

Lines changed: 376 additions & 148 deletions

File tree

api/Avalonia.nupkg.xml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,18 @@
1919
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
2020
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
2121
</Suppression>
22+
<Suppression>
23+
<DiagnosticId>CP0002</DiagnosticId>
24+
<Target>M:Avalonia.Platform.IBitmapImpl.Save(System.IO.Stream,System.Nullable{System.Int32})</Target>
25+
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
26+
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
27+
</Suppression>
28+
<Suppression>
29+
<DiagnosticId>CP0002</DiagnosticId>
30+
<Target>M:Avalonia.Platform.IBitmapImpl.Save(System.String,System.Nullable{System.Int32})</Target>
31+
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
32+
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
33+
</Suppression>
2234
<Suppression>
2335
<DiagnosticId>CP0002</DiagnosticId>
2436
<Target>M:Avalonia.Platform.IRenderTarget.RenderTargetSceneInfo.#ctor(Avalonia.PixelSize,System.Double,Avalonia.Size)</Target>
@@ -79,6 +91,18 @@
7991
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
8092
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
8193
</Suppression>
94+
<Suppression>
95+
<DiagnosticId>CP0002</DiagnosticId>
96+
<Target>M:Avalonia.Platform.IBitmapImpl.Save(System.IO.Stream,System.Nullable{System.Int32})</Target>
97+
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
98+
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
99+
</Suppression>
100+
<Suppression>
101+
<DiagnosticId>CP0002</DiagnosticId>
102+
<Target>M:Avalonia.Platform.IBitmapImpl.Save(System.String,System.Nullable{System.Int32})</Target>
103+
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
104+
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
105+
</Suppression>
82106
<Suppression>
83107
<DiagnosticId>CP0002</DiagnosticId>
84108
<Target>M:Avalonia.Platform.IRenderTarget.RenderTargetSceneInfo.#ctor(Avalonia.PixelSize,System.Double,Avalonia.Size)</Target>
@@ -121,6 +145,12 @@
121145
<Left>baseline/Avalonia/lib/net8.0/Avalonia.OpenGL.dll</Left>
122146
<Right>current/Avalonia/lib/net8.0/Avalonia.OpenGL.dll</Right>
123147
</Suppression>
148+
<Suppression>
149+
<DiagnosticId>CP0006</DiagnosticId>
150+
<Target>M:Avalonia.Platform.IBitmapImpl.Save(System.IO.Stream,Avalonia.Media.Imaging.BitmapEncoderOptions)</Target>
151+
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
152+
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
153+
</Suppression>
124154
<Suppression>
125155
<DiagnosticId>CP0006</DiagnosticId>
126156
<Target>M:Avalonia.Platform.Storage.IStorageProvider.OpenFilePickerWithResultAsync(Avalonia.Platform.Storage.FilePickerOpenOptions)</Target>
@@ -139,6 +169,12 @@
139169
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
140170
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
141171
</Suppression>
172+
<Suppression>
173+
<DiagnosticId>CP0006</DiagnosticId>
174+
<Target>M:Avalonia.Platform.IBitmapImpl.Save(System.IO.Stream,Avalonia.Media.Imaging.BitmapEncoderOptions)</Target>
175+
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
176+
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
177+
</Suppression>
142178
<Suppression>
143179
<DiagnosticId>CP0006</DiagnosticId>
144180
<Target>M:Avalonia.Platform.Storage.IStorageProvider.OpenFilePickerWithResultAsync(Avalonia.Platform.Storage.FilePickerOpenOptions)</Target>

samples/GpuInterop/VulkanDemo/VulkanImage.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Linq;
55
using System.Runtime.InteropServices;
66
using Avalonia;
7+
using Avalonia.Media.Imaging;
78
using Avalonia.Platform;
89
using Avalonia.Vulkan;
910
using Silk.NET.Core.Native;
@@ -355,7 +356,7 @@ public void SaveTexture(string path)
355356
using var b = new Avalonia.Media.Imaging.Bitmap(PixelFormat.Bgra8888,
356357
AlphaFormat.Premul, pSurface, new PixelSize(w, h),
357358
new Vector(96, 96), (int)sstride);
358-
b.Save(path);
359+
b.Save(path, PngBitmapEncoderOptions.Default);
359360

360361
NativeMethods.IOSurfaceUnlock(surf, 0, IntPtr.Zero);
361362
return;

src/Android/Avalonia.Android/PlatformIconLoader.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.IO;
2+
using Avalonia.Media.Imaging;
23
using Avalonia.Platform;
34

45
namespace Avalonia.Android
@@ -9,7 +10,7 @@ public IWindowIconImpl LoadIcon(IBitmapImpl bitmap)
910
{
1011
using (var stream = new MemoryStream())
1112
{
12-
bitmap.Save(stream);
13+
bitmap.Save(stream, PngBitmapEncoderOptions.Default);
1314
return LoadIcon(stream);
1415
}
1516
}

src/Android/Avalonia.Android/Stubs.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.IO;
3+
using Avalonia.Media.Imaging;
34
using Avalonia.Platform;
45

56
namespace Avalonia.Android
@@ -22,7 +23,7 @@ public IWindowIconImpl LoadIcon(IBitmapImpl bitmap)
2223
{
2324
using (var stream = new MemoryStream())
2425
{
25-
bitmap.Save(stream);
26+
bitmap.Save(stream, PngBitmapEncoderOptions.Default);
2627
return LoadIcon(stream);
2728
}
2829
}

src/Avalonia.Base/Media/Imaging/Bitmap.cs

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -147,31 +147,52 @@ public Bitmap(PixelFormat format, AlphaFormat alphaFormat, IntPtr data, PixelSiz
147147
IRef<IBitmapImpl> IBitmap.PlatformImpl => PlatformImpl;
148148

149149
/// <summary>
150-
/// Saves the bitmap to a file.
150+
/// Saves the bitmap to a file, in PNG format.
151151
/// </summary>
152152
/// <param name="fileName">The filename.</param>
153153
/// <param name="quality">
154154
/// The optional quality for compression.
155155
/// The quality value is interpreted from 0 - 100. If quality is null the default quality
156156
/// setting is applied.
157157
/// </param>
158+
[Obsolete($"Use the overload accepting {nameof(BitmapEncoderOptions)} instead.")]
158159
public void Save(string fileName, int? quality = null)
160+
=> Save(fileName, PngBitmapEncoderOptions.Default);
161+
162+
/// <summary>
163+
/// Saves the bitmap to a file with the specified options.
164+
/// </summary>
165+
/// <param name="fileName">The filename.</param>
166+
/// <param name="options">
167+
/// The options specifying the format and settings to use.
168+
/// Typical usages include <see cref="PngBitmapEncoderOptions"/> and <see cref="JpegBitmapEncoderOptions"/>.
169+
/// </param>
170+
public void Save(string fileName, BitmapEncoderOptions options)
159171
{
160-
PlatformImpl.Item.Save(fileName, quality);
172+
using var stream = File.Create(fileName);
173+
174+
Save(stream, options);
161175
}
162176

163177
/// <summary>
164-
/// Saves the bitmap to a stream.
178+
/// Saves the bitmap to a stream, in PNG format.
165179
/// </summary>
166180
/// <param name="stream">The stream.</param>
167181
/// <param name="quality">
168-
/// The optional quality for compression.
169-
/// The quality value is interpreted from 0 - 100. If quality is null the default quality
182+
/// The optional quality for compression.
183+
/// The quality value is interpreted from 0 - 100. If quality is null the default quality
170184
/// setting is applied.
171185
/// </param>
186+
[Obsolete($"Use the overload accepting {nameof(BitmapEncoderOptions)} instead.")]
172187
public void Save(Stream stream, int? quality = null)
173188
{
174-
PlatformImpl.Item.Save(stream, quality);
189+
PlatformImpl.Item.Save(stream, PngBitmapEncoderOptions.Default);
190+
}
191+
192+
/// <inheritdoc />
193+
public void Save(Stream stream, BitmapEncoderOptions options)
194+
{
195+
PlatformImpl.Item.Save(stream, options);
175196
}
176197

177198
public virtual PixelFormat? Format => (PlatformImpl.Item as IReadableBitmapImpl)?.Format;
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using System.IO;
2+
3+
namespace Avalonia.Media.Imaging;
4+
5+
/// <summary>
6+
/// Represents the options used while saving a bitmap using <see cref="Bitmap.Save(Stream, BitmapEncoderOptions)"/>.
7+
/// Common implementations are <see cref="PngBitmapEncoderOptions"/>, <see cref="JpegBitmapEncoderOptions"/>.
8+
/// </summary>
9+
public abstract class BitmapEncoderOptions
10+
{
11+
internal BitmapEncoderOptions()
12+
{
13+
}
14+
}

src/Avalonia.Base/Media/Imaging/IBitmap.cs

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -32,25 +32,13 @@ internal interface IBitmap : IImage, IDisposable
3232
IRef<IBitmapImpl> PlatformImpl { get; }
3333

3434
/// <summary>
35-
/// Saves the bitmap to a file.
36-
/// </summary>
37-
/// <param name="fileName">The filename.</param>
38-
/// <param name="quality">
39-
/// The optional quality for compression if supported by the specific backend.
40-
/// The quality value is interpreted from 0 - 100. If quality is null the default quality
41-
/// setting of the backend is applied.
42-
/// </param>
43-
void Save(string fileName, int? quality = null);
44-
45-
/// <summary>
46-
/// Saves the bitmap to a stream in png format.
35+
/// Saves the bitmap to a stream with the specified options.
4736
/// </summary>
4837
/// <param name="stream">The stream.</param>
49-
/// <param name="quality">
50-
/// The optional quality for compression if supported by the specific backend.
51-
/// The quality value is interpreted from 0 - 100. If quality is null the default quality
52-
/// setting of the backend is applied.
38+
/// <param name="options">
39+
/// The options specifying the format and settings to use.
40+
/// Typical usages include <see cref="PngBitmapEncoderOptions"/> and <see cref="JpegBitmapEncoderOptions"/>.
5341
/// </param>
54-
void Save(Stream stream, int? quality = null);
42+
void Save(Stream stream, BitmapEncoderOptions options);
5543
}
5644
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
namespace Avalonia.Media.Imaging;
2+
3+
/// <summary>
4+
/// Represents the options used to save a bitmap in the JPEG format.
5+
/// </summary>
6+
public sealed class JpegBitmapEncoderOptions : BitmapEncoderOptions
7+
{
8+
/// <summary>
9+
/// Gets the default JPEG encoder options.
10+
/// </summary>
11+
public static JpegBitmapEncoderOptions Default { get; } = new();
12+
13+
/// <summary>
14+
/// Gets or sets the quality to use, from 0 (lowest) to 100 (highest).
15+
/// Defaults to 100.
16+
/// </summary>
17+
public int Quality { get; init; } = 100;
18+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using System.IO.Compression;
2+
3+
namespace Avalonia.Media.Imaging;
4+
5+
/// <summary>
6+
/// Represents the options used to save a bitmap in the PNG format.
7+
/// </summary>
8+
public sealed class PngBitmapEncoderOptions : BitmapEncoderOptions
9+
{
10+
/// <summary>
11+
/// Gets the default PNG encoder options.
12+
/// </summary>
13+
public static PngBitmapEncoderOptions Default { get; } = new();
14+
15+
/// <summary>
16+
/// Gets or sets the compression level to use.
17+
/// Defaults to <see cref="System.IO.Compression.CompressionLevel.Optimal"/>.
18+
/// </summary>
19+
public CompressionLevel CompressionLevel { get; init; } = CompressionLevel.Optimal;
20+
}

src/Avalonia.Base/Platform/IBitmapImpl.cs

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.IO;
3+
using Avalonia.Media.Imaging;
34
using Avalonia.Metadata;
45

56
namespace Avalonia.Platform
@@ -26,25 +27,13 @@ public interface IBitmapImpl : IDisposable
2627
int Version { get; }
2728

2829
/// <summary>
29-
/// Saves the bitmap to a file.
30-
/// </summary>
31-
/// <param name="fileName">The filename.</param>
32-
/// <param name="quality">
33-
/// The optional quality for compression if supported by the specific backend.
34-
/// The quality value is interpreted from 0 - 100. If quality is null the default quality
35-
/// setting of the backend is applied.
36-
/// </param>
37-
void Save(string fileName, int? quality = null);
38-
39-
/// <summary>
40-
/// Saves the bitmap to a stream in png format.
30+
/// Saves the bitmap to a stream with the specified options.
4131
/// </summary>
4232
/// <param name="stream">The stream.</param>
43-
/// <param name="quality">
44-
/// The optional quality for compression if supported by the specific backend.
45-
/// The quality value is interpreted from 0 - 100. If quality is null the default quality
46-
/// setting of the backend is applied.
33+
/// <param name="options">
34+
/// The options specifying the format and settings to use.
35+
/// Typical usages include <see cref="PngBitmapEncoderOptions"/> and <see cref="JpegBitmapEncoderOptions"/>.
4736
/// </param>
48-
void Save(Stream stream, int? quality = null);
37+
void Save(Stream stream, BitmapEncoderOptions options);
4938
}
5039
}

0 commit comments

Comments
 (0)