Conflicts: src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs tests/Avalonia.RenderTests/Avalonia.RenderTests.projitems tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.csscenegraph-after-breakage
@ -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; |
using System; |
||||
|
|
||||
namespace Avalonia.Controls.Platform.Surfaces |
namespace Avalonia.Platform |
||||
{ |
{ |
||||
public interface ILockedFramebuffer : IDisposable |
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(null)) |
||||
|
{ |
||||
|
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(null)) |
||||
|
{ |
||||
|
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.PlatformImpl, 1, rc, rc); |
||||
|
} |
||||
|
rtb.Save(System.IO.Path.Combine(OutputPath, testName + ".out.png")); |
||||
|
} |
||||
|
CompareImagesNoRenderer(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")); |
||||
|
CompareImagesNoRenderer(testName); |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,174 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.IO; |
||||
|
using Avalonia.Media; |
||||
|
using Avalonia.Platform; |
||||
|
|
||||
|
namespace Avalonia.Visuals.UnitTests.VisualTree |
||||
|
{ |
||||
|
class MockRenderInterface : IPlatformRenderInterface |
||||
|
{ |
||||
|
public IFormattedTextImpl CreateFormattedText( |
||||
|
string text, |
||||
|
string fontFamilyName, |
||||
|
double fontSize, |
||||
|
FontStyle fontStyle, |
||||
|
TextAlignment textAlignment, |
||||
|
FontWeight fontWeight, |
||||
|
TextWrapping wrapping) |
||||
|
{ |
||||
|
throw new NotImplementedException(); |
||||
|
} |
||||
|
|
||||
|
public IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces) |
||||
|
{ |
||||
|
throw new NotImplementedException(); |
||||
|
} |
||||
|
|
||||
|
public IRenderTargetBitmapImpl CreateRenderTargetBitmap(int width, int height) |
||||
|
{ |
||||
|
throw new NotImplementedException(); |
||||
|
} |
||||
|
|
||||
|
public IStreamGeometryImpl CreateStreamGeometry() |
||||
|
{ |
||||
|
return new MockStreamGeometry(); |
||||
|
} |
||||
|
|
||||
|
public IBitmapImpl LoadBitmap(Stream stream) |
||||
|
{ |
||||
|
throw new NotImplementedException(); |
||||
|
} |
||||
|
|
||||
|
public IBitmapImpl LoadBitmap(string fileName) |
||||
|
{ |
||||
|
throw new NotImplementedException(); |
||||
|
} |
||||
|
|
||||
|
public IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, int width, int height, int stride) |
||||
|
{ |
||||
|
throw new NotImplementedException(); |
||||
|
} |
||||
|
|
||||
|
public IWritableBitmapImpl CreateWritableBitmap(int width, int height, PixelFormat? fmt) |
||||
|
{ |
||||
|
throw new NotImplementedException(); |
||||
|
} |
||||
|
|
||||
|
class MockStreamGeometry : IStreamGeometryImpl |
||||
|
{ |
||||
|
private MockStreamGeometryContext _impl = new MockStreamGeometryContext(); |
||||
|
public Rect Bounds |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
throw new NotImplementedException(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public Matrix Transform |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
throw new NotImplementedException(); |
||||
|
} |
||||
|
|
||||
|
set |
||||
|
{ |
||||
|
throw new NotImplementedException(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public IStreamGeometryImpl Clone() |
||||
|
{ |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public bool FillContains(Point point) |
||||
|
{ |
||||
|
return _impl.FillContains(point); |
||||
|
} |
||||
|
|
||||
|
public Rect GetRenderBounds(double strokeThickness) |
||||
|
{ |
||||
|
throw new NotImplementedException(); |
||||
|
} |
||||
|
|
||||
|
public IStreamGeometryContextImpl Open() |
||||
|
{ |
||||
|
return _impl; |
||||
|
} |
||||
|
|
||||
|
class MockStreamGeometryContext : IStreamGeometryContextImpl |
||||
|
{ |
||||
|
private List<Point> points = new List<Point>(); |
||||
|
public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection) |
||||
|
{ |
||||
|
throw new NotImplementedException(); |
||||
|
} |
||||
|
|
||||
|
public void BeginFigure(Point startPoint, bool isFilled) |
||||
|
{ |
||||
|
points.Add(startPoint); |
||||
|
} |
||||
|
|
||||
|
public void CubicBezierTo(Point point1, Point point2, Point point3) |
||||
|
{ |
||||
|
throw new NotImplementedException(); |
||||
|
} |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public void EndFigure(bool isClosed) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public void LineTo(Point point) |
||||
|
{ |
||||
|
points.Add(point); |
||||
|
} |
||||
|
|
||||
|
public void QuadraticBezierTo(Point control, Point endPoint) |
||||
|
{ |
||||
|
throw new NotImplementedException(); |
||||
|
} |
||||
|
|
||||
|
public void SetFillRule(FillRule fillRule) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public bool FillContains(Point point) |
||||
|
{ |
||||
|
// Use the algorithm from http://www.blackpawn.com/texts/pointinpoly/default.html
|
||||
|
// to determine if the point is in the geometry (since it will always be convex in this situation)
|
||||
|
for (int i = 0; i < points.Count; i++) |
||||
|
{ |
||||
|
var a = points[i]; |
||||
|
var b = points[(i + 1) % points.Count]; |
||||
|
var c = points[(i + 2) % points.Count]; |
||||
|
|
||||
|
Vector v0 = c - a; |
||||
|
Vector v1 = b - a; |
||||
|
Vector v2 = point - a; |
||||
|
|
||||
|
var dot00 = v0 * v0; |
||||
|
var dot01 = v0 * v1; |
||||
|
var dot02 = v0 * v2; |
||||
|
var dot11 = v1 * v1; |
||||
|
var dot12 = v1 * v2; |
||||
|
|
||||
|
|
||||
|
var invDenom = 1 / (dot00 * dot11 - dot01 * dot01); |
||||
|
var u = (dot11 * dot02 - dot01 * dot12) * invDenom; |
||||
|
var v = (dot00 * dot12 - dot01 * dot02) * invDenom; |
||||
|
if ((u >= 0) && (v >= 0) && (u + v < 1)) return true; |
||||
|
} |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
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 |