diff --git a/src/Avalonia.Base/Media/Imaging/WriteableBitmap.cs b/src/Avalonia.Base/Media/Imaging/WriteableBitmap.cs index 73dcf6365b..435e51009d 100644 --- a/src/Avalonia.Base/Media/Imaging/WriteableBitmap.cs +++ b/src/Avalonia.Base/Media/Imaging/WriteableBitmap.cs @@ -20,7 +20,7 @@ namespace Avalonia.Media.Imaging /// The DPI of the bitmap. /// The pixel format (optional). /// The alpha format (optional). - /// An . + /// An instance of the class. public WriteableBitmap(PixelSize size, Vector dpi, PixelFormat? format = null, AlphaFormat? alphaFormat = null) : this(CreatePlatformImpl(size, dpi, format, alphaFormat)) { diff --git a/src/Avalonia.Controls.ColorPicker/Avalonia.Controls.ColorPicker.csproj b/src/Avalonia.Controls.ColorPicker/Avalonia.Controls.ColorPicker.csproj index 0c11339be8..a34b91b6e6 100644 --- a/src/Avalonia.Controls.ColorPicker/Avalonia.Controls.ColorPicker.csproj +++ b/src/Avalonia.Controls.ColorPicker/Avalonia.Controls.ColorPicker.csproj @@ -1,6 +1,7 @@  net6.0;netstandard2.0 + true diff --git a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs index 31ac18100e..5ee8136d26 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs @@ -1,4 +1,6 @@ using System; +using System.Buffers; +using Avalonia.Collections.Pooled; using Avalonia.Controls.Metadata; using Avalonia.Layout; using Avalonia.Media; @@ -32,7 +34,7 @@ namespace Avalonia.Controls.Primitives protected bool ignorePropertyChanged = false; - private WriteableBitmap? _backgroundBitmap; + private Bitmap? _backgroundBitmap; /// /// Initializes a new instance of the class. @@ -114,7 +116,10 @@ namespace Avalonia.Controls.Primitives if (pixelWidth != 0 && pixelHeight != 0) { - ArrayList bgraPixelData = await ColorPickerHelpers.CreateComponentBitmapAsync( + // siteToCapacity = true, because CreateComponentBitmapAsync sets bytes via indexer over pre-allocated buffer. + using var bgraPixelData = new PooledList(pixelWidth * pixelHeight * 4, ClearMode.Never, true); + await ColorPickerHelpers.CreateComponentBitmapAsync( + bgraPixelData, pixelWidth, pixelHeight, Orientation, @@ -124,23 +129,8 @@ namespace Avalonia.Controls.Primitives IsAlphaVisible, IsPerceptive); - if (_backgroundBitmap != null) - { - // TODO: CURRENTLY DISABLED DUE TO INTERMITTENT CRASHES IN SKIA/RENDERER - // - // Re-use the existing WriteableBitmap - // This assumes the height, width and byte counts are the same and must be set to null - // elsewhere if that assumption is ever not true. - // ColorPickerHelpers.UpdateBitmapFromPixelData(_backgroundBitmap, bgraPixelData); - - // TODO: ALSO DISABLED DISPOSE DUE TO INTERMITTENT CRASHES - //_backgroundBitmap?.Dispose(); - _backgroundBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraPixelData, pixelWidth, pixelHeight); - } - else - { - _backgroundBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraPixelData, pixelWidth, pixelHeight); - } + _backgroundBitmap?.Dispose(); + _backgroundBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraPixelData, pixelWidth, pixelHeight); Background = new ImageBrush(_backgroundBitmap); } diff --git a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs index deb0dfb6dd..7ddd6d15ec 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using Avalonia.Collections.Pooled; using Avalonia.Controls.Metadata; using Avalonia.Controls.Shapes; using Avalonia.Input; @@ -64,19 +65,19 @@ namespace Avalonia.Controls.Primitives private Panel? _selectionEllipsePanel; // Put the spectrum images in a bitmap, which is then given to an ImageBrush. - private WriteableBitmap? _hueRedBitmap; - private WriteableBitmap? _hueYellowBitmap; - private WriteableBitmap? _hueGreenBitmap; - private WriteableBitmap? _hueCyanBitmap; - private WriteableBitmap? _hueBlueBitmap; - private WriteableBitmap? _huePurpleBitmap; + private Bitmap? _hueRedBitmap; + private Bitmap? _hueYellowBitmap; + private Bitmap? _hueGreenBitmap; + private Bitmap? _hueCyanBitmap; + private Bitmap? _hueBlueBitmap; + private Bitmap? _huePurpleBitmap; - private WriteableBitmap? _saturationMinimumBitmap; - private WriteableBitmap? _saturationMaximumBitmap; + private Bitmap? _saturationMinimumBitmap; + private Bitmap? _saturationMaximumBitmap; - private WriteableBitmap? _valueBitmap; - private WriteableBitmap? _minBitmap; - private WriteableBitmap? _maxBitmap; + private Bitmap? _valueBitmap; + private Bitmap? _minBitmap; + private Bitmap? _maxBitmap; // Fields used by UpdateEllipse() to ensure that it's using the data // associated with the last call to CreateBitmapsAndColorMap(), @@ -1101,17 +1102,7 @@ namespace Avalonia.Controls.Primitives } Hsv hsv = new Hsv(hsvColor); - - // The middle 4 are only needed and used in the case of hue as the third dimension. - // Saturation and luminosity need only a min and max. - ArrayList bgraMinPixelData; - ArrayList bgraMiddle1PixelData; - ArrayList bgraMiddle2PixelData; - ArrayList bgraMiddle3PixelData; - ArrayList bgraMiddle4PixelData; - ArrayList bgraMaxPixelData; - List newHsvValues; - + // In Avalonia, Bounds returns the actual device-independent pixel size of a control. // However, this is not necessarily the size of the control rendered on a display. // A desktop or application scaling factor may be applied which must be accounted for here. @@ -1121,27 +1112,20 @@ namespace Avalonia.Controls.Primitives int pixelDimension = (int)Math.Round(minDimension * scale); var pixelCount = pixelDimension * pixelDimension; var pixelDataSize = pixelCount * 4; - - bgraMinPixelData = new ArrayList(pixelDataSize); - bgraMaxPixelData = new ArrayList(pixelDataSize); - newHsvValues = new List(pixelCount); - // We'll only save pixel data for the middle bitmaps if our third dimension is hue. - if (components == ColorSpectrumComponents.ValueSaturation || - components == ColorSpectrumComponents.SaturationValue) - { - bgraMiddle1PixelData = new ArrayList(pixelDataSize); - bgraMiddle2PixelData = new ArrayList(pixelDataSize); - bgraMiddle3PixelData = new ArrayList(pixelDataSize); - bgraMiddle4PixelData = new ArrayList(pixelDataSize); - } - else - { - bgraMiddle1PixelData = new ArrayList(0); - bgraMiddle2PixelData = new ArrayList(0); - bgraMiddle3PixelData = new ArrayList(0); - bgraMiddle4PixelData = new ArrayList(0); - } + var middleBitmapsSize = + components is ColorSpectrumComponents.ValueSaturation or ColorSpectrumComponents.SaturationValue + ? pixelDataSize : 0; + + var newHsvValues = new List(pixelCount); + using var bgraMinPixelData = new PooledList(pixelDataSize, ClearMode.Never); + using var bgraMaxPixelData = new PooledList(pixelDataSize, ClearMode.Never); + // The middle 4 are only needed and used in the case of hue as the third dimension. + // Saturation and luminosity need only a min and max. + using var bgraMiddle1PixelData = new PooledList(middleBitmapsSize, ClearMode.Never); + using var bgraMiddle2PixelData = new PooledList(middleBitmapsSize, ClearMode.Never); + using var bgraMiddle3PixelData = new PooledList(middleBitmapsSize, ClearMode.Never); + using var bgraMiddle4PixelData = new PooledList(middleBitmapsSize, ClearMode.Never); await Task.Run(() => { @@ -1187,7 +1171,7 @@ namespace Avalonia.Controls.Primitives } }); - Dispatcher.UIThread.Post(() => + await Dispatcher.UIThread.InvokeAsync(() => { int pixelWidth = pixelDimension; int pixelHeight = pixelDimension; @@ -1201,20 +1185,29 @@ namespace Avalonia.Controls.Primitives { case ColorSpectrumComponents.HueValue: case ColorSpectrumComponents.ValueHue: + _saturationMinimumBitmap?.Dispose(); _saturationMinimumBitmap = _minBitmap; + _saturationMaximumBitmap?.Dispose(); _saturationMaximumBitmap = _maxBitmap; break; case ColorSpectrumComponents.HueSaturation: case ColorSpectrumComponents.SaturationHue: + _valueBitmap?.Dispose(); _valueBitmap = _maxBitmap; break; case ColorSpectrumComponents.ValueSaturation: case ColorSpectrumComponents.SaturationValue: + _hueRedBitmap?.Dispose(); _hueRedBitmap = _minBitmap; + _hueYellowBitmap?.Dispose(); _hueYellowBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraMiddle1PixelData, pixelWidth, pixelHeight); + _hueGreenBitmap?.Dispose(); _hueGreenBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraMiddle2PixelData, pixelWidth, pixelHeight); + _hueCyanBitmap?.Dispose(); _hueCyanBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraMiddle3PixelData, pixelWidth, pixelHeight); + _hueBlueBitmap?.Dispose(); _hueBlueBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraMiddle4PixelData, pixelWidth, pixelHeight); + _huePurpleBitmap?.Dispose(); _huePurpleBitmap = _maxBitmap; break; } @@ -1249,12 +1242,12 @@ namespace Avalonia.Controls.Primitives double maxSaturation, double minValue, double maxValue, - ArrayList bgraMinPixelData, - ArrayList bgraMiddle1PixelData, - ArrayList bgraMiddle2PixelData, - ArrayList bgraMiddle3PixelData, - ArrayList bgraMiddle4PixelData, - ArrayList bgraMaxPixelData, + PooledList bgraMinPixelData, + PooledList bgraMiddle1PixelData, + PooledList bgraMiddle2PixelData, + PooledList bgraMiddle3PixelData, + PooledList bgraMiddle4PixelData, + PooledList bgraMaxPixelData, List newHsvValues) { double hMin = minHue; @@ -1409,12 +1402,12 @@ namespace Avalonia.Controls.Primitives double maxSaturation, double minValue, double maxValue, - ArrayList bgraMinPixelData, - ArrayList bgraMiddle1PixelData, - ArrayList bgraMiddle2PixelData, - ArrayList bgraMiddle3PixelData, - ArrayList bgraMiddle4PixelData, - ArrayList bgraMaxPixelData, + PooledList bgraMinPixelData, + PooledList bgraMiddle1PixelData, + PooledList bgraMiddle2PixelData, + PooledList bgraMiddle3PixelData, + PooledList bgraMiddle4PixelData, + PooledList bgraMaxPixelData, List newHsvValues) { double hMin = minHue; diff --git a/src/Avalonia.Controls.ColorPicker/Helpers/ArrayList.cs b/src/Avalonia.Controls.ColorPicker/Helpers/ArrayList.cs deleted file mode 100644 index 0b4c2f8579..0000000000 --- a/src/Avalonia.Controls.ColorPicker/Helpers/ArrayList.cs +++ /dev/null @@ -1,71 +0,0 @@ -namespace Avalonia.Controls.Primitives -{ - /// - /// A thin wrapper over an that allows some additional list-like functionality. - /// - /// - /// This is only for internal ColorPicker-related functionality and should not be used elsewhere. - /// It is added for performance to enjoy the simplicity of the IList.Add() method without requiring - /// an additional copy to turn a list into an array for bitmaps. - /// - /// The type of items in the array. - internal class ArrayList - { - private int _nextIndex = 0; - - /// - /// Initializes a new instance of the class. - /// - public ArrayList(int capacity) - { - Capacity = capacity; - Array = new T[capacity]; - } - - /// - /// Provides access to the underlying array by index. - /// This exists for simplification and the property - /// may also be used. - /// - /// The index of the item to get or set. - /// The item at the given index. - public T this[int i] - { - get => Array[i]; - set => Array[i] = value; - } - - /// - /// Gets the underlying array. - /// - public T[] Array { get; private set; } - - /// - /// Gets the fixed capacity/size of the array. - /// This must be set during construction. - /// - public int Capacity { get; private set; } - - /// - /// Adds the given item to the array at the next available index. - /// WARNING: This must be used carefully and only once, in sequence. - /// - /// The item to add. - public void Add(T item) - { - if (_nextIndex >= 0 && - _nextIndex < Capacity) - { - Array[_nextIndex] = item; - _nextIndex++; - } - else - { - // If necessary an exception could be thrown here - // throw new IndexOutOfRangeException(); - } - - return; - } - } -} diff --git a/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs b/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs index c2332751af..71c6b5c178 100644 --- a/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs +++ b/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs @@ -6,6 +6,7 @@ using System; using System.Runtime.InteropServices; using System.Threading.Tasks; +using Avalonia.Collections.Pooled; using Avalonia.Layout; using Avalonia.Media; using Avalonia.Media.Imaging; @@ -34,7 +35,8 @@ namespace Avalonia.Controls.Primitives /// Whether the slider adapts rendering to improve user-perception over exactness. /// This will ensure colors are always discernible. /// A new bitmap representing a gradient of color component values. - public static async Task> CreateComponentBitmapAsync( + public static Task CreateComponentBitmapAsync( + PooledList bgraPixelData, int width, int height, Orientation orientation, @@ -46,22 +48,19 @@ namespace Avalonia.Controls.Primitives { if (width == 0 || height == 0) { - return new ArrayList(0); + return Task.CompletedTask; } - var bitmap = await Task.Run>(() => + return Task.Run(() => { int pixelDataIndex = 0; double componentStep; - ArrayList bgraPixelData; Color baseRgbColor = Colors.White; Color rgbColor; int bgraPixelDataHeight; int bgraPixelDataWidth; - // Allocate the buffer // BGRA formatted color components 1 byte each (4 bytes in a pixel) - bgraPixelData = new ArrayList(width * height * 4); bgraPixelDataHeight = height * 4; bgraPixelDataWidth = width * 4; @@ -318,8 +317,6 @@ namespace Avalonia.Controls.Primitives return bgraPixelData; }); - - return bitmap; } public static Hsv IncrementColorComponent( @@ -619,45 +616,17 @@ namespace Avalonia.Controls.Primitives /// The pixel width of the bitmap. /// The pixel height of the bitmap. /// A new . - public static WriteableBitmap CreateBitmapFromPixelData( - ArrayList bgraPixelData, + public static unsafe Bitmap CreateBitmapFromPixelData( + PooledList bgraPixelData, int pixelWidth, int pixelHeight) { - // Standard may need to change on some devices - Vector dpi = new Vector(96, 96); - - var bitmap = new WriteableBitmap( - new PixelSize(pixelWidth, pixelHeight), - dpi, - PixelFormat.Bgra8888, - AlphaFormat.Premul); - - using (var frameBuffer = bitmap.Lock()) + fixed (byte* array = bgraPixelData.Span) { - Marshal.Copy(bgraPixelData.Array, 0, frameBuffer.Address, bgraPixelData.Array.Length); + return new Bitmap(PixelFormat.Bgra8888, AlphaFormat.Premul, new IntPtr(array), + new PixelSize(pixelWidth, pixelHeight), + new Vector(96, 96), pixelWidth * 4); } - - return bitmap; - } - - /// - /// Updates the given with new, raw BGRA pre-multiplied alpha pixel data. - /// TODO: THIS METHOD IS CURRENTLY PROVIDED AS REFERENCE BUT CAUSES INTERMITTENT CRASHES IF USED. - /// WARNING: The bitmap's width, height and byte count MUST not have changed and MUST be enforced externally. - /// - /// The existing to update. - /// The bitmap (in raw BGRA pre-multiplied alpha pixels). - public static void UpdateBitmapFromPixelData( - WriteableBitmap bitmap, - ArrayList bgraPixelData) - { - using (var frameBuffer = bitmap.Lock()) - { - Marshal.Copy(bgraPixelData.Array, 0, frameBuffer.Address, bgraPixelData.Array.Length); - } - - return; } } } diff --git a/src/Windows/Avalonia.Win32/TrayIconImpl.cs b/src/Windows/Avalonia.Win32/TrayIconImpl.cs index 606905d5e8..9d2b274a2b 100644 --- a/src/Windows/Avalonia.Win32/TrayIconImpl.cs +++ b/src/Windows/Avalonia.Win32/TrayIconImpl.cs @@ -16,9 +16,7 @@ namespace Avalonia.Win32 { internal class TrayIconImpl : ITrayIconImpl { - private static readonly Win32Icon s_emptyIcon = new(new WriteableBitmap(new PixelSize(32, 32), - new Vector(96, 96), - PixelFormats.Bgra8888, AlphaFormat.Unpremul)); + private static readonly Win32Icon s_emptyIcon; private readonly int _uniqueId; private static int s_nextUniqueId; private bool _iconAdded; @@ -29,6 +27,13 @@ namespace Avalonia.Win32 private bool _disposedValue; private static readonly uint WM_TASKBARCREATED = RegisterWindowMessage("TaskbarCreated"); + static TrayIconImpl() + { + using var bitmap = new WriteableBitmap( + new PixelSize(32, 32), new Vector(96, 96), PixelFormats.Bgra8888, AlphaFormat.Unpremul); + s_emptyIcon = new Win32Icon(bitmap); + } + public TrayIconImpl() { _exporter = new Win32NativeToManagedMenuExporter();