diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs index 405da12967..2afa4e83f1 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs @@ -20,9 +20,10 @@ namespace Avalonia.Android.Platform.SkiaPlatform ANativeWindow_Buffer buffer; var rc = new ARect() { - right = Width = ANativeWindow_getWidth(_window), - bottom = Height = ANativeWindow_getHeight(_window) + right = ANativeWindow_getWidth(_window), + bottom = ANativeWindow_getHeight(_window) }; + Size = new PixelSize(rc.right, rc.bottom); ANativeWindow_lock(_window, out buffer, ref rc); Format = buffer.format == AndroidPixelFormat.WINDOW_FORMAT_RGB_565 @@ -41,8 +42,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform } public IntPtr Address { get; set; } - public int Width { get; } - public int Height { get; } + public PixelSize Size { get; } public int RowBytes { get; } public Vector Dpi { get; } = new Vector(96, 96); public PixelFormat Format { get; } diff --git a/src/Avalonia.Controls/Image.cs b/src/Avalonia.Controls/Image.cs index 40203bab02..72379e7b53 100644 --- a/src/Avalonia.Controls/Image.cs +++ b/src/Avalonia.Controls/Image.cs @@ -58,7 +58,7 @@ namespace Avalonia.Controls if (source != null) { Rect viewPort = new Rect(Bounds.Size); - Size sourceSize = new Size(source.PixelWidth, source.PixelHeight); + Size sourceSize = new Size(source.PixelSize.Width, source.PixelSize.Height); Vector scale = Stretch.CalculateScaling(Bounds.Size, sourceSize); Size scaledSize = sourceSize * scale; Rect destRect = viewPort @@ -84,8 +84,7 @@ namespace Avalonia.Controls if (source != null) { - Size sourceSize = new Size(source.PixelWidth, source.PixelHeight); - + Size sourceSize = new Size(source.PixelSize.Width, source.PixelSize.Height); if (double.IsInfinity(availableSize.Width) || double.IsInfinity(availableSize.Height)) { return sourceSize; diff --git a/src/Avalonia.Controls/Remote/RemoteWidget.cs b/src/Avalonia.Controls/Remote/RemoteWidget.cs index 106c6fff63..539fe1ec4b 100644 --- a/src/Avalonia.Controls/Remote/RemoteWidget.cs +++ b/src/Avalonia.Controls/Remote/RemoteWidget.cs @@ -73,9 +73,9 @@ namespace Avalonia.Controls.Remote if (_lastFrame != null) { var fmt = (PixelFormat) _lastFrame.Format; - if (_bitmap == null || _bitmap.PixelWidth != _lastFrame.Width || - _bitmap.PixelHeight != _lastFrame.Height) - _bitmap = new WriteableBitmap(_lastFrame.Width, _lastFrame.Height, fmt); + if (_bitmap == null || _bitmap.PixelSize.Width != _lastFrame.Width || + _bitmap.PixelSize.Height != _lastFrame.Height) + _bitmap = new WriteableBitmap(new PixelSize(_lastFrame.Width, _lastFrame.Height), new Vector(96, 96), fmt); using (var l = _bitmap.Lock()) { var lineLen = (fmt == PixelFormat.Rgb565 ? 2 : 4) * _lastFrame.Width; @@ -83,7 +83,7 @@ namespace Avalonia.Controls.Remote Marshal.Copy(_lastFrame.Data, y * _lastFrame.Stride, new IntPtr(l.Address.ToInt64() + l.RowBytes * y), lineLen); } - context.DrawImage(_bitmap, 1, new Rect(0, 0, _bitmap.PixelWidth, _bitmap.PixelHeight), + context.DrawImage(_bitmap, 1, new Rect(0, 0, _bitmap.PixelSize.Width, _bitmap.PixelSize.Height), new Rect(Bounds.Size)); } base.Render(context); diff --git a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs index 257e3e9c2a..17cdbf834c 100644 --- a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs +++ b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs @@ -267,7 +267,7 @@ namespace Avalonia.Controls.Remote.Server var handle = GCHandle.Alloc(data, GCHandleType.Pinned); try { - _framebuffer = new LockedFramebuffer(handle.AddrOfPinnedObject(), width, height, width * bpp, _dpi, (PixelFormat)fmt, + _framebuffer = new LockedFramebuffer(handle.AddrOfPinnedObject(), new PixelSize(width, height), width * bpp, _dpi, (PixelFormat)fmt, null); Paint?.Invoke(new Rect(0, 0, width, height)); } diff --git a/src/Avalonia.Native/DeferredFramebuffer.cs b/src/Avalonia.Native/DeferredFramebuffer.cs index 4bee59266c..adc721de42 100644 --- a/src/Avalonia.Native/DeferredFramebuffer.cs +++ b/src/Avalonia.Native/DeferredFramebuffer.cs @@ -18,15 +18,14 @@ namespace Avalonia.Native { _lockWindow = lockWindow; Address = Marshal.AllocHGlobal(width * height * 4); - Width = width; - Height = height; + Size = new PixelSize(width, height); RowBytes = width * 4; Dpi = dpi; Format = PixelFormat.Rgba8888; } public IntPtr Address { get; set; } - public int Width { get; set; } + public PixelSize Size { get; set; } public int Height { get; set; } public int RowBytes { get; set; } public Vector Dpi { get; set; } @@ -66,8 +65,8 @@ namespace Avalonia.Native X = Dpi.X, Y = Dpi.Y }, - Width = Width, - Height = Height, + Width = Size.Width, + Height = Size.Height, PixelFormat = (AvnPixelFormat)Format, Stride = RowBytes }; diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index e81e912a0a..aaaba44fff 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -100,15 +100,13 @@ namespace Avalonia.Native public FramebufferWrapper(AvnFramebuffer fb) { Address = fb.Data; - Width = fb.Width; - Height = fb.Height; + Size = new PixelSize(fb.Width, fb.Height); RowBytes = fb.Stride; Dpi = new Vector(fb.Dpi.X, fb.Dpi.Y); Format = (PixelFormat)fb.PixelFormat; } public IntPtr Address { get; set; } - public int Width { get; set; } - public int Height { get; set; } + public PixelSize Size { get; set; } public int RowBytes {get;set;} public Vector Dpi { get; set; } public PixelFormat Format { get; } diff --git a/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs b/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs index cb98ed9a9c..0ad826f5a9 100644 --- a/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs +++ b/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs @@ -56,30 +56,29 @@ namespace Avalonia.Media.Imaging { PlatformImpl.Dispose(); } - + /// /// Initializes a new instance of the class. /// - /// Pixel format - /// Pointer to source bytes - /// Bitmap width - /// Bitmap height - /// Bytes per row - public Bitmap(PixelFormat format, IntPtr data, int width, int height, int stride) + /// The pixel format. + /// The pointer to the source bytes. + /// The size of the bitmap in device pixels. + /// The DPI of the bitmap. + /// The number of bytes per row. + public Bitmap(PixelFormat format, IntPtr data, PixelSize size, Vector dpi, int stride) { PlatformImpl = RefCountable.Create(AvaloniaLocator.Current.GetService() - .LoadBitmap(format, data, width, height, stride)); + .LoadBitmap(format, data, size, dpi, stride)); } - /// - /// Gets the width of the bitmap, in pixels. - /// - public int PixelWidth => PlatformImpl.Item.PixelWidth; + /// + public Vector Dpi => PlatformImpl.Item.Dpi; - /// - /// Gets the height of the bitmap, in pixels. - /// - public int PixelHeight => PlatformImpl.Item.PixelHeight; + /// + public Size Size => PlatformImpl.Item.PixelSize.ToSize(Dpi); + + /// + public PixelSize PixelSize => PlatformImpl.Item.PixelSize; /// /// Gets the platform-specific bitmap implementation. diff --git a/src/Avalonia.Visuals/Media/Imaging/IBitmap.cs b/src/Avalonia.Visuals/Media/Imaging/IBitmap.cs index 465173059e..90b13088e1 100644 --- a/src/Avalonia.Visuals/Media/Imaging/IBitmap.cs +++ b/src/Avalonia.Visuals/Media/Imaging/IBitmap.cs @@ -14,20 +14,33 @@ namespace Avalonia.Media.Imaging public interface IBitmap : IDisposable { /// - /// Gets the width of the bitmap, in pixels. + /// Gets the dots per inch (DPI) of the image. /// - int PixelWidth { get; } + /// + /// Note that Skia does not currently support reading the DPI of an image so this value + /// will always be 96dpi on Skia. + /// + Vector Dpi { get; } /// - /// Gets the height of the bitmap, in pixels. + /// Gets the size of the bitmap, in device pixels. /// - int PixelHeight { get; } + PixelSize PixelSize { get; } /// /// Gets the platform-specific bitmap implementation. /// IRef PlatformImpl { get; } + /// + /// Gets the size of the image, in device independent pixels. + /// + /// + /// Note that Skia does not currently support reading the DPI of an image so this value + /// will equal on Skia. + /// + Size Size { get; } + /// /// Saves the bitmap to a file. /// diff --git a/src/Avalonia.Visuals/Media/Imaging/RenderTargetBitmap.cs b/src/Avalonia.Visuals/Media/Imaging/RenderTargetBitmap.cs index db2259dde3..d0b85ef14e 100644 --- a/src/Avalonia.Visuals/Media/Imaging/RenderTargetBitmap.cs +++ b/src/Avalonia.Visuals/Media/Imaging/RenderTargetBitmap.cs @@ -17,12 +17,19 @@ namespace Avalonia.Media.Imaging /// /// Initializes a new instance of the class. /// - /// The width of the bitmap in pixels. - /// The height of the bitmap in pixels. - /// The horizontal DPI of the bitmap. - /// The vertical DPI of the bitmap. - public RenderTargetBitmap(int pixelWidth, int pixelHeight, double dpiX = 96, double dpiY = 96) - : this(RefCountable.Create(CreateImpl(pixelWidth, pixelHeight, dpiX, dpiY))) + /// The size of the bitmap. + public RenderTargetBitmap(PixelSize pixelSize) + : this(pixelSize, new Vector(96, 96)) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The size of the bitmap in device pixels. + /// The DPI of the bitmap. + public RenderTargetBitmap(PixelSize pixelSize, Vector dpi) + : this(RefCountable.Create(CreateImpl(pixelSize, dpi))) { } @@ -45,15 +52,13 @@ namespace Avalonia.Media.Imaging /// /// Creates a platform-specific implementation for a . /// - /// The width of the bitmap. - /// The height of the bitmap. - /// The horizontal DPI of the bitmap. - /// The vertical DPI of the bitmap. + /// The size of the bitmap in device pixels. + /// The DPI of the bitmap. /// The platform-specific implementation. - private static IRenderTargetBitmapImpl CreateImpl(int width, int height, double dpiX, double dpiY) + private static IRenderTargetBitmapImpl CreateImpl(PixelSize size, Vector dpi) { IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService(); - return factory.CreateRenderTargetBitmap(width, height, dpiX, dpiY); + return factory.CreateRenderTargetBitmap(size, dpi); } /// diff --git a/src/Avalonia.Visuals/Media/Imaging/WriteableBitmap.cs b/src/Avalonia.Visuals/Media/Imaging/WriteableBitmap.cs index 529c2efa42..88c0799cb9 100644 --- a/src/Avalonia.Visuals/Media/Imaging/WriteableBitmap.cs +++ b/src/Avalonia.Visuals/Media/Imaging/WriteableBitmap.cs @@ -7,8 +7,15 @@ namespace Avalonia.Media.Imaging /// public class WriteableBitmap : Bitmap { - public WriteableBitmap(int width, int height, PixelFormat? format = null) - : base(AvaloniaLocator.Current.GetService().CreateWriteableBitmap(width, height, format)) + /// + /// Initializes a new instance of the class. + /// + /// The size of the bitmap in device pixels. + /// The DPI of the bitmap. + /// The pixel format (optional). + /// An . + public WriteableBitmap(PixelSize size, Vector dpi, PixelFormat? format = null) + : base(AvaloniaLocator.Current.GetService().CreateWriteableBitmap(size, dpi, format)) { } diff --git a/src/Avalonia.Visuals/Media/PixelSize.cs b/src/Avalonia.Visuals/Media/PixelSize.cs new file mode 100644 index 0000000000..3d0ae0b351 --- /dev/null +++ b/src/Avalonia.Visuals/Media/PixelSize.cs @@ -0,0 +1,174 @@ +// 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.Globalization; +using Avalonia.Utilities; + +namespace Avalonia +{ + /// + /// Represents a size in device pixels. + /// + public readonly struct PixelSize + { + /// + /// A size representing zero + /// + public static readonly PixelSize Empty = new PixelSize(0, 0); + + /// + /// Initializes a new instance of the structure. + /// + /// The width. + /// The height. + public PixelSize(int width, int height) + { + Width = width; + Height = height; + } + + /// + /// Gets the aspect ratio of the size. + /// + public double AspectRatio => (double)Width / Height; + + /// + /// Gets the width. + /// + public int Width { get; } + + /// + /// Gets the height. + /// + public int Height { get; } + + /// + /// Checks for equality between two s. + /// + /// The first size. + /// The second size. + /// True if the sizes are equal; otherwise false. + public static bool operator ==(PixelSize left, PixelSize right) + { + return left.Width == right.Width && left.Height == right.Height; + } + + /// + /// Checks for inequality between two s. + /// + /// The first size. + /// The second size. + /// True if the sizes are unequal; otherwise false. + public static bool operator !=(PixelSize left, PixelSize right) + { + return !(left == right); + } + + /// + /// Parses a string. + /// + /// The string. + /// The . + public static PixelSize Parse(string s) + { + using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Size")) + { + return new PixelSize( + tokenizer.ReadInt32(), + tokenizer.ReadInt32()); + } + } + + /// + /// Checks for equality between a size and an object. + /// + /// The object. + /// + /// True if is a size that equals the current size. + /// + public override bool Equals(object obj) + { + if (obj is PixelSize other) + { + return this == other; + } + + return false; + } + + /// + /// Returns a hash code for a . + /// + /// The hash code. + public override int GetHashCode() + { + unchecked + { + int hash = 17; + hash = (hash * 23) + Width.GetHashCode(); + hash = (hash * 23) + Height.GetHashCode(); + return hash; + } + } + + /// + /// Returns a new with the same height and the specified width. + /// + /// The width. + /// The new . + public PixelSize WithWidth(int width) => new PixelSize(width, Height); + + /// + /// Returns a new with the same width and the specified height. + /// + /// The height. + /// The new . + public PixelSize WithHeight(int height) => new PixelSize(Width, height); + + /// + /// Converts the to a device-independent using the + /// specified dots per inch (DPI). + /// + /// The dots per inch. + /// The device-independent size. + public Size ToSize(double dpi) => new Size(Width / (dpi / 96), Height / (dpi / 96)); + + /// + /// Converts the to a device-independent using the + /// specified dots per inch (DPI). + /// + /// The dots per inch. + /// The device-independent size. + public Size ToSize(Vector dpi) => new Size(Width / (dpi.X / 96), Height / (dpi.Y / 96)); + + /// + /// Converts a to device pixels using the specified dots per inch (DPI). + /// + /// The size. + /// The dots per inch. + /// The device-independent size. + public static PixelSize FromSize(Size size, double dpi) => new PixelSize( + (int)(size.Width * (dpi / 96)), + (int)(size.Height * (dpi / 96))); + + /// + /// Converts a to device pixels using the specified dots per inch (DPI). + /// + /// The size. + /// The dots per inch. + /// The device-independent size. + public static PixelSize FromSize(Size size, Vector dpi) => new PixelSize( + (int)(size.Width * (dpi.X / 96)), + (int)(size.Height * (dpi.Y / 96))); + + /// + /// Returns the string representation of the size. + /// + /// The string representation of the size. + public override string ToString() + { + return string.Format(CultureInfo.InvariantCulture, "{0}, {1}", Width, Height); + } + } +} diff --git a/src/Avalonia.Visuals/Platform/IBitmapImpl.cs b/src/Avalonia.Visuals/Platform/IBitmapImpl.cs index 6d19205b8f..1c8ac31955 100644 --- a/src/Avalonia.Visuals/Platform/IBitmapImpl.cs +++ b/src/Avalonia.Visuals/Platform/IBitmapImpl.cs @@ -12,14 +12,14 @@ namespace Avalonia.Platform public interface IBitmapImpl : IDisposable { /// - /// Gets the width of the bitmap, in pixels. + /// Gets the dots per inch (DPI) of the image. /// - int PixelWidth { get; } + Vector Dpi { get; } /// - /// Gets the height of the bitmap, in pixels. + /// Gets the size of the bitmap, in device pixels. /// - int PixelHeight { get; } + PixelSize PixelSize { get; } /// /// Version of the pixel data diff --git a/src/Avalonia.Visuals/Platform/ILockedFramebuffer.cs b/src/Avalonia.Visuals/Platform/ILockedFramebuffer.cs index 45ca1a5a99..2f631142d2 100644 --- a/src/Avalonia.Visuals/Platform/ILockedFramebuffer.cs +++ b/src/Avalonia.Visuals/Platform/ILockedFramebuffer.cs @@ -10,14 +10,9 @@ namespace Avalonia.Platform IntPtr Address { get; } /// - /// Framebuffer width + /// Gets the framebuffer size in device pixels. /// - int Width { get; } - - /// - /// Framebuffer height - /// - int Height { get; } + PixelSize Size{ get; } /// /// Number of bytes per row diff --git a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs index cc17efd2bb..aacdef0538 100644 --- a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs +++ b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs @@ -49,25 +49,19 @@ namespace Avalonia.Platform /// /// Creates a render target bitmap implementation. /// - /// The width of the bitmap. - /// The height of the bitmap. - /// The horizontal DPI of the bitmap. - /// The vertical DPI of the bitmap. + /// The size of the bitmap in device pixels. + /// The DPI of the bitmap. /// An . - IRenderTargetBitmapImpl CreateRenderTargetBitmap( - int width, - int height, - double dpiX, - double dpiY); + IRenderTargetBitmapImpl CreateRenderTargetBitmap(PixelSize size, Vector dpi); /// /// Creates a writeable bitmap implementation. /// - /// The width of the bitmap. - /// The height of the bitmap. + /// The size of the bitmap in device pixels. + /// The DPI of the bitmap. /// Pixel format (optional). /// An . - IWriteableBitmapImpl CreateWriteableBitmap(int width, int height, PixelFormat? format = null); + IWriteableBitmapImpl CreateWriteableBitmap(PixelSize size, Vector dpi, PixelFormat? format = null); /// /// Loads a bitmap implementation from a file.. @@ -84,14 +78,14 @@ namespace Avalonia.Platform IBitmapImpl LoadBitmap(Stream stream); /// - /// Loads a bitmap implementation from a pixels in memory.. + /// Loads a bitmap implementation from a pixels in memory. /// - /// Pixel format - /// Pointer to source bytes - /// Bitmap width - /// Bitmap height - /// Bytes per row + /// The pixel format. + /// The pointer to source bytes. + /// The size of the bitmap in device pixels. + /// The DPI of the bitmap. + /// The number of bytes per row. /// An . - IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, int width, int height, int stride); + IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, PixelSize size, Vector dpi, int stride); } } diff --git a/src/Avalonia.Visuals/Platform/LockedFramebuffer.cs b/src/Avalonia.Visuals/Platform/LockedFramebuffer.cs index c03b714956..c5b3c048b1 100644 --- a/src/Avalonia.Visuals/Platform/LockedFramebuffer.cs +++ b/src/Avalonia.Visuals/Platform/LockedFramebuffer.cs @@ -6,21 +6,19 @@ namespace Avalonia.Platform { private readonly Action _onDispose; - public LockedFramebuffer(IntPtr address, int width, int height, int rowBytes, Vector dpi, PixelFormat format, + public LockedFramebuffer(IntPtr address, PixelSize size, int rowBytes, Vector dpi, PixelFormat format, Action onDispose) { _onDispose = onDispose; Address = address; - Width = width; - Height = height; + Size = Size; RowBytes = rowBytes; Dpi = dpi; Format = format; } public IntPtr Address { get; } - public int Width { get; } - public int Height { get; } + public PixelSize Size { get; } public int RowBytes { get; } public Vector Dpi { get; } public PixelFormat Format { get; } @@ -30,4 +28,4 @@ namespace Avalonia.Platform _onDispose?.Invoke(); } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs index fc67b5c461..5a6f086f7e 100644 --- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs @@ -356,7 +356,7 @@ namespace Avalonia.Rendering foreach (var layer in scene.Layers) { var bitmap = Layers[layer.LayerRoot].Bitmap; - var sourceRect = new Rect(0, 0, bitmap.Item.PixelWidth, bitmap.Item.PixelHeight); + var sourceRect = new Rect(0, 0, bitmap.Item.PixelSize.Width, bitmap.Item.PixelSize.Height); if (layer.GeometryClip != null) { @@ -380,7 +380,7 @@ namespace Avalonia.Rendering if (_overlay != null) { - var sourceRect = new Rect(0, 0, _overlay.Item.PixelWidth, _overlay.Item.PixelHeight); + var sourceRect = new Rect(0, 0, _overlay.Item.PixelSize.Width, _overlay.Item.PixelSize.Height); context.DrawImage(_overlay, 0.5, sourceRect, clientRect); } @@ -432,8 +432,8 @@ namespace Avalonia.Rendering var pixelSize = size * scaling; if (_overlay == null || - _overlay.Item.PixelWidth != pixelSize.Width || - _overlay.Item.PixelHeight != pixelSize.Height) + _overlay.Item.PixelSize.Width != pixelSize.Width || + _overlay.Item.PixelSize.Height != pixelSize.Height) { _overlay?.Dispose(); _overlay = RefCountable.Create(parentContext.CreateLayer(size)); diff --git a/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs b/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs index cd0074c287..44d887241c 100644 --- a/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs +++ b/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs @@ -22,8 +22,7 @@ namespace Avalonia.Gtk3 height *= _factor; _surface = new ManagedCairoSurface(width, height); - Width = width; - Height = height; + Size = new PixelSize(width, height); Address = _surface.Buffer; RowBytes = _surface.Stride; Native.CairoSurfaceFlush(_surface.Surface); @@ -115,18 +114,17 @@ namespace Avalonia.Gtk3 if (_impl.CurrentCairoContext != IntPtr.Zero) Draw(_impl.CurrentCairoContext, _surface.Surface, _factor); else - DrawToWidget(_widget, _surface.Surface, Width, Height, _factor); + DrawToWidget(_widget, _surface.Surface, Size.Width, Size.Height, _factor); _surface.Dispose(); } else - _impl.SetNextRenderOperation(new RenderOp(_widget, _surface, _factor, Width, Height)); + _impl.SetNextRenderOperation(new RenderOp(_widget, _surface, _factor, Size.Width, Size.Height)); _surface = null; } } public IntPtr Address { get; } - public int Width { get; } - public int Height { get; } + public PixelSize Size { get; } public int RowBytes { get; } diff --git a/src/Gtk/Avalonia.Gtk3/X11Framebuffer.cs b/src/Gtk/Avalonia.Gtk3/X11Framebuffer.cs index dde29f85c2..c84d7c7541 100644 --- a/src/Gtk/Avalonia.Gtk3/X11Framebuffer.cs +++ b/src/Gtk/Avalonia.Gtk3/X11Framebuffer.cs @@ -13,12 +13,11 @@ namespace Avalonia.Gtk3 { _display = display; _xid = xid; - Width = width*factor; - Height = height*factor; - RowBytes = Width * 4; + Size = new PixelSize(width * factor, height * factor); + RowBytes = Size.Width * 4; Dpi = new Vector(96, 96) * factor; Format = PixelFormat.Bgra8888; - _blob = AvaloniaLocator.Current.GetService().AllocBlob(RowBytes * Height); + _blob = AvaloniaLocator.Current.GetService().AllocBlob(RowBytes * Size.Height); Address = _blob.Address; } @@ -26,8 +25,8 @@ namespace Avalonia.Gtk3 { var image = new X11.XImage(); int bitsPerPixel = 32; - image.width = Width; - image.height = Height; + image.width = Size.Width; + image.height = Size.Height; image.format = 2; //ZPixmap; image.data = Address; image.byte_order = 0;// LSBFirst; @@ -35,12 +34,12 @@ namespace Avalonia.Gtk3 image.bitmap_bit_order = 0;// LSBFirst; image.bitmap_pad = bitsPerPixel; image.depth = 24; - image.bytes_per_line = RowBytes - Width * 4; + image.bytes_per_line = RowBytes - Size.Width * 4; image.bits_per_pixel = bitsPerPixel; X11.XLockDisplay(_display); X11.XInitImage(ref image); var gc = X11.XCreateGC(_display, _xid, 0, IntPtr.Zero); - X11.XPutImage(_display, _xid, gc, ref image, 0, 0, 0, 0, (uint) Width, (uint) Height); + X11.XPutImage(_display, _xid, gc, ref image, 0, 0, 0, 0, (uint)Size.Width, (uint)Size.Height); X11.XFreeGC(_display, gc); X11.XSync(_display, true); X11.XUnlockDisplay(_display); @@ -48,8 +47,7 @@ namespace Avalonia.Gtk3 } public IntPtr Address { get; } - public int Width { get; } - public int Height { get; } + public PixelSize Size { get; } public int RowBytes { get; } public Vector Dpi { get; } public PixelFormat Format { get; } diff --git a/src/Linux/Avalonia.LinuxFramebuffer/LockedFramebuffer.cs b/src/Linux/Avalonia.LinuxFramebuffer/LockedFramebuffer.cs index 795d9648ea..ed59166eb8 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/LockedFramebuffer.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/LockedFramebuffer.cs @@ -19,7 +19,7 @@ namespace Avalonia.LinuxFramebuffer _address = address; Dpi = dpi; //Use double buffering to avoid flicker - Address = Marshal.AllocHGlobal(RowBytes * Height); + Address = Marshal.AllocHGlobal(RowBytes * Size.Height); } @@ -31,17 +31,16 @@ namespace Avalonia.LinuxFramebuffer public void Dispose() { VSync(); - NativeUnsafeMethods.memcpy(_address, Address, new IntPtr(RowBytes * Height)); + NativeUnsafeMethods.memcpy(_address, Address, new IntPtr(RowBytes * Size.Height)); Marshal.FreeHGlobal(Address); Address = IntPtr.Zero; } public IntPtr Address { get; private set; } - public int Width => (int)_varInfo.xres; - public int Height => (int) _varInfo.yres; + public PixelSize Size => new PixelSize((int)_varInfo.xres, (int) _varInfo.yres); public int RowBytes => (int) _fixedInfo.line_length; public Vector Dpi { get; } public PixelFormat Format => _varInfo.blue.offset == 16 ? PixelFormat.Rgba8888 : PixelFormat.Bgra8888; } -} \ No newline at end of file +} diff --git a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github index 8e4700d4b2..8abbe09592 160000 --- a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github +++ b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github @@ -1 +1 @@ -Subproject commit 8e4700d4b24935ed5400e5e0d6fce96b5b4a317a +Subproject commit 8abbe09592668efb573ac4d5548ba2d7e464ba78 diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index f43bddd4d5..1bfd7fd90d 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -142,7 +142,7 @@ namespace Avalonia.Skia public void DrawImage(IRef source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect) { PushOpacityMask(opacityMask, opacityMaskRect); - DrawImage(source, 1, new Rect(0, 0, source.Item.PixelWidth, source.Item.PixelHeight), destRect, BitmapInterpolationMode.Default); + DrawImage(source, 1, new Rect(0, 0, source.Item.PixelSize.Width, source.Item.PixelSize.Height), destRect, BitmapInterpolationMode.Default); PopOpacityMask(); } @@ -391,7 +391,7 @@ namespace Avalonia.Skia private void ConfigureTileBrush(ref PaintWrapper paintWrapper, Size targetSize, ITileBrush tileBrush, IDrawableBitmapImpl tileBrushImage) { var calc = new TileBrushCalculator(tileBrush, - new Size(tileBrushImage.PixelWidth, tileBrushImage.PixelHeight), targetSize); + new Size(tileBrushImage.PixelSize.Width, tileBrushImage.PixelSize.Height), targetSize); var intermediate = CreateRenderTarget( (int)calc.IntermediateSize.Width, @@ -401,7 +401,7 @@ namespace Avalonia.Skia using (var context = intermediate.CreateDrawingContext(null)) { - var rect = new Rect(0, 0, tileBrushImage.PixelWidth, tileBrushImage.PixelHeight); + var rect = new Rect(0, 0, tileBrushImage.PixelSize.Width, tileBrushImage.PixelSize.Height); context.Clear(Colors.Transparent); context.PushClip(calc.IntermediateClip); diff --git a/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs b/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs index 829b9097c9..b2c936d6ad 100644 --- a/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs @@ -41,7 +41,7 @@ namespace Avalonia.Skia public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) { var framebuffer = _platformSurface.Lock(); - var framebufferImageInfo = new SKImageInfo(framebuffer.Width, framebuffer.Height, + var framebufferImageInfo = new SKImageInfo(framebuffer.Size.Width, framebuffer.Size.Height, framebuffer.Format.ToSkColorType(), SKAlphaType.Premul); CreateSurface(framebufferImageInfo, framebuffer); diff --git a/src/Skia/Avalonia.Skia/ImmutableBitmap.cs b/src/Skia/Avalonia.Skia/ImmutableBitmap.cs index 9eb443caa3..f94a226bbc 100644 --- a/src/Skia/Avalonia.Skia/ImmutableBitmap.cs +++ b/src/Skia/Avalonia.Skia/ImmutableBitmap.cs @@ -31,22 +31,24 @@ namespace Avalonia.Skia throw new ArgumentException("Unable to load bitmap from provided data"); } - PixelWidth = _image.Width; - PixelHeight = _image.Height; + PixelSize = new PixelSize(_image.Width, _image.Height); + + // TODO: Skia doesn't have an API for DPI. + Dpi = new Vector(96, 96); } } /// /// Create immutable bitmap from given pixel data copy. /// - /// Width of data pixels. - /// Height of data pixels. + /// Size of the bitmap. + /// DPI of the bitmap. /// Stride of data pixels. /// Format of data pixels. /// Data pixels. - public ImmutableBitmap(int width, int height, int stride, PixelFormat format, IntPtr data) + public ImmutableBitmap(PixelSize size, Vector dpi, int stride, PixelFormat format, IntPtr data) { - var imageInfo = new SKImageInfo(width, height, format.ToSkColorType(), SKAlphaType.Premul); + var imageInfo = new SKImageInfo(size.Width, size.Height, format.ToSkColorType(), SKAlphaType.Premul); _image = SKImage.FromPixelCopy(imageInfo, data, stride); @@ -55,15 +57,12 @@ namespace Avalonia.Skia throw new ArgumentException("Unable to create bitmap from provided data"); } - PixelWidth = width; - PixelHeight = height; + PixelSize = size; + Dpi = dpi; } - /// - public int PixelWidth { get; } - - /// - public int PixelHeight { get; } + public Vector Dpi { get; } + public PixelSize PixelSize { get; } public int Version { get; } = 1; @@ -91,4 +90,4 @@ namespace Avalonia.Skia context.Canvas.DrawImage(_image, sourceRect, destRect, paint); } } -} \ No newline at end of file +} diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index 80e2608dbe..dfd277ec86 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -67,34 +67,28 @@ namespace Avalonia.Skia } /// - public IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, int width, int height, int stride) + public IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, PixelSize size, Vector dpi, int stride) { - return new ImmutableBitmap(width, height, stride, format, data); + return new ImmutableBitmap(size, dpi, stride, format, data); } /// - public IRenderTargetBitmapImpl CreateRenderTargetBitmap( - int width, - int height, - double dpiX, - double dpiY) + public IRenderTargetBitmapImpl CreateRenderTargetBitmap(PixelSize size, Vector dpi) { - if (width < 1) + if (size.Width < 1) { - throw new ArgumentException("Width can't be less than 1", nameof(width)); + throw new ArgumentException("Width can't be less than 1", nameof(size)); } - if (height < 1) + if (size.Height < 1) { - throw new ArgumentException("Height can't be less than 1", nameof(height)); + throw new ArgumentException("Height can't be less than 1", nameof(size)); } - var dpi = new Vector(dpiX, dpiY); - var createInfo = new SurfaceRenderTarget.CreateInfo { - Width = width, - Height = height, + Width = size.Width, + Height = size.Height, Dpi = dpi, DisableTextLcdRendering = false, GrContext = GrContext @@ -123,9 +117,9 @@ namespace Avalonia.Skia } /// - public IWriteableBitmapImpl CreateWriteableBitmap(int width, int height, PixelFormat? format = null) + public IWriteableBitmapImpl CreateWriteableBitmap(PixelSize size, Vector dpi, PixelFormat? format = null) { - return new WriteableBitmapImpl(width, height, format); + return new WriteableBitmapImpl(size, dpi, format); } } } diff --git a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs index a1e9f1d31e..6955cc666f 100644 --- a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs @@ -16,7 +16,6 @@ namespace Avalonia.Skia /// public class SurfaceRenderTarget : IRenderTargetBitmapImpl, IDrawableBitmapImpl { - private readonly Vector _dpi; private readonly SKSurface _surface; private readonly SKCanvas _canvas; private readonly bool _disableLcdRendering; @@ -28,12 +27,12 @@ namespace Avalonia.Skia /// Create info. public SurfaceRenderTarget(CreateInfo createInfo) { - PixelWidth = createInfo.Width; - PixelHeight = createInfo.Height; - _dpi = createInfo.Dpi; + PixelSize = new PixelSize(createInfo.Width, createInfo.Height); + Dpi = createInfo.Dpi; + _disableLcdRendering = createInfo.DisableTextLcdRendering; _grContext = createInfo.GrContext; - _surface = CreateSurface(createInfo.GrContext, PixelWidth, PixelHeight, createInfo.Format); + _surface = CreateSurface(createInfo.GrContext, PixelSize.Width, PixelSize.Height, createInfo.Format); _canvas = _surface?.Canvas; @@ -74,7 +73,7 @@ namespace Avalonia.Skia var createInfo = new DrawingContextImpl.CreateInfo { Canvas = _canvas, - Dpi = _dpi, + Dpi = Dpi, VisualBrushRenderer = visualBrushRenderer, DisableTextLcdRendering = _disableLcdRendering, GrContext = _grContext @@ -84,10 +83,10 @@ namespace Avalonia.Skia } /// - public int PixelWidth { get; } + public Vector Dpi { get; } /// - public int PixelHeight { get; } + public PixelSize PixelSize { get; } public int Version { get; private set; } = 1; diff --git a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs index 8ad454461c..36ccd7930a 100644 --- a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs +++ b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs @@ -22,13 +22,13 @@ namespace Avalonia.Skia /// /// Create new writeable bitmap. /// - /// Width. - /// Height. - /// Format. - public WriteableBitmapImpl(int width, int height, PixelFormat? format = null) + /// The size of the bitmap in device pixels. + /// The DPI of the bitmap. + /// The pixel format. + public WriteableBitmapImpl(PixelSize size, Vector dpi, PixelFormat? format = null) { - PixelHeight = height; - PixelWidth = width; + PixelSize = size; + Dpi = dpi; var colorType = PixelFormatHelper.ResolveColorType(format); @@ -38,24 +38,23 @@ namespace Avalonia.Skia { _bitmap = new SKBitmap(); - var nfo = new SKImageInfo(width, height, colorType, SKAlphaType.Premul); + var nfo = new SKImageInfo(size.Width, size.Height, colorType, SKAlphaType.Premul); var blob = runtimePlatform.AllocBlob(nfo.BytesSize); _bitmap.InstallPixels(nfo, blob.Address, nfo.RowBytes, null, s_releaseDelegate, blob); } else { - _bitmap = new SKBitmap(width, height, colorType, SKAlphaType.Premul); + _bitmap = new SKBitmap(size.Width, size.Height, colorType, SKAlphaType.Premul); } _bitmap.Erase(SKColor.Empty); } - /// - public int PixelWidth { get; } + public Vector Dpi { get; } /// - public int PixelHeight { get; } + public PixelSize PixelSize { get; } public int Version { get; private set; } = 1; @@ -146,10 +145,7 @@ namespace Avalonia.Skia public IntPtr Address => _bitmap.GetPixels(); /// - public int Width => _bitmap.Width; - - /// - public int Height => _bitmap.Height; + public PixelSize Size => new PixelSize(_bitmap.Width, _bitmap.Height); /// public int RowBytes => _bitmap.RowBytes; diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index c330377e20..3ec18dac5e 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -110,9 +110,9 @@ namespace Avalonia.Direct2D1 SharpDX.Configuration.EnableReleaseOnFinalizer = true; } - public IBitmapImpl CreateBitmap(int width, int height) + public IBitmapImpl CreateBitmap(PixelSize size, Vector dpi) { - return new WicBitmapImpl(width, height); + return new WicBitmapImpl(size, dpi); } public IFormattedTextImpl CreateFormattedText( @@ -159,18 +159,14 @@ namespace Avalonia.Direct2D1 throw new NotSupportedException("Don't know how to create a Direct2D1 renderer from any of provided surfaces"); } - public IRenderTargetBitmapImpl CreateRenderTargetBitmap( - int width, - int height, - double dpiX, - double dpiY) + public IRenderTargetBitmapImpl CreateRenderTargetBitmap(PixelSize size, Vector dpi) { - return new WicRenderTargetBitmapImpl(width, height, dpiX, dpiY); + return new WicRenderTargetBitmapImpl(size, dpi); } - public IWriteableBitmapImpl CreateWriteableBitmap(int width, int height, PixelFormat? format = null) + public IWriteableBitmapImpl CreateWriteableBitmap(PixelSize size, Vector dpi, PixelFormat? format = null) { - return new WriteableWicBitmapImpl(width, height, format); + return new WriteableWicBitmapImpl(size, dpi, format); } public IStreamGeometryImpl CreateStreamGeometry() @@ -188,9 +184,9 @@ namespace Avalonia.Direct2D1 return new WicBitmapImpl(stream); } - public IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, int width, int height, int stride) + public IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, PixelSize size, Vector dpi, int stride) { - return new WicBitmapImpl(format, data, width, height, stride); + return new WicBitmapImpl(format, data, size, dpi, stride); } } } diff --git a/src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs index 5ae174083c..f7e7ed3dc2 100644 --- a/src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs @@ -40,7 +40,7 @@ namespace Avalonia.Direct2D1 private readonly ILockedFramebuffer _target; public FramebufferShim(ILockedFramebuffer target) : - base(target.Width, target.Height, target.Dpi.X, target.Dpi.Y, target.Format) + base(target.Size, target.Dpi, target.Format) { _target = target; } @@ -51,7 +51,7 @@ namespace Avalonia.Direct2D1 { using (var l = WicImpl.Lock(BitmapLockFlags.Read)) { - for (var y = 0; y < _target.Height; y++) + for (var y = 0; y < _target.Size.Height; y++) { UnmanagedMethods.CopyMemory( (_target.Address + _target.RowBytes * y), diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index b842f26edb..efe26ba01a 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs @@ -330,12 +330,8 @@ namespace Avalonia.Direct2D1.Media { var platform = AvaloniaLocator.Current.GetService(); var dpi = new Vector(_deviceContext.DotsPerInch.Width, _deviceContext.DotsPerInch.Height); - var pixelSize = size * (dpi / 96); - return platform.CreateRenderTargetBitmap( - (int)pixelSize.Width, - (int)pixelSize.Height, - dpi.X, - dpi.Y); + var pixelSize = PixelSize.FromSize(size, dpi); + return platform.CreateRenderTargetBitmap(pixelSize, dpi); } } diff --git a/src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs index 3fc8760a9e..55877cefcb 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs @@ -20,7 +20,7 @@ namespace Avalonia.Direct2D1.Media BitmapImpl bitmap, Size targetSize) { - var calc = new TileBrushCalculator(brush, new Size(bitmap.PixelWidth, bitmap.PixelHeight), targetSize); + var calc = new TileBrushCalculator(brush, bitmap.PixelSize.ToSize(96), targetSize); if (!calc.NeedsIntermediate) { @@ -99,7 +99,7 @@ namespace Avalonia.Direct2D1.Media using (var context = new RenderTarget(result).CreateDrawingContext(null)) { - var rect = new Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight); + var rect = new Rect(0, 0, bitmap.PixelSize.Width, bitmap.PixelSize.Height); context.Clear(Colors.Transparent); context.PushClip(calc.IntermediateClip); diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs index 265f6f205a..af6d5c5e7b 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs @@ -7,8 +7,8 @@ namespace Avalonia.Direct2D1.Media { public abstract class BitmapImpl : IBitmapImpl, IDisposable { - public abstract int PixelWidth { get; } - public abstract int PixelHeight { get; } + public abstract Vector Dpi { get; } + public abstract PixelSize PixelSize { get; } public int Version { get; protected set; } = 1; public abstract OptionalDispose GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget target); diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs index b0edb9c3bf..1ee869ecb9 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs @@ -30,8 +30,8 @@ namespace Avalonia.Direct2D1.Media _direct2DBitmap = d2DBitmap ?? throw new ArgumentNullException(nameof(d2DBitmap)); } - public override int PixelWidth => _direct2DBitmap.PixelSize.Width; - public override int PixelHeight => _direct2DBitmap.PixelSize.Height; + public override Vector Dpi => _direct2DBitmap.DotsPerInch.ToAvaloniaVector(); + public override PixelSize PixelSize => _direct2DBitmap.PixelSize.ToAvalonia(); public override void Dispose() { diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs index bb43256657..72367ac50f 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs @@ -19,9 +19,6 @@ namespace Avalonia.Direct2D1.Media.Imaging _renderTarget = renderTarget; } - public override int PixelWidth => _renderTarget.PixelSize.Width; - public override int PixelHeight => _renderTarget.PixelSize.Height; - public static D2DRenderTargetBitmapImpl CreateCompatible( SharpDX.Direct2D1.RenderTarget renderTarget, Size size) diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs index d9fb00fa5b..176c3e0e23 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs @@ -44,10 +44,10 @@ namespace Avalonia.Direct2D1.Media /// /// Initializes a new instance of the class. /// - /// The width of the bitmap. - /// The height of the bitmap. + /// The size of the bitmap in device pixels. + /// The DPI of the bitmap. /// Pixel format - public WicBitmapImpl(int width, int height, APixelFormat? pixelFormat = null) + public WicBitmapImpl(PixelSize size, Vector dpi, APixelFormat? pixelFormat = null) { if (!pixelFormat.HasValue) { @@ -57,19 +57,22 @@ namespace Avalonia.Direct2D1.Media PixelFormat = pixelFormat; WicImpl = new Bitmap( Direct2D1Platform.ImagingFactory, - width, - height, + size.Width, + size.Height, pixelFormat.Value.ToWic(), BitmapCreateCacheOption.CacheOnLoad); + WicImpl.SetResolution(dpi.X, dpi.Y); } - public WicBitmapImpl(APixelFormat format, IntPtr data, int width, int height, int stride) + public WicBitmapImpl(APixelFormat format, IntPtr data, PixelSize size, Vector dpi, int stride) { - WicImpl = new Bitmap(Direct2D1Platform.ImagingFactory, width, height, format.ToWic(), BitmapCreateCacheOption.CacheOnDemand); + WicImpl = new Bitmap(Direct2D1Platform.ImagingFactory, size.Width, size.Height, format.ToWic(), BitmapCreateCacheOption.CacheOnDemand); + WicImpl.SetResolution(dpi.X, dpi.Y); + PixelFormat = format; using (var l = WicImpl.Lock(BitmapLockFlags.Write)) { - for (var row = 0; row < height; row++) + for (var row = 0; row < size.Height; row++) { UnmanagedMethods.CopyMemory( (l.Data.DataPointer + row * l.Stride), @@ -79,17 +82,18 @@ namespace Avalonia.Direct2D1.Media } } - protected APixelFormat? PixelFormat { get; } + public override Vector Dpi + { + get + { + WicImpl.GetResolution(out double x, out double y); + return new Vector(x, y); + } + } - /// - /// Gets the width of the bitmap, in pixels. - /// - public override int PixelWidth => WicImpl.Size.Width; + public override PixelSize PixelSize => WicImpl.Size.ToAvalonia(); - /// - /// Gets the height of the bitmap, in pixels. - /// - public override int PixelHeight => WicImpl.Size.Height; + protected APixelFormat? PixelFormat { get; } public override void Dispose() { diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs index d620266086..88e669357f 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs @@ -13,17 +13,15 @@ namespace Avalonia.Direct2D1.Media private readonly WicRenderTarget _renderTarget; public WicRenderTargetBitmapImpl( - int width, - int height, - double dpiX, - double dpiY, + PixelSize size, + Vector dpi, Platform.PixelFormat? pixelFormat = null) - : base(width, height, pixelFormat) + : base(size, dpi, pixelFormat) { var props = new RenderTargetProperties { - DpiX = (float)dpiX, - DpiY = (float)dpiY, + DpiX = (float)dpi.X, + DpiY = (float)dpi.Y, }; _renderTarget = new WicRenderTarget( diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs index 7930266f75..68f0f0e30d 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs @@ -10,8 +10,8 @@ namespace Avalonia.Direct2D1.Media.Imaging { class WriteableWicBitmapImpl : WicBitmapImpl, IWriteableBitmapImpl { - public WriteableWicBitmapImpl(int width, int height, PixelFormat? pixelFormat) - : base(width, height, pixelFormat) + public WriteableWicBitmapImpl(PixelSize size, Vector dpi, PixelFormat? pixelFormat) + : base(size, dpi, pixelFormat) { } @@ -36,8 +36,7 @@ namespace Avalonia.Direct2D1.Media.Imaging } public IntPtr Address => _lock.Data.DataPointer; - public int Width => _lock.Size.Width; - public int Height => _lock.Size.Height; + public PixelSize Size => _lock.Size.ToAvalonia(); public int RowBytes => _lock.Stride; public Vector Dpi { get; } = new Vector(96, 96); public PixelFormat Format => _format; diff --git a/src/Windows/Avalonia.Direct2D1/PrimitiveExtensions.cs b/src/Windows/Avalonia.Direct2D1/PrimitiveExtensions.cs index 124e33c5a3..5209014271 100644 --- a/src/Windows/Avalonia.Direct2D1/PrimitiveExtensions.cs +++ b/src/Windows/Avalonia.Direct2D1/PrimitiveExtensions.cs @@ -41,6 +41,10 @@ namespace Avalonia.Direct2D1 return new Rect(new Point(r.Left, r.Top), new Point(r.Right, r.Bottom)); } + public static PixelSize ToAvalonia(this Size2 p) => new PixelSize(p.Width, p.Height); + + public static Vector ToAvaloniaVector(this Size2F p) => new Vector(p.Width, p.Height); + public static RawRectangleF ToSharpDX(this Rect r) { return new RawRectangleF((float)r.X, (float)r.Y, (float)r.Right, (float)r.Bottom); diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WritableBitmapSurface.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WritableBitmapSurface.cs index 79340fa335..05fa9d9426 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/WritableBitmapSurface.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WritableBitmapSurface.cs @@ -58,8 +58,7 @@ namespace Avalonia.Win32.Interop.Wpf } public IntPtr Address => _bitmap.BackBuffer; - public int Width => _bitmap.PixelWidth; - public int Height => _bitmap.PixelHeight; + public PixelSize Size => new PixelSize(_bitmap.PixelWidth, _bitmap.PixelHeight); public int RowBytes => _bitmap.BackBufferStride; public Vector Dpi { get; } public PixelFormat Format => PixelFormat.Bgra8888; diff --git a/src/Windows/Avalonia.Win32/FramebufferManager.cs b/src/Windows/Avalonia.Win32/FramebufferManager.cs index bb0bde34ff..c910703181 100644 --- a/src/Windows/Avalonia.Win32/FramebufferManager.cs +++ b/src/Windows/Avalonia.Win32/FramebufferManager.cs @@ -21,11 +21,11 @@ namespace Avalonia.Win32 UnmanagedMethods.GetClientRect(_hwnd, out rc); var width = rc.right - rc.left; var height = rc.bottom - rc.top; - if ((_fb == null || _fb.Width != width || _fb.Height != height) && width > 0 && height > 0) + if ((_fb == null || _fb.Size.Width != width || _fb.Size.Height != height) && width > 0 && height > 0) { _fb?.Deallocate(); _fb = null; - _fb = new WindowFramebuffer(_hwnd, width, height); + _fb = new WindowFramebuffer(_hwnd, new PixelSize(width, height)); } return _fb; } diff --git a/src/Windows/Avalonia.Win32/WindowFramebuffer.cs b/src/Windows/Avalonia.Win32/WindowFramebuffer.cs index 6193ebd02b..9c331f662d 100644 --- a/src/Windows/Avalonia.Win32/WindowFramebuffer.cs +++ b/src/Windows/Avalonia.Win32/WindowFramebuffer.cs @@ -11,21 +11,21 @@ namespace Avalonia.Win32 private IUnmanagedBlob _bitmapBlob; private UnmanagedMethods.BITMAPINFOHEADER _bmpInfo; - public WindowFramebuffer(IntPtr handle, int width, int height) + public WindowFramebuffer(IntPtr handle, PixelSize size) { - if (width <= 0) - throw new ArgumentException("width is less than zero"); - if (height <= 0) - throw new ArgumentException("height is less than zero"); + if (size.Width <= 0) + throw new ArgumentException("Width is less than zero"); + if (size.Height <= 0) + throw new ArgumentException("Height is less than zero"); _handle = handle; _bmpInfo.Init(); _bmpInfo.biPlanes = 1; _bmpInfo.biBitCount = 32; _bmpInfo.Init(); - _bmpInfo.biWidth = width; - _bmpInfo.biHeight = -height; - _bitmapBlob = AvaloniaLocator.Current.GetService().AllocBlob(width * height * 4); + _bmpInfo.biWidth = size.Width; + _bmpInfo.biHeight = -size.Height; + _bitmapBlob = AvaloniaLocator.Current.GetService().AllocBlob(size.Width * size.Height * 4); } ~WindowFramebuffer() @@ -34,7 +34,7 @@ namespace Avalonia.Win32 } public IntPtr Address => _bitmapBlob.Address; - public int RowBytes => Width * 4; + public int RowBytes => Size.Width * 4; public PixelFormat Format => PixelFormat.Bgra8888; public Vector Dpi @@ -61,19 +61,17 @@ namespace Avalonia.Win32 } } - public int Width => _bmpInfo.biWidth; - - public int Height => -_bmpInfo.biHeight; + public PixelSize Size => new PixelSize(_bmpInfo.biWidth, _bmpInfo.biHeight); public void DrawToDevice(IntPtr hDC, int destX = 0, int destY = 0, int srcX = 0, int srcY = 0, int width = -1, int height = -1) { if (width == -1) - width = Width; + width = Size.Width; if (height == -1) - height = Height; + height = Size.Height; UnmanagedMethods.SetDIBitsToDevice(hDC, destX, destY, (uint) width, (uint) height, srcX, srcY, - 0, (uint)Height, _bitmapBlob.Address, ref _bmpInfo, 0); + 0, (uint)Size.Height, _bitmapBlob.Address, ref _bmpInfo, 0); } public bool DrawToWindow(IntPtr hWnd, int destX = 0, int destY = 0, int srcX = 0, int srcY = 0, int width = -1, diff --git a/src/iOS/Avalonia.iOS/EmulatedFramebuffer.cs b/src/iOS/Avalonia.iOS/EmulatedFramebuffer.cs index b3b41fe724..89c7aaf76c 100644 --- a/src/iOS/Avalonia.iOS/EmulatedFramebuffer.cs +++ b/src/iOS/Avalonia.iOS/EmulatedFramebuffer.cs @@ -23,12 +23,11 @@ namespace Avalonia.iOS var frame = view.Frame; _viewWidth = frame.Width; _viewHeight = frame.Height; - Width = (int) frame.Width * factor; - Height = (int) frame.Height * factor; - RowBytes = Width * 4; + Size = new PixelSize((int)frame.Width * factor, (int)frame.Height * factor); + RowBytes = Size.Width * 4; Dpi = new Vector(96, 96) * factor; Format = PixelFormat.Rgba8888; - Address = Marshal.AllocHGlobal(Height * RowBytes); + Address = Marshal.AllocHGlobal(Size.Height * RowBytes); } public void Dispose() @@ -37,7 +36,7 @@ namespace Avalonia.iOS return; var nfo = (int) CGBitmapFlags.ByteOrder32Big | (int) CGImageAlphaInfo.PremultipliedLast; using (var colorSpace = CGColorSpace.CreateDeviceRGB()) - using (var bContext = new CGBitmapContext(Address, Width, Height, 8, Width * 4, + using (var bContext = new CGBitmapContext(Address, Size.Width, Size.Height, 8, Size.Width * 4, colorSpace, (CGImageAlphaInfo) nfo)) using (var image = bContext.ToImage()) using (var context = UIGraphics.GetCurrentContext()) @@ -52,8 +51,7 @@ namespace Avalonia.iOS } public IntPtr Address { get; private set; } - public int Width { get; } - public int Height { get; } + public PixelSize Size { get; } public int RowBytes { get; } public Vector Dpi { get; } public PixelFormat Format { get; } diff --git a/tests/Avalonia.Controls.UnitTests/ImageTests.cs b/tests/Avalonia.Controls.UnitTests/ImageTests.cs index 7f2b5e77f9..e92fc572b4 100644 --- a/tests/Avalonia.Controls.UnitTests/ImageTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ImageTests.cs @@ -13,7 +13,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Measure_Should_Return_Correct_Size_For_No_Stretch() { - var bitmap = Mock.Of(x => x.PixelWidth == 50 && x.PixelHeight == 100); + var bitmap = Mock.Of(x => x.PixelSize == new PixelSize(50, 100)); var target = new Image(); target.Stretch = Stretch.None; target.Source = bitmap; @@ -26,7 +26,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Measure_Should_Return_Correct_Size_For_Fill_Stretch() { - var bitmap = Mock.Of(x => x.PixelWidth == 50 && x.PixelHeight == 100); + var bitmap = Mock.Of(x => x.PixelSize == new PixelSize(50, 100)); var target = new Image(); target.Stretch = Stretch.Fill; target.Source = bitmap; @@ -39,7 +39,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Measure_Should_Return_Correct_Size_For_Uniform_Stretch() { - var bitmap = Mock.Of(x => x.PixelWidth == 50 && x.PixelHeight == 100); + var bitmap = Mock.Of(x => x.PixelSize == new PixelSize(50, 100)); var target = new Image(); target.Stretch = Stretch.Uniform; target.Source = bitmap; @@ -52,7 +52,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Measure_Should_Return_Correct_Size_For_UniformToFill_Stretch() { - var bitmap = Mock.Of(x => x.PixelWidth == 50 && x.PixelHeight == 100); + var bitmap = Mock.Of(x => x.PixelSize == new PixelSize(50, 100)); var target = new Image(); target.Stretch = Stretch.UniformToFill; target.Source = bitmap; diff --git a/tests/Avalonia.RenderTests/Media/BitmapTests.cs b/tests/Avalonia.RenderTests/Media/BitmapTests.cs index 4cee05a0d9..e6cd800529 100644 --- a/tests/Avalonia.RenderTests/Media/BitmapTests.cs +++ b/tests/Avalonia.RenderTests/Media/BitmapTests.cs @@ -29,14 +29,13 @@ namespace Avalonia.Direct2D1.RenderTests.Media class Framebuffer : ILockedFramebuffer, IFramebufferPlatformSurface { - public Framebuffer(PixelFormat fmt, int width, int height) + public Framebuffer(PixelFormat fmt, PixelSize size) { Format = fmt; var bpp = fmt == PixelFormat.Rgb565 ? 2 : 4; - Width = width; - Height = height; - RowBytes = bpp * width; - Address = Marshal.AllocHGlobal(Height * RowBytes); + Size = size; + RowBytes = bpp * size.Width; + Address = Marshal.AllocHGlobal(size.Height * RowBytes); } public IntPtr Address { get; } @@ -45,12 +44,10 @@ namespace Avalonia.Direct2D1.RenderTests.Media public PixelFormat Format { get; } - public int Height { get; } + public PixelSize Size { get; } public int RowBytes { get; } - public int Width { get; } - public void Dispose() { //no-op @@ -74,7 +71,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media public void FramebufferRenderResultsShouldBeUsableAsBitmap(PixelFormat fmt) { var testName = nameof(FramebufferRenderResultsShouldBeUsableAsBitmap) + "_" + fmt; - var fb = new Framebuffer(fmt, 80, 80); + var fb = new Framebuffer(fmt, new PixelSize(80, 80)); var r = Avalonia.AvaloniaLocator.Current.GetService(); using (var target = r.CreateRenderTarget(new object[] { fb })) using (var ctx = target.CreateDrawingContext(null)) @@ -87,9 +84,9 @@ namespace Avalonia.Direct2D1.RenderTests.Media ctx.PopOpacity(); } - var bmp = new Bitmap(fmt, fb.Address, fb.Width, fb.Height, fb.RowBytes); + var bmp = new Bitmap(fmt, fb.Address, fb.Size, new Vector(96, 96), fb.RowBytes); fb.Deallocate(); - using (var rtb = new RenderTargetBitmap(100, 100)) + using (var rtb = new RenderTargetBitmap(new PixelSize(100, 100), new Vector(96, 96))) { using (var ctx = rtb.CreateDrawingContext(null)) { @@ -108,7 +105,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media [InlineData(PixelFormat.Bgra8888), InlineData(PixelFormat.Rgba8888)] public void WriteableBitmapShouldBeUsable(PixelFormat fmt) { - var writeableBitmap = new WriteableBitmap(256, 256, fmt); + var writeableBitmap = new WriteableBitmap(new PixelSize(256, 256), new Vector(96, 96), fmt); var data = new int[256 * 256]; for (int y = 0; y < 256; y++) diff --git a/tests/Avalonia.RenderTests/TestBase.cs b/tests/Avalonia.RenderTests/TestBase.cs index 19413b32eb..2892615e48 100644 --- a/tests/Avalonia.RenderTests/TestBase.cs +++ b/tests/Avalonia.RenderTests/TestBase.cs @@ -73,22 +73,21 @@ namespace Avalonia.Direct2D1.RenderTests var immediatePath = Path.Combine(OutputPath, testName + ".immediate.out.png"); var deferredPath = Path.Combine(OutputPath, testName + ".deferred.out.png"); var factory = AvaloniaLocator.Current.GetService(); + var pixelSize = new PixelSize((int)target.Width, (int)target.Height); + var size = new Size(target.Width, target.Height); + var dpi = new Vector(96, 96); - using (RenderTargetBitmap bitmap = new RenderTargetBitmap( - (int)target.Width, - (int)target.Height)) + using (RenderTargetBitmap bitmap = new RenderTargetBitmap(pixelSize, dpi)) { - Size size = new Size(target.Width, target.Height); target.Measure(size); target.Arrange(new Rect(size)); bitmap.Render(target); bitmap.Save(immediatePath); } - using (var rtb = factory.CreateRenderTargetBitmap((int)target.Width, (int)target.Height, 96, 96)) + using (var rtb = factory.CreateRenderTargetBitmap(pixelSize, dpi)) using (var renderer = new DeferredRenderer(target, rtb)) { - Size size = new Size(target.Width, target.Height); target.Measure(size); target.Arrange(new Rect(size)); renderer.UnitTestUpdateScene(); diff --git a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs index de2b517956..f8ad7e63f2 100644 --- a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs +++ b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs @@ -25,11 +25,7 @@ namespace Avalonia.UnitTests return Mock.Of(); } - public IRenderTargetBitmapImpl CreateRenderTargetBitmap( - int width, - int height, - double dpiX, - double dpiY) + public IRenderTargetBitmapImpl CreateRenderTargetBitmap(PixelSize size, Vector dpi) { return Mock.Of(); } @@ -39,7 +35,10 @@ namespace Avalonia.UnitTests return new MockStreamGeometryImpl(); } - public IWriteableBitmapImpl CreateWriteableBitmap(int width, int height, PixelFormat? format = default(PixelFormat?)) + public IWriteableBitmapImpl CreateWriteableBitmap( + PixelSize size, + Vector dpi, + PixelFormat? format = default(PixelFormat?)) { throw new NotImplementedException(); } @@ -54,7 +53,12 @@ namespace Avalonia.UnitTests return Mock.Of(); } - public IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, int width, int height, int stride) + public IBitmapImpl LoadBitmap( + PixelFormat format, + IntPtr data, + PixelSize size, + Vector dpi, + int stride) { throw new NotImplementedException(); } diff --git a/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs b/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs index 93b5a8a764..92c12c6b9e 100644 --- a/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs +++ b/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs @@ -24,7 +24,7 @@ namespace Avalonia.Visuals.UnitTests.VisualTree throw new NotImplementedException(); } - public IRenderTargetBitmapImpl CreateRenderTargetBitmap(int width, int height, double dpiX, double dpiY) + public IRenderTargetBitmapImpl CreateRenderTargetBitmap(PixelSize size, Vector dpi) { throw new NotImplementedException(); } @@ -44,12 +44,12 @@ namespace Avalonia.Visuals.UnitTests.VisualTree throw new NotImplementedException(); } - public IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, int width, int height, int stride) + public IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, PixelSize size, Vector dpi, int stride) { throw new NotImplementedException(); } - public IWriteableBitmapImpl CreateWriteableBitmap(int width, int height, PixelFormat? fmt) + public IWriteableBitmapImpl CreateWriteableBitmap(PixelSize size, Vector dpi, PixelFormat? fmt) { throw new NotImplementedException(); }