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