Browse Source

Add new ArrayList to avoid an extra copy when creating spectrum bitmaps

pull/9140/head
robloo 4 years ago
parent
commit
ca7543f1d6
  1. 8
      src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs
  2. 61
      src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs
  3. 71
      src/Avalonia.Controls.ColorPicker/Helpers/ArrayList.cs
  4. 18
      src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs

8
src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs

@ -121,7 +121,7 @@ namespace Avalonia.Controls.Primitives
if (pixelWidth != 0 && pixelHeight != 0) if (pixelWidth != 0 && pixelHeight != 0)
{ {
var bitmap = await ColorPickerHelpers.CreateComponentBitmapAsync( ArrayList<byte> bgraPixelData = await ColorPickerHelpers.CreateComponentBitmapAsync(
pixelWidth, pixelWidth,
pixelHeight, pixelHeight,
Orientation, Orientation,
@ -131,18 +131,18 @@ namespace Avalonia.Controls.Primitives
IsAlphaMaxForced, IsAlphaMaxForced,
IsSaturationValueMaxForced); IsSaturationValueMaxForced);
if (bitmap != null) if (bgraPixelData != null)
{ {
if (_backgroundBitmap != null) if (_backgroundBitmap != null)
{ {
// Re-use the existing WriteableBitmap // Re-use the existing WriteableBitmap
// This assumes the height, width and byte counts are the same and must be set to null // 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. // elsewhere if that assumption is ever not true.
ColorPickerHelpers.UpdateBitmapFromPixelData(_backgroundBitmap, bitmap); ColorPickerHelpers.UpdateBitmapFromPixelData(_backgroundBitmap, bgraPixelData);
} }
else else
{ {
_backgroundBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bitmap, pixelWidth, pixelHeight); _backgroundBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraPixelData, pixelWidth, pixelHeight);
} }
Background = new ImageBrush(_backgroundBitmap); Background = new ImageBrush(_backgroundBitmap);

61
src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs

@ -994,13 +994,13 @@ namespace Avalonia.Controls.Primitives
// The middle 4 are only needed and used in the case of hue as the third dimension. // 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. // Saturation and luminosity need only a min and max.
List<byte> bgraMinPixelData = new List<byte>(); ArrayList<byte> bgraMinPixelData;
List<byte> bgraMiddle1PixelData = new List<byte>(); ArrayList<byte> bgraMiddle1PixelData;
List<byte> bgraMiddle2PixelData = new List<byte>(); ArrayList<byte> bgraMiddle2PixelData;
List<byte> bgraMiddle3PixelData = new List<byte>(); ArrayList<byte> bgraMiddle3PixelData;
List<byte> bgraMiddle4PixelData = new List<byte>(); ArrayList<byte> bgraMiddle4PixelData;
List<byte> bgraMaxPixelData = new List<byte>(); ArrayList<byte> bgraMaxPixelData;
List<Hsv> newHsvValues = new List<Hsv>(); List<Hsv> newHsvValues;
// In Avalonia, Bounds returns the actual device-independent pixel size of a control. // 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. // However, this is not necessarily the size of the control rendered on a display.
@ -1011,20 +1011,27 @@ namespace Avalonia.Controls.Primitives
int pixelDimension = (int)Math.Round(minDimension * scale); int pixelDimension = (int)Math.Round(minDimension * scale);
var pixelCount = pixelDimension * pixelDimension; var pixelCount = pixelDimension * pixelDimension;
var pixelDataSize = pixelCount * 4; var pixelDataSize = pixelCount * 4;
bgraMinPixelData.Capacity = pixelDataSize;
bgraMinPixelData = new ArrayList<byte>(pixelDataSize);
bgraMaxPixelData = new ArrayList<byte>(pixelDataSize);
newHsvValues = new List<Hsv>(pixelCount);
// We'll only save pixel data for the middle bitmaps if our third dimension is hue. // We'll only save pixel data for the middle bitmaps if our third dimension is hue.
if (components == ColorSpectrumComponents.ValueSaturation || if (components == ColorSpectrumComponents.ValueSaturation ||
components == ColorSpectrumComponents.SaturationValue) components == ColorSpectrumComponents.SaturationValue)
{ {
bgraMiddle1PixelData.Capacity = pixelDataSize; bgraMiddle1PixelData = new ArrayList<byte>(pixelDataSize);
bgraMiddle2PixelData.Capacity = pixelDataSize; bgraMiddle2PixelData = new ArrayList<byte>(pixelDataSize);
bgraMiddle3PixelData.Capacity = pixelDataSize; bgraMiddle3PixelData = new ArrayList<byte>(pixelDataSize);
bgraMiddle4PixelData.Capacity = pixelDataSize; bgraMiddle4PixelData = new ArrayList<byte>(pixelDataSize);
}
else
{
bgraMiddle1PixelData = new ArrayList<byte>(0);
bgraMiddle2PixelData = new ArrayList<byte>(0);
bgraMiddle3PixelData = new ArrayList<byte>(0);
bgraMiddle4PixelData = new ArrayList<byte>(0);
} }
bgraMaxPixelData.Capacity = pixelDataSize;
newHsvValues.Capacity = pixelCount;
await Task.Run(() => await Task.Run(() =>
{ {
@ -1132,12 +1139,12 @@ namespace Avalonia.Controls.Primitives
double maxSaturation, double maxSaturation,
double minValue, double minValue,
double maxValue, double maxValue,
List<byte> bgraMinPixelData, ArrayList<byte> bgraMinPixelData,
List<byte> bgraMiddle1PixelData, ArrayList<byte> bgraMiddle1PixelData,
List<byte> bgraMiddle2PixelData, ArrayList<byte> bgraMiddle2PixelData,
List<byte> bgraMiddle3PixelData, ArrayList<byte> bgraMiddle3PixelData,
List<byte> bgraMiddle4PixelData, ArrayList<byte> bgraMiddle4PixelData,
List<byte> bgraMaxPixelData, ArrayList<byte> bgraMaxPixelData,
List<Hsv> newHsvValues) List<Hsv> newHsvValues)
{ {
double hMin = minHue; double hMin = minHue;
@ -1292,12 +1299,12 @@ namespace Avalonia.Controls.Primitives
double maxSaturation, double maxSaturation,
double minValue, double minValue,
double maxValue, double maxValue,
List<byte> bgraMinPixelData, ArrayList<byte> bgraMinPixelData,
List<byte> bgraMiddle1PixelData, ArrayList<byte> bgraMiddle1PixelData,
List<byte> bgraMiddle2PixelData, ArrayList<byte> bgraMiddle2PixelData,
List<byte> bgraMiddle3PixelData, ArrayList<byte> bgraMiddle3PixelData,
List<byte> bgraMiddle4PixelData, ArrayList<byte> bgraMiddle4PixelData,
List<byte> bgraMaxPixelData, ArrayList<byte> bgraMaxPixelData,
List<Hsv> newHsvValues) List<Hsv> newHsvValues)
{ {
double hMin = minHue; double hMin = minHue;

71
src/Avalonia.Controls.ColorPicker/Helpers/ArrayList.cs

@ -0,0 +1,71 @@
namespace Avalonia.Controls.Primitives
{
/// <summary>
/// A thin wrapper over an <see cref="System.Array"/> that allows some additional list-like functionality.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
/// <typeparam name="T">The type of items in the array.</typeparam>
internal class ArrayList<T>
{
private int _nextIndex = 0;
/// <summary>
/// Initializes a new instance of the <see cref="ArrayList{T}"/> class.
/// </summary>
public ArrayList(int capacity)
{
Capacity = capacity;
Array = new T[capacity];
}
/// <summary>
/// Provides access to the underlying array by index.
/// This exists for simplification and the <see cref="Array"/> property
/// may also be used.
/// </summary>
/// <param name="i">The index of the item to get or set.</param>
/// <returns>The item at the given index.</returns>
public T this[int i]
{
get => Array[i];
set => Array[i] = value;
}
/// <summary>
/// Gets the underlying array.
/// </summary>
public T[] Array { get; private set; }
/// <summary>
/// Gets the fixed capacity/size of the array.
/// This must be set during construction.
/// </summary>
public int Capacity { get; private set; }
/// <summary>
/// Adds the given item to the array at the next available index.
/// WARNING: This must be used carefully and only once, in sequence.
/// </summary>
/// <param name="item">The item to add.</param>
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;
}
}
}

18
src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs

@ -37,7 +37,7 @@ namespace Avalonia.Controls.Primitives
/// during calculation with the HSVA color model. /// during calculation with the HSVA color model.
/// This will ensure colors are always discernible regardless of saturation/value.</param> /// This will ensure colors are always discernible regardless of saturation/value.</param>
/// <returns>A new bitmap representing a gradient of color component values.</returns> /// <returns>A new bitmap representing a gradient of color component values.</returns>
public static async Task<byte[]> CreateComponentBitmapAsync( public static async Task<ArrayList<byte>> CreateComponentBitmapAsync(
int width, int width,
int height, int height,
Orientation orientation, Orientation orientation,
@ -49,14 +49,14 @@ namespace Avalonia.Controls.Primitives
{ {
if (width == 0 || height == 0) if (width == 0 || height == 0)
{ {
return new byte[0]; return new ArrayList<byte>(0);
} }
var bitmap = await Task.Run<byte[]>(() => var bitmap = await Task.Run<ArrayList<byte>>(() =>
{ {
int pixelDataIndex = 0; int pixelDataIndex = 0;
double componentStep; double componentStep;
byte[] bgraPixelData; ArrayList<byte> bgraPixelData;
Color baseRgbColor = Colors.White; Color baseRgbColor = Colors.White;
Color rgbColor; Color rgbColor;
int bgraPixelDataHeight; int bgraPixelDataHeight;
@ -64,7 +64,7 @@ namespace Avalonia.Controls.Primitives
// Allocate the buffer // Allocate the buffer
// BGRA formatted color components 1 byte each (4 bytes in a pixel) // BGRA formatted color components 1 byte each (4 bytes in a pixel)
bgraPixelData = new byte[width * height * 4]; bgraPixelData = new ArrayList<byte>(width * height * 4);
bgraPixelDataHeight = height * 4; bgraPixelDataHeight = height * 4;
bgraPixelDataWidth = width * 4; bgraPixelDataWidth = width * 4;
@ -604,7 +604,7 @@ namespace Avalonia.Controls.Primitives
/// <param name="pixelHeight">The pixel height of the bitmap.</param> /// <param name="pixelHeight">The pixel height of the bitmap.</param>
/// <returns>A new <see cref="WriteableBitmap"/>.</returns> /// <returns>A new <see cref="WriteableBitmap"/>.</returns>
public static WriteableBitmap CreateBitmapFromPixelData( public static WriteableBitmap CreateBitmapFromPixelData(
IList<byte> bgraPixelData, ArrayList<byte> bgraPixelData,
int pixelWidth, int pixelWidth,
int pixelHeight) int pixelHeight)
{ {
@ -619,7 +619,7 @@ namespace Avalonia.Controls.Primitives
using (var frameBuffer = bitmap.Lock()) using (var frameBuffer = bitmap.Lock())
{ {
Marshal.Copy(bgraPixelData.ToArray(), 0, frameBuffer.Address, bgraPixelData.Count); Marshal.Copy(bgraPixelData.Array, 0, frameBuffer.Address, bgraPixelData.Array.Length);
} }
return bitmap; return bitmap;
@ -633,11 +633,11 @@ namespace Avalonia.Controls.Primitives
/// <param name="bgraPixelData">The bitmap (in raw BGRA pre-multiplied alpha pixels).</param> /// <param name="bgraPixelData">The bitmap (in raw BGRA pre-multiplied alpha pixels).</param>
public static void UpdateBitmapFromPixelData( public static void UpdateBitmapFromPixelData(
WriteableBitmap bitmap, WriteableBitmap bitmap,
IList<byte> bgraPixelData) ArrayList<byte> bgraPixelData)
{ {
using (var frameBuffer = bitmap.Lock()) using (var frameBuffer = bitmap.Lock())
{ {
Marshal.Copy(bgraPixelData.ToArray(), 0, frameBuffer.Address, bgraPixelData.Count); Marshal.Copy(bgraPixelData.Array, 0, frameBuffer.Address, bgraPixelData.Array.Length);
} }
return; return;

Loading…
Cancel
Save