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();