Browse Source
* Implement Bitmap.Save with format options * Rename BitmapEncoderOptions.cs to match its type * Update API suppressionspull/20993/head
committed by
GitHub
31 changed files with 376 additions and 148 deletions
@ -0,0 +1,14 @@ |
|||
using System.IO; |
|||
|
|||
namespace Avalonia.Media.Imaging; |
|||
|
|||
/// <summary>
|
|||
/// Represents the options used while saving a bitmap using <see cref="Bitmap.Save(Stream, BitmapEncoderOptions)"/>.
|
|||
/// Common implementations are <see cref="PngBitmapEncoderOptions"/>, <see cref="JpegBitmapEncoderOptions"/>.
|
|||
/// </summary>
|
|||
public abstract class BitmapEncoderOptions |
|||
{ |
|||
internal BitmapEncoderOptions() |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
namespace Avalonia.Media.Imaging; |
|||
|
|||
/// <summary>
|
|||
/// Represents the options used to save a bitmap in the JPEG format.
|
|||
/// </summary>
|
|||
public sealed class JpegBitmapEncoderOptions : BitmapEncoderOptions |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the default JPEG encoder options.
|
|||
/// </summary>
|
|||
public static JpegBitmapEncoderOptions Default { get; } = new(); |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the quality to use, from 0 (lowest) to 100 (highest).
|
|||
/// Defaults to 100.
|
|||
/// </summary>
|
|||
public int Quality { get; init; } = 100; |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
using System.IO.Compression; |
|||
|
|||
namespace Avalonia.Media.Imaging; |
|||
|
|||
/// <summary>
|
|||
/// Represents the options used to save a bitmap in the PNG format.
|
|||
/// </summary>
|
|||
public sealed class PngBitmapEncoderOptions : BitmapEncoderOptions |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the default PNG encoder options.
|
|||
/// </summary>
|
|||
public static PngBitmapEncoderOptions Default { get; } = new(); |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the compression level to use.
|
|||
/// Defaults to <see cref="System.IO.Compression.CompressionLevel.Optimal"/>.
|
|||
/// </summary>
|
|||
public CompressionLevel CompressionLevel { get; init; } = CompressionLevel.Optimal; |
|||
} |
|||
@ -0,0 +1,108 @@ |
|||
using System; |
|||
using System.IO; |
|||
using System.IO.Compression; |
|||
using System.Runtime.InteropServices; |
|||
using Avalonia.Media.Imaging; |
|||
using Avalonia.Platform; |
|||
using Avalonia.UnitTests; |
|||
using SkiaSharp; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Skia.UnitTests.Media |
|||
{ |
|||
public class BitmapSaveTests |
|||
{ |
|||
[Fact] |
|||
public void Save_With_Null_Options_Throws() |
|||
{ |
|||
using var app = Start(); |
|||
|
|||
using var bitmap = CreateBitmap(SKColors.Red, 16, 16); |
|||
using var stream = new MemoryStream(); |
|||
|
|||
Assert.Throws<ArgumentNullException>(() => bitmap.Save(stream, (BitmapEncoderOptions)null!)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Save_With_Invalid_Png_CompressionLevel_Throws() |
|||
{ |
|||
using var app = Start(); |
|||
|
|||
using var bitmap = CreateBitmap(SKColors.Red, 16, 16); |
|||
using var stream = new MemoryStream(); |
|||
var options = new PngBitmapEncoderOptions { CompressionLevel = (CompressionLevel)42 }; |
|||
|
|||
Assert.Throws<ArgumentOutOfRangeException>(() => bitmap.Save(stream, options)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Save_With_Invalid_Jpeg_Quality_Throws() |
|||
{ |
|||
using var app = Start(); |
|||
|
|||
using var bitmap = CreateBitmap(SKColors.Red, 16, 16); |
|||
using var stream = new MemoryStream(); |
|||
var options = new JpegBitmapEncoderOptions { Quality = -1 }; |
|||
|
|||
Assert.Throws<ArgumentOutOfRangeException>(() => bitmap.Save(stream, options)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Save_With_Png_Options_Produces_Png() |
|||
{ |
|||
using var app = Start(); |
|||
|
|||
using var bitmap = CreateBitmap(SKColors.Red, 16, 16); |
|||
using var stream = new MemoryStream(); |
|||
|
|||
bitmap.Save(stream, PngBitmapEncoderOptions.Default); |
|||
|
|||
stream.Position = 0; |
|||
using var codec = SKCodec.Create(stream); |
|||
|
|||
Assert.Equal(SKEncodedImageFormat.Png, codec.EncodedFormat); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Save_With_Jpeg_Options_Produces_Jpeg() |
|||
{ |
|||
using var app = Start(); |
|||
|
|||
using var bitmap = CreateBitmap(SKColors.Red, 16, 16); |
|||
using var stream = new MemoryStream(); |
|||
|
|||
bitmap.Save(stream, JpegBitmapEncoderOptions.Default); |
|||
|
|||
stream.Position = 0; |
|||
using var codec = SKCodec.Create(stream); |
|||
|
|||
Assert.Equal(SKEncodedImageFormat.Jpeg, codec.EncodedFormat); |
|||
} |
|||
|
|||
private static WriteableBitmap CreateBitmap(SKColor color, int width, int height) |
|||
{ |
|||
var pixel = (color.Alpha << 24) | (color.Red << 16) | (color.Green << 8) | color.Blue; |
|||
|
|||
var data = new int[width * height]; |
|||
data.AsSpan().Fill(pixel); |
|||
|
|||
return CreateBitmap(width, height, data); |
|||
} |
|||
|
|||
private static WriteableBitmap CreateBitmap(int width, int height, int[] data) |
|||
{ |
|||
var bitmap = new WriteableBitmap(new PixelSize(width, height), new Vector(96, 96), PixelFormat.Bgra8888, AlphaFormat.Premul); |
|||
|
|||
using var fb = bitmap.Lock(); |
|||
|
|||
for (var y = 0; y < height; y++) |
|||
Marshal.Copy(data, y * width, fb.Address + y * fb.RowBytes, width); |
|||
|
|||
return bitmap; |
|||
} |
|||
|
|||
private static IDisposable Start() |
|||
=> UnitTestApplication.Start( |
|||
TestServices.MockPlatformRenderInterface.With(renderInterface: new PlatformRenderInterface())); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue