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)
{
var bitmap = await ColorPickerHelpers.CreateComponentBitmapAsync(
ArrayList<byte> bgraPixelData = await ColorPickerHelpers.CreateComponentBitmapAsync(
pixelWidth,
pixelHeight,
Orientation,
@ -131,18 +131,18 @@ namespace Avalonia.Controls.Primitives
IsAlphaMaxForced,
IsSaturationValueMaxForced);
if (bitmap != null)
if (bgraPixelData != null)
{
if (_backgroundBitmap != null)
{
// 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, bitmap);
ColorPickerHelpers.UpdateBitmapFromPixelData(_backgroundBitmap, bgraPixelData);
}
else
{
_backgroundBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bitmap, pixelWidth, pixelHeight);
_backgroundBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraPixelData, pixelWidth, pixelHeight);
}
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.
// Saturation and luminosity need only a min and max.
List<byte> bgraMinPixelData = new List<byte>();
List<byte> bgraMiddle1PixelData = new List<byte>();
List<byte> bgraMiddle2PixelData = new List<byte>();
List<byte> bgraMiddle3PixelData = new List<byte>();
List<byte> bgraMiddle4PixelData = new List<byte>();
List<byte> bgraMaxPixelData = new List<byte>();
List<Hsv> newHsvValues = new List<Hsv>();
ArrayList<byte> bgraMinPixelData;
ArrayList<byte> bgraMiddle1PixelData;
ArrayList<byte> bgraMiddle2PixelData;
ArrayList<byte> bgraMiddle3PixelData;
ArrayList<byte> bgraMiddle4PixelData;
ArrayList<byte> bgraMaxPixelData;
List<Hsv> 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.
@ -1011,20 +1011,27 @@ namespace Avalonia.Controls.Primitives
int pixelDimension = (int)Math.Round(minDimension * scale);
var pixelCount = pixelDimension * pixelDimension;
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.
if (components == ColorSpectrumComponents.ValueSaturation ||
components == ColorSpectrumComponents.SaturationValue)
{
bgraMiddle1PixelData.Capacity = pixelDataSize;
bgraMiddle2PixelData.Capacity = pixelDataSize;
bgraMiddle3PixelData.Capacity = pixelDataSize;
bgraMiddle4PixelData.Capacity = pixelDataSize;
bgraMiddle1PixelData = new ArrayList<byte>(pixelDataSize);
bgraMiddle2PixelData = new ArrayList<byte>(pixelDataSize);
bgraMiddle3PixelData = new ArrayList<byte>(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(() =>
{
@ -1132,12 +1139,12 @@ namespace Avalonia.Controls.Primitives
double maxSaturation,
double minValue,
double maxValue,
List<byte> bgraMinPixelData,
List<byte> bgraMiddle1PixelData,
List<byte> bgraMiddle2PixelData,
List<byte> bgraMiddle3PixelData,
List<byte> bgraMiddle4PixelData,
List<byte> bgraMaxPixelData,
ArrayList<byte> bgraMinPixelData,
ArrayList<byte> bgraMiddle1PixelData,
ArrayList<byte> bgraMiddle2PixelData,
ArrayList<byte> bgraMiddle3PixelData,
ArrayList<byte> bgraMiddle4PixelData,
ArrayList<byte> bgraMaxPixelData,
List<Hsv> newHsvValues)
{
double hMin = minHue;
@ -1292,12 +1299,12 @@ namespace Avalonia.Controls.Primitives
double maxSaturation,
double minValue,
double maxValue,
List<byte> bgraMinPixelData,
List<byte> bgraMiddle1PixelData,
List<byte> bgraMiddle2PixelData,
List<byte> bgraMiddle3PixelData,
List<byte> bgraMiddle4PixelData,
List<byte> bgraMaxPixelData,
ArrayList<byte> bgraMinPixelData,
ArrayList<byte> bgraMiddle1PixelData,
ArrayList<byte> bgraMiddle2PixelData,
ArrayList<byte> bgraMiddle3PixelData,
ArrayList<byte> bgraMiddle4PixelData,
ArrayList<byte> bgraMaxPixelData,
List<Hsv> newHsvValues)
{
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.
/// This will ensure colors are always discernible regardless of saturation/value.</param>
/// <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 height,
Orientation orientation,
@ -49,14 +49,14 @@ namespace Avalonia.Controls.Primitives
{
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;
double componentStep;
byte[] bgraPixelData;
ArrayList<byte> bgraPixelData;
Color baseRgbColor = Colors.White;
Color rgbColor;
int bgraPixelDataHeight;
@ -64,7 +64,7 @@ namespace Avalonia.Controls.Primitives
// Allocate the buffer
// 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;
bgraPixelDataWidth = width * 4;
@ -604,7 +604,7 @@ namespace Avalonia.Controls.Primitives
/// <param name="pixelHeight">The pixel height of the bitmap.</param>
/// <returns>A new <see cref="WriteableBitmap"/>.</returns>
public static WriteableBitmap CreateBitmapFromPixelData(
IList<byte> bgraPixelData,
ArrayList<byte> bgraPixelData,
int pixelWidth,
int pixelHeight)
{
@ -619,7 +619,7 @@ namespace Avalonia.Controls.Primitives
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;
@ -633,11 +633,11 @@ namespace Avalonia.Controls.Primitives
/// <param name="bgraPixelData">The bitmap (in raw BGRA pre-multiplied alpha pixels).</param>
public static void UpdateBitmapFromPixelData(
WriteableBitmap bitmap,
IList<byte> bgraPixelData)
ArrayList<byte> bgraPixelData)
{
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;

Loading…
Cancel
Save