Browse Source

Implement ability to decode a bitmap at a specified width or height.

pull/3890/head
Dan Walmsley 6 years ago
parent
commit
663b91a8bf
  1. 24
      src/Avalonia.Visuals/Media/Imaging/Bitmap.cs
  2. 11
      src/Avalonia.Visuals/Media/Imaging/BitmapDecodeOptions.cs
  3. 25
      src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs
  4. 61
      src/Skia/Avalonia.Skia/ImmutableBitmap.cs
  5. 40
      src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
  6. 15
      src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs

24
src/Avalonia.Visuals/Media/Imaging/Bitmap.cs

@ -11,6 +11,18 @@ namespace Avalonia.Media.Imaging
/// </summary>
public class Bitmap : IBitmap
{
public static Bitmap DecodeToWidth(Stream stream, int width, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{
IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
return new Bitmap(factory.LoadBitmapToWidth(stream, width, interpolationMode));
}
public static Bitmap DecodeToHeight(Stream stream, int height, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{
IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
return new Bitmap(factory.LoadBitmapToHeight(stream, height, interpolationMode));
}
/// <summary>
/// Initializes a new instance of the <see cref="Bitmap"/> class.
/// </summary>
@ -31,18 +43,6 @@ namespace Avalonia.Media.Imaging
PlatformImpl = RefCountable.Create(factory.LoadBitmap(stream));
}
public Bitmap(Stream stream, BitmapDecodeOptions decodeOptions)
{
IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
PlatformImpl = RefCountable.Create(factory.LoadBitmap(stream, decodeOptions));
}
public Bitmap(string file, BitmapDecodeOptions decodeOptions)
{
IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
PlatformImpl = RefCountable.Create(factory.LoadBitmap(file, decodeOptions));
}
/// <summary>
/// Initializes a new instance of the <see cref="Bitmap"/> class.
/// </summary>

11
src/Avalonia.Visuals/Media/Imaging/BitmapDecodeOptions.cs

@ -1,11 +0,0 @@
using Avalonia.Visuals.Media.Imaging;
namespace Avalonia.Media.Imaging
{
public struct BitmapDecodeOptions
{
public PixelSize DecodePixelSize { get; set; }
public BitmapInterpolationMode InterpolationMode { get; set; }
}
}

25
src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Visuals.Media.Imaging;
namespace Avalonia.Platform
{
@ -88,18 +89,30 @@ namespace Avalonia.Platform
/// <summary>
/// Loads a bitmap implementation from a file..
/// </summary>
/// <param name="fileName">The filename of the bitmap.</param>
/// <param name="decodeOptions">Decode options that specify the size of the decoded bitmap.</param>
/// <param name="fileName">The filename of the bitmap.</param>
/// <returns>An <see cref="IBitmapImpl"/>.</returns>
IBitmapImpl LoadBitmap(string fileName, BitmapDecodeOptions? decodeOptions = null);
IBitmapImpl LoadBitmap(string fileName);
/// <summary>
/// Loads a bitmap implementation from a file..
/// </summary>
/// <param name="stream">The stream to read the bitmap from.</param>
/// <param name="decodeOptions">Decode options that specify the size of the decoded bitmap.</param>
/// <param name="stream">The stream to read the bitmap from.</param>
/// <returns>An <see cref="IBitmapImpl"/>.</returns>
IBitmapImpl LoadBitmap(Stream stream, BitmapDecodeOptions? decodeOptions = null);
IBitmapImpl LoadBitmap(Stream stream);
/// <summary>
/// Loads a bitmap implementation from a file..
/// </summary>
/// <param name="stream">The stream to read the bitmap from.</param>
/// <returns>An <see cref="IBitmapImpl"/>.</returns>
IBitmapImpl LoadBitmapToWidth(Stream stream, int width, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality);
/// <summary>
/// Loads a bitmap implementation from a file..
/// </summary>
/// <param name="stream">The stream to read the bitmap from.</param>
/// <returns>An <see cref="IBitmapImpl"/>.</returns>
IBitmapImpl LoadBitmapToHeight(Stream stream, int height, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality);
/// <summary>
/// Loads a bitmap implementation from a pixels in memory.

61
src/Skia/Avalonia.Skia/ImmutableBitmap.cs

@ -1,7 +1,11 @@
using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Skia.Helpers;
using Avalonia.Visuals.Media.Imaging;
using SkiaSharp;
namespace Avalonia.Skia
@ -36,6 +40,63 @@ namespace Avalonia.Skia
}
}
// NOTE, putting the stream before options in the parameters, causes an exception
// inside SKCodec.Create with optimized code. Probably a bug in .net compiler.
// Other option is to have the argument order as desired and use PreserveSig options.
[MethodImpl(MethodImplOptions.PreserveSig)]
public ImmutableBitmap(Stream stream, int decodeSize, bool horizontal, BitmapInterpolationMode interpolationMode)
{
// create the codec
var codec = SKCodec.Create(stream);
var info = codec.Info;
// get the scale that is nearest to what we want (eg: jpg returned 512)
var supportedScale = codec.GetScaledDimensions(horizontal ? ((float)decodeSize / info.Width) : ((float)decodeSize / info.Height));
// decode the bitmap at the nearest size
var nearest = new SKImageInfo(supportedScale.Width, supportedScale.Height);
var bmp = SKBitmap.Decode(codec, nearest);
// now scale that to the size that we want
var realScale = horizontal ? ((double)info.Height / info.Width) : ((double)info.Width / info.Height);
SKImageInfo desired;
if (horizontal)
{
desired = new SKImageInfo(decodeSize, (int)(realScale * decodeSize));
}
else
{
desired = new SKImageInfo((int)(realScale * decodeSize), decodeSize);
}
if (bmp.Width != desired.Width || bmp.Height != desired.Height)
{
if (bmp.Height != bmp.Width)
{
}
var scaledBmp = bmp.Resize(desired, interpolationMode.ToSKFilterQuality());
bmp.Dispose();
bmp = scaledBmp;
}
_image = SKImage.FromBitmap(bmp);
bmp.Dispose();
if (_image == null)
{
throw new ArgumentException("Unable to load bitmap from provided data");
}
PixelSize = new PixelSize(_image.Width, _image.Height);
// TODO: Skia doesn't have an API for DPI.
Dpi = new Vector(96, 96);
}
/// <summary>
/// Create immutable bitmap from given pixel data copy.
/// </summary>

40
src/Skia/Avalonia.Skia/PlatformRenderInterface.cs

@ -1,11 +1,13 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.OpenGL;
using Avalonia.Platform;
using Avalonia.Visuals.Media.Imaging;
using SkiaSharp;
namespace Avalonia.Skia
@ -78,37 +80,27 @@ namespace Avalonia.Skia
return new StreamGeometryImpl();
}
/// <inheritdoc />
public unsafe IBitmapImpl LoadBitmap(Stream stream, BitmapDecodeOptions? decodeOptions = null)
{
if (decodeOptions is null)
public IBitmapImpl LoadBitmap(string fileName)
{
using (var stream = File.OpenRead(fileName))
{
return new ImmutableBitmap(stream);
return LoadBitmap(stream);
}
else
{
var options = decodeOptions.Value;
var skBitmap = SKBitmap.Decode(stream);
skBitmap = skBitmap.Resize(new SKImageInfo(options.DecodePixelSize.Width, options.DecodePixelSize.Height), options.InterpolationMode.ToSKFilterQuality());
}
fixed (byte* p = skBitmap.Bytes)
{
IntPtr ptr = (IntPtr)p;
public IBitmapImpl LoadBitmap(Stream stream)
{
return new ImmutableBitmap(stream);
}
return LoadBitmap(PixelFormat.Bgra8888, ptr, new PixelSize(skBitmap.Width, skBitmap.Height), new Vector(96, 96), skBitmap.RowBytes);
}
}
public IBitmapImpl LoadBitmapToWidth(Stream stream, int width, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{
return new ImmutableBitmap(stream, width, true, interpolationMode);
}
/// <inheritdoc />
public IBitmapImpl LoadBitmap(string fileName, BitmapDecodeOptions? decodeOptions = null)
public IBitmapImpl LoadBitmapToHeight(Stream stream, int height, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{
using (var stream = File.OpenRead(fileName))
{
return LoadBitmap(stream, decodeOptions);
}
return new ImmutableBitmap(stream, height, false, interpolationMode);
}
/// <inheritdoc />

15
src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs

@ -9,6 +9,7 @@ using Avalonia.Direct2D1.Media.Imaging;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Visuals.Media.Imaging;
using SharpDX.DirectWrite;
using GlyphRun = Avalonia.Media.GlyphRun;
using TextAlignment = Avalonia.Media.TextAlignment;
@ -180,16 +181,26 @@ namespace Avalonia.Direct2D1
public IGeometryImpl CreateRectangleGeometry(Rect rect) => new RectangleGeometryImpl(rect);
public IStreamGeometryImpl CreateStreamGeometry() => new StreamGeometryImpl();
public IBitmapImpl LoadBitmap(string fileName, BitmapDecodeOptions? options = null)
public IBitmapImpl LoadBitmap(string fileName)
{
return new WicBitmapImpl(fileName);
}
public IBitmapImpl LoadBitmap(Stream stream, BitmapDecodeOptions? options = null)
public IBitmapImpl LoadBitmap(Stream stream)
{
return new WicBitmapImpl(stream);
}
public IBitmapImpl LoadBitmapToWidth(Stream stream, int width, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{
throw new NotImplementedException();
}
public IBitmapImpl LoadBitmapToHeight(Stream stream, int height, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{
throw new NotImplementedException();
}
public IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, PixelSize size, Vector dpi, int stride)
{
return new WicBitmapImpl(format, data, size, dpi, stride);

Loading…
Cancel
Save