in src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs [1042:1231]
private async void CreateBitmapsAndColorMap()
{
if (_layoutRoot == null ||
_sizingPanel == null ||
_inputTarget == null ||
_spectrumRectangle == null ||
_spectrumEllipse == null ||
_spectrumOverlayRectangle == null ||
_spectrumOverlayEllipse == null
/*|| SharedHelpers.IsInDesignMode*/)
{
return;
}
// We want ColorSpectrum to always be a square, so we'll take the smaller of the dimensions
// and size the sizing panel to that.
double minDimension = Math.Min(_layoutRoot.Bounds.Width, _layoutRoot.Bounds.Height);
if (minDimension == 0)
{
return;
}
_sizingPanel.Width = minDimension;
_sizingPanel.Height = minDimension;
_inputTarget.Width = minDimension;
_inputTarget.Height = minDimension;
_spectrumRectangle.Width = minDimension;
_spectrumRectangle.Height = minDimension;
_spectrumEllipse.Width = minDimension;
_spectrumEllipse.Height = minDimension;
_spectrumOverlayRectangle.Width = minDimension;
_spectrumOverlayRectangle.Height = minDimension;
_spectrumOverlayEllipse.Width = minDimension;
_spectrumOverlayEllipse.Height = minDimension;
HsvColor hsvColor = HsvColor;
int minHue = MinHue;
int maxHue = MaxHue;
int minSaturation = MinSaturation;
int maxSaturation = MaxSaturation;
int minValue = MinValue;
int maxValue = MaxValue;
ColorSpectrumShape shape = Shape;
ColorSpectrumComponents components = Components;
// If min >= max, then by convention, min is the only number that a property can have.
if (minHue >= maxHue)
{
maxHue = minHue;
}
if (minSaturation >= maxSaturation)
{
maxSaturation = minSaturation;
}
if (minValue >= maxValue)
{
maxValue = minValue;
}
Hsv hsv = new Hsv(hsvColor);
// 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.
// Remember bitmaps in Avalonia are rendered mapping to actual device pixels, not the device-
// independent pixels of controls.
var scale = LayoutHelper.GetLayoutScale(this);
int pixelDimension = (int)Math.Round(minDimension * scale);
var pixelCount = pixelDimension * pixelDimension;
var pixelDataSize = pixelCount * 4;
// We'll only save pixel data for the middle bitmaps if our third dimension is hue.
var middleBitmapsSize =
components is ColorSpectrumComponents.ValueSaturation or ColorSpectrumComponents.SaturationValue
? pixelDataSize : 0;
var newHsvValues = new List<Hsv>(pixelCount);
using var bgraMinPixelData = new PooledList<byte>(pixelDataSize, ClearMode.Never);
using var bgraMaxPixelData = new PooledList<byte>(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<byte>(middleBitmapsSize, ClearMode.Never);
using var bgraMiddle2PixelData = new PooledList<byte>(middleBitmapsSize, ClearMode.Never);
using var bgraMiddle3PixelData = new PooledList<byte>(middleBitmapsSize, ClearMode.Never);
using var bgraMiddle4PixelData = new PooledList<byte>(middleBitmapsSize, ClearMode.Never);
await Task.Run(() =>
{
// As the user perceives it, every time the third dimension not represented in the ColorSpectrum changes,
// the ColorSpectrum will visually change to accommodate that value. For example, if the ColorSpectrum handles hue and luminosity,
// and the saturation externally goes from 1.0 to 0.5, then the ColorSpectrum will visually change to look more washed out
// to represent that third dimension's new value.
// Internally, however, we don't want to regenerate the ColorSpectrum bitmap every single time this happens, since that's very expensive.
// In order to make it so that we don't have to, we implement an optimization where, rather than having only one bitmap,
// we instead have multiple that we blend together using opacity to create the effect that we want.
// In the case where the third dimension is saturation or luminosity, we only need two: one bitmap at the minimum value
// of the third dimension, and one bitmap at the maximum. Then we set the second's opacity at whatever the value of
// the third dimension is - e.g., a saturation of 0.5 implies an opacity of 50%.
// In the case where the third dimension is hue, we need six: one bitmap corresponding to red, yellow, green, cyan, blue, and purple.
// We'll then blend between whichever colors our hue exists between - e.g., an orange color would use red and yellow with an opacity of 50%.
// This optimization does incur slightly more startup time initially since we have to generate multiple bitmaps at once instead of only one,
// but the running time savings after that are *huge* when we can just set an opacity instead of generating a brand new bitmap.
if (shape == ColorSpectrumShape.Box)
{
for (int x = pixelDimension - 1; x >= 0; --x)
{
for (int y = pixelDimension - 1; y >= 0; --y)
{
FillPixelForBox(
x, y, hsv, pixelDimension, components, minHue, maxHue, minSaturation, maxSaturation, minValue, maxValue,
bgraMinPixelData, bgraMiddle1PixelData, bgraMiddle2PixelData, bgraMiddle3PixelData, bgraMiddle4PixelData, bgraMaxPixelData,
newHsvValues);
}
}
}
else
{
for (int y = 0; y < pixelDimension; ++y)
{
for (int x = 0; x < pixelDimension; ++x)
{
FillPixelForRing(
x, y, pixelDimension / 2.0, hsv, components, minHue, maxHue, minSaturation, maxSaturation, minValue, maxValue,
bgraMinPixelData, bgraMiddle1PixelData, bgraMiddle2PixelData, bgraMiddle3PixelData, bgraMiddle4PixelData, bgraMaxPixelData,
newHsvValues);
}
}
}
});
await Dispatcher.UIThread.InvokeAsync(() =>
{
int pixelWidth = pixelDimension;
int pixelHeight = pixelDimension;
ColorSpectrumComponents components2 = Components;
_minBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraMinPixelData, pixelWidth, pixelHeight);
_maxBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraMaxPixelData, pixelWidth, pixelHeight);
switch (components2)
{
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;
}
_shapeFromLastBitmapCreation = Shape;
_componentsFromLastBitmapCreation = Components;
_imageWidthFromLastBitmapCreation = pixelDimension;
_imageHeightFromLastBitmapCreation = pixelDimension;
_minHueFromLastBitmapCreation = MinHue;
_maxHueFromLastBitmapCreation = MaxHue;
_minSaturationFromLastBitmapCreation = MinSaturation;
_maxSaturationFromLastBitmapCreation = MaxSaturation;
_minValueFromLastBitmapCreation = MinValue;
_maxValueFromLastBitmapCreation = MaxValue;
_hsvValues = newHsvValues;
UpdateBitmapSources();
UpdateEllipse();
});
}