I am currently trying to modify color picker control. Everything seems to be working as expected. However I would like to have possibility to set "selectedColor" on initialization. So that flow would be as follows:
- Pick needed color
- Save it in Preferences
- Close application
- Open application again
- Color picker is initialized on previously selected color
Currently picker is taking in account only coordinates of pointer X and Y. This means if I will provide previously selected color for Color picker control it will not be able to place pointer in a right place, because it is waiting for X, Y coordinates and not color. I have got a work-around where I save all needed parameters into string (Color Hex code, as well as X and Y coordinates). It is working, however this is adding additional complexity for combining strings and then parsing them inside ViewModels.
I have been getting familiar with possibility to read pixels, searching for needed color and getting it's coordinates. Here are some problems:
- Loop iteration for reading pixels is freezing UI, especially for larger color pickers (large image)
- Not always providing correct coordinates
- During initialization there is problem with black and white colors #00000000 and #FFFFFFFF. So I have added them into if method. It looks like before color picker is actually generated image is black and white? This is of course not a good solution in real case scenario as picked color can be white or black:
-
if (this.PickedColor.ToSKColor() == pixelColor && this.PickedColor.ToSKColor() != Color.FromHex("#00000000").ToSKColor() && this.PickedColor.ToSKColor() != Color.FromHex("#FFFFFFFF").ToSKColor()) { //this.SelectedPoint = new Point(x, y); Debug.WriteLine(String.Format("Color: {0} | Coordinate: {1} {2}", pixelColor, x, y)); }
Here is OnPaintSurface method of Color Picker control (you can see at the bottom method this.GetPixelCoordinates(bitmap); that is commented out):
protected override void OnPaintSurface(SKPaintSurfaceEventArgs e)
{
SKImageInfo skImageInfo = e.Info;
SKSurface skSurface = e.Surface;
this.SKCanvas = skSurface.Canvas;
int skCanvasWidth = skImageInfo.Width;
int skCanvasHeight = skImageInfo.Height;
this.SKCanvas.Clear();
// Draw gradient rainbow Color spectrum
using (SKPaint paint = new SKPaint())
{
paint.IsAntialias = true;
System.Collections.Generic.List<SKColor> colors = new System.Collections.Generic.List<SKColor>();
this.ColorList.ForEach((color) => { colors.Add(Color.FromHex(color).ToSKColor()); });
// create the gradient shader between Colors
using (SKShader shader = SKShader.CreateLinearGradient(
new SKPoint(0, 0),
this.ColorListDirection == ColorListDirection.Horizontal ?
new SKPoint(skCanvasWidth, 0) : new SKPoint(0, skCanvasHeight),
colors.ToArray(),
null,
SKShaderTileMode.Clamp))
{
paint.Shader = shader;
this.SKCanvas.DrawPaint(paint);
}
}
// Draw darker gradient spectrum
using (SKPaint paint = new SKPaint())
{
paint.IsAntialias = true;
// Initiate the darkened primary color list
SKColor[] colors = GetGradientOrder();
// create the gradient shader
using (SKShader shader = SKShader.CreateLinearGradient(
new SKPoint(0, 0),
this.ColorListDirection == ColorListDirection.Horizontal ?
new SKPoint(0, skCanvasHeight) : new SKPoint(skCanvasWidth, 0),
colors,
null,
SKShaderTileMode.Clamp))
{
paint.Shader = shader;
this.SKCanvas.DrawPaint(paint);
}
}
// Picking the Pixel Color values on the Touch Point
// Represent the color of the current Touch point
SKColor touchPointColor;
// Efficient and fast
// https://forums.xamarin.com/discussion/92899/read-a-pixel-info-from-a-canvas
// create the 1x1 bitmap (auto allocates the pixel buffer)
using (SKBitmap bitmap = new SKBitmap(skImageInfo))
{
// get the pixel buffer for the bitmap
IntPtr dstpixels = bitmap.GetPixels();
// read the surface into the bitmap
skSurface.ReadPixels(skImageInfo,
dstpixels,
skImageInfo.RowBytes,
(int)this.SelectedPoint.X,
(int)this.SelectedPoint.Y);
// access the color
touchPointColor = bitmap.GetPixel(0, 0);
//this.GetPixelCoordinates(bitmap);
//bitmap.SetPixel(50, 50, this.PickedColor.ToSKColor());
}
Here is GetPixelCoordinates method:
private void GetPixelCoordinates(SKBitmap bitmap)
{
if (bitmap == null)
{
return;
}
for (int x = 0; x < bitmap.Width; x++)
{
for (int y = 0; y < bitmap.Height; y++)
{
SKColor pixelColor = bitmap.GetPixel(x, y);
if (this.PickedColor.ToSKColor() == pixelColor
&& this.PickedColor.ToSKColor() != Color.FromHex("#00000000").ToSKColor()
&& this.PickedColor.ToSKColor() != Color.FromHex("#FFFFFFFF").ToSKColor())
{
//this.SelectedPoint = new Point(x, y);
Debug.WriteLine(String.Format("Color: {0} | Coordinate: {1} {2}", pixelColor, x, y));
}
}
}
}
Here is PickedColor property:
public static readonly BindableProperty PickedColorProperty
= BindableProperty.Create(
propertyName: nameof(PickedColor),
returnType: typeof(Color),
declaringType: typeof(ColorPicker),
defaultValue: Color.Green,
defaultBindingMode: BindingMode.TwoWay,
propertyChanged: OnColorChanged);
private static void OnColorChanged(BindableObject bindable, object oldValue, object newValue)
{
ColorPicker control = (ColorPicker)bindable;
control.PickedColor = (Color)newValue;
}
/// <summary>
/// Set the Color Spectrum Gradient Style
/// </summary>
public GradientColorStyle GradientColorStyle
{
get { return (GradientColorStyle)GetValue(GradientColorStyleProperty); }
set { SetValue(GradientColorStyleProperty, value); }
}
public static readonly BindableProperty ColorListProperty
= BindableProperty.Create(
propertyName: nameof(ColorList),
returnType: typeof(string[]),
declaringType: typeof(ColorPicker),
defaultValue: new string[]
{
new Color(255, 0, 0).ToHex(), // Red
new Color(255, 255, 0).ToHex(), // Yellow
new Color(0, 255, 0).ToHex(), // Green (Lime)
new Color(0, 255, 255).ToHex(), // Aqua
new Color(0, 0, 255).ToHex(), // Blue
new Color(255, 0, 255).ToHex(), // Fuchsia
new Color(255, 0, 0).ToHex(), // Red
},
defaultBindingMode: BindingMode.OneTime, null);
My question is: Is generating string with parameters the only way to go (Color Hex code, as well as X and Y coordinates)? Is there some possibility to place pointer on control initialization by provided color in some efficient way without constant loop iteration and freeze of UI?
Color palette: