Browse Source

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

pull/3890/head
Dan Walmsley 7 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> /// </summary>
public class Bitmap : IBitmap 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> /// <summary>
/// Initializes a new instance of the <see cref="Bitmap"/> class. /// Initializes a new instance of the <see cref="Bitmap"/> class.
/// </summary> /// </summary>
@ -31,18 +43,6 @@ namespace Avalonia.Media.Imaging
PlatformImpl = RefCountable.Create(factory.LoadBitmap(stream)); 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> /// <summary>
/// Initializes a new instance of the <see cref="Bitmap"/> class. /// Initializes a new instance of the <see cref="Bitmap"/> class.
/// </summary> /// </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 System.IO;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Media.Imaging; using Avalonia.Media.Imaging;
using Avalonia.Visuals.Media.Imaging;
namespace Avalonia.Platform namespace Avalonia.Platform
{ {
@ -88,18 +89,30 @@ namespace Avalonia.Platform
/// <summary> /// <summary>
/// Loads a bitmap implementation from a file.. /// Loads a bitmap implementation from a file..
/// </summary> /// </summary>
/// <param name="fileName">The filename of the bitmap.</param> /// <param name="fileName">The filename of the bitmap.</param>
/// <param name="decodeOptions">Decode options that specify the size of the decoded bitmap.</param>
/// <returns>An <see cref="IBitmapImpl"/>.</returns> /// <returns>An <see cref="IBitmapImpl"/>.</returns>
IBitmapImpl LoadBitmap(string fileName, BitmapDecodeOptions? decodeOptions = null); IBitmapImpl LoadBitmap(string fileName);
/// <summary> /// <summary>
/// Loads a bitmap implementation from a file.. /// Loads a bitmap implementation from a file..
/// </summary> /// </summary>
/// <param name="stream">The stream to read the bitmap from.</param> /// <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>
/// <returns>An <see cref="IBitmapImpl"/>.</returns> /// <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> /// <summary>
/// Loads a bitmap implementation from a pixels in memory. /// Loads a bitmap implementation from a pixels in memory.

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

@ -1,7 +1,11 @@
using System; using System;
using System.IO; using System.IO;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using Avalonia.Media.Imaging;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Skia.Helpers; using Avalonia.Skia.Helpers;
using Avalonia.Visuals.Media.Imaging;
using SkiaSharp; using SkiaSharp;
namespace Avalonia.Skia 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> /// <summary>
/// Create immutable bitmap from given pixel data copy. /// Create immutable bitmap from given pixel data copy.
/// </summary> /// </summary>

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

@ -1,11 +1,13 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Security.Cryptography;
using Avalonia.Controls.Platform.Surfaces; using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Media.Imaging; using Avalonia.Media.Imaging;
using Avalonia.OpenGL; using Avalonia.OpenGL;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Visuals.Media.Imaging;
using SkiaSharp; using SkiaSharp;
namespace Avalonia.Skia namespace Avalonia.Skia
@ -78,37 +80,27 @@ namespace Avalonia.Skia
return new StreamGeometryImpl(); return new StreamGeometryImpl();
} }
/// <inheritdoc /> public IBitmapImpl LoadBitmap(string fileName)
public unsafe IBitmapImpl LoadBitmap(Stream stream, BitmapDecodeOptions? decodeOptions = null) {
{ using (var stream = File.OpenRead(fileName))
if (decodeOptions is null)
{ {
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) public IBitmapImpl LoadBitmap(Stream stream)
{ {
IntPtr ptr = (IntPtr)p; 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 LoadBitmapToHeight(Stream stream, int height, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
public IBitmapImpl LoadBitmap(string fileName, BitmapDecodeOptions? decodeOptions = null)
{ {
using (var stream = File.OpenRead(fileName)) return new ImmutableBitmap(stream, height, false, interpolationMode);
{
return LoadBitmap(stream, decodeOptions);
}
} }
/// <inheritdoc /> /// <inheritdoc />

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

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

Loading…
Cancel
Save