Implemented WritableBitmap and bitmap creation by copying from in-memory datapull/907/head
@ -1,15 +0,0 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Avalonia.Controls.Platform.Surfaces |
|||
{ |
|||
public enum PixelFormat |
|||
{ |
|||
Rgb565, |
|||
Rgba8888, |
|||
Bgra8888 |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
using Avalonia.Platform; |
|||
|
|||
namespace Avalonia.Media.Imaging |
|||
{ |
|||
/// <summary>
|
|||
/// Holds a writable bitmap image.
|
|||
/// </summary>
|
|||
public class WritableBitmap : Bitmap |
|||
{ |
|||
public WritableBitmap(int width, int height, PixelFormat? format = null) |
|||
: base(AvaloniaLocator.Current.GetService<IPlatformRenderInterface>().CreateWritableBitmap(width, height, format)) |
|||
{ |
|||
} |
|||
|
|||
public ILockedFramebuffer Lock() => ((IWritableBitmapImpl) PlatformImpl).Lock(); |
|||
} |
|||
} |
|||
@ -1,6 +1,6 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Controls.Platform.Surfaces |
|||
namespace Avalonia.Platform |
|||
{ |
|||
public interface ILockedFramebuffer : IDisposable |
|||
{ |
|||
@ -0,0 +1,16 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Avalonia.Platform |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the platform-specific interface for a <see cref="Avalonia.Media.Imaging.WritableBitmap"/>.
|
|||
/// </summary>
|
|||
public interface IWritableBitmapImpl : IBitmapImpl |
|||
{ |
|||
ILockedFramebuffer Lock(); |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
namespace Avalonia.Platform |
|||
{ |
|||
public enum PixelFormat |
|||
{ |
|||
Rgb565, |
|||
Rgba8888, |
|||
Bgra8888 |
|||
} |
|||
} |
|||
@ -0,0 +1,47 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
using Avalonia.Platform; |
|||
using SharpDX.WIC; |
|||
using PixelFormat = Avalonia.Platform.PixelFormat; |
|||
|
|||
namespace Avalonia.Direct2D1.Media.Imaging |
|||
{ |
|||
class WritableWicBitmapImpl : WicBitmapImpl, IWritableBitmapImpl |
|||
{ |
|||
public WritableWicBitmapImpl(ImagingFactory factory, int width, int height, PixelFormat? pixelFormat) |
|||
: base(factory, width, height, pixelFormat) |
|||
{ |
|||
} |
|||
|
|||
class LockedBitmap : ILockedFramebuffer |
|||
{ |
|||
private readonly BitmapLock _lock; |
|||
private readonly PixelFormat _format; |
|||
|
|||
public LockedBitmap(BitmapLock l, PixelFormat format) |
|||
{ |
|||
_lock = l; |
|||
_format = format; |
|||
} |
|||
|
|||
|
|||
public void Dispose() |
|||
{ |
|||
_lock.Dispose(); |
|||
} |
|||
|
|||
public IntPtr Address => _lock.Data.DataPointer; |
|||
public int Width => _lock.Size.Width; |
|||
public int Height => _lock.Size.Height; |
|||
public int RowBytes => _lock.Stride; |
|||
public Size Dpi { get; } = new Size(96, 96); |
|||
public PixelFormat Format => _format; |
|||
|
|||
} |
|||
|
|||
public ILockedFramebuffer Lock() => new LockedBitmap(WicImpl.Lock(BitmapLockFlags.Write), PixelFormat.Value); |
|||
} |
|||
} |
|||
@ -0,0 +1,139 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.IO; |
|||
using System.Runtime.InteropServices; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Controls.Platform.Surfaces; |
|||
using Avalonia.Controls.Shapes; |
|||
using Avalonia.Layout; |
|||
using Avalonia.Media; |
|||
using Avalonia.Media.Imaging; |
|||
using Avalonia.Platform; |
|||
using Xunit; |
|||
|
|||
#if AVALONIA_CAIRO
|
|||
namespace Avalonia.Cairo.RenderTests.Media |
|||
#elif AVALONIA_SKIA
|
|||
namespace Avalonia.Skia.RenderTests |
|||
#else
|
|||
namespace Avalonia.Direct2D1.RenderTests.Media |
|||
#endif
|
|||
{ |
|||
public class BitmapTests : TestBase |
|||
{ |
|||
public BitmapTests() |
|||
: base(@"Media\Bitmap") |
|||
{ |
|||
Directory.CreateDirectory(OutputPath); |
|||
} |
|||
|
|||
class Framebuffer : ILockedFramebuffer, IFramebufferPlatformSurface |
|||
{ |
|||
public Framebuffer(PixelFormat fmt, int width, int height) |
|||
{ |
|||
Format = fmt; |
|||
var bpp = fmt == PixelFormat.Rgb565 ? 2 : 4; |
|||
Width = width; |
|||
Height = height; |
|||
RowBytes = bpp * width; |
|||
Address = Marshal.AllocHGlobal(Height * RowBytes); |
|||
} |
|||
|
|||
public IntPtr Address { get; } |
|||
|
|||
public Size Dpi { get; } = new Size(96, 96); |
|||
|
|||
public PixelFormat Format { get; } |
|||
|
|||
public int Height { get; } |
|||
|
|||
public int RowBytes { get; } |
|||
|
|||
public int Width { get; } |
|||
|
|||
public void Dispose() |
|||
{ |
|||
//no-op
|
|||
} |
|||
|
|||
public ILockedFramebuffer Lock() |
|||
{ |
|||
return this; |
|||
} |
|||
|
|||
public void Deallocate() => Marshal.FreeHGlobal(Address); |
|||
} |
|||
|
|||
|
|||
#if AVALONIA_SKIA
|
|||
[Theory] |
|||
#else
|
|||
[Theory(Skip = "Framebuffer not supported")] |
|||
#endif
|
|||
[InlineData(PixelFormat.Rgba8888), InlineData(PixelFormat.Bgra8888), InlineData(PixelFormat.Rgb565)] |
|||
public void FramebufferRenderResultsShouldBeUsableAsBitmap(PixelFormat fmt) |
|||
{ |
|||
var testName = nameof(FramebufferRenderResultsShouldBeUsableAsBitmap) + "_" + fmt; |
|||
var fb = new Framebuffer(fmt, 80, 80); |
|||
var r = Avalonia.AvaloniaLocator.Current.GetService<IPlatformRenderInterface>(); |
|||
using (var target = r.CreateRenderTarget(new object[] { fb })) |
|||
using (var ctx = target.CreateDrawingContext()) |
|||
{ |
|||
ctx.PushOpacity(0.8); |
|||
ctx.FillRectangle(Brushes.Chartreuse, new Rect(0, 0, 20, 100)); |
|||
ctx.FillRectangle(Brushes.Crimson, new Rect(20, 0, 20, 100)); |
|||
ctx.FillRectangle(Brushes.Gold, new Rect(40, 0, 20, 100)); |
|||
} |
|||
|
|||
var bmp = new Bitmap(fmt, fb.Address, fb.Width, fb.Height, fb.RowBytes); |
|||
fb.Deallocate(); |
|||
using (var rtb = new RenderTargetBitmap(100, 100)) |
|||
{ |
|||
using (var ctx = rtb.CreateDrawingContext()) |
|||
{ |
|||
ctx.FillRectangle(Brushes.Blue, new Rect(0, 0, 100, 100)); |
|||
ctx.FillRectangle(Brushes.Pink, new Rect(0, 20, 100, 10)); |
|||
|
|||
var rc = new Rect(0, 0, 60, 60); |
|||
ctx.DrawImage(bmp, 1, rc, rc); |
|||
} |
|||
rtb.Save(System.IO.Path.Combine(OutputPath, testName + ".out.png")); |
|||
} |
|||
CompareImages(testName); |
|||
} |
|||
|
|||
#if AVALONIA_CAIRO
|
|||
//wontfix
|
|||
#else
|
|||
[Theory] |
|||
#endif
|
|||
[InlineData(PixelFormat.Bgra8888), InlineData(PixelFormat.Rgba8888)] |
|||
public void WritableBitmapShouldBeUsable(PixelFormat fmt) |
|||
{ |
|||
var writableBitmap = new WritableBitmap(256, 256, fmt); |
|||
|
|||
var data = new int[256 * 256]; |
|||
for (int y = 0; y < 256; y++) |
|||
for (int x = 0; x < 256; x++) |
|||
data[y * 256 + x] =(int)((uint)(x + (y << 8)) | 0xFF000000u); |
|||
|
|||
|
|||
using (var l = writableBitmap.Lock()) |
|||
{ |
|||
for(var r = 0; r<256; r++) |
|||
{ |
|||
Marshal.Copy(data, r * 256, new IntPtr(l.Address.ToInt64() + r * l.RowBytes), 256); |
|||
} |
|||
} |
|||
|
|||
|
|||
var name = nameof(WritableBitmapShouldBeUsable) + "_" + fmt; |
|||
|
|||
writableBitmap.Save(System.IO.Path.Combine(OutputPath, name + ".out.png")); |
|||
CompareImages(name); |
|||
|
|||
} |
|||
} |
|||
} |
|||
|
After Width: | Height: | Size: 144 KiB |
|
After Width: | Height: | Size: 89 KiB |
|
After Width: | Height: | Size: 800 B |
|
After Width: | Height: | Size: 786 B |
|
After Width: | Height: | Size: 800 B |
|
After Width: | Height: | Size: 144 KiB |
|
After Width: | Height: | Size: 89 KiB |