Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 93 additions & 10 deletions XFColorPickerControl/XFColorPickerControl/Controls/ColorPicker.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System.Diagnostics;
using System.Linq;
using SkiaSharp;
using SkiaSharp.Views.Forms;
using Xamarin.Forms;
Expand Down Expand Up @@ -27,10 +29,18 @@ public static readonly BindableProperty PickedColorProperty
public Color PickedColor
{
get { return (Color)GetValue(PickedColorProperty); }
private set { SetValue(PickedColorProperty, value); }
set
{
if (!PickedColor.Equals(value))
{
SetValue(PickedColorProperty, value);
_lastTouchPoint = new SKPoint() { X = int.MaxValue, Y = int.MaxValue };
SkCanvasView.InvalidateSurface();
PickedColorChanged?.Invoke(this, PickedColor);
}
}
}


public static readonly BindableProperty GradientColorStyleProperty
= BindableProperty.Create(
nameof(GradientColorStyle),
Expand All @@ -48,7 +58,6 @@ public GradientColorStyle GradientColorStyle
set { SetValue(GradientColorStyleProperty, value); }
}


public static readonly BindableProperty ColorListProperty
= BindableProperty.Create(
nameof(ColorList),
Expand All @@ -75,7 +84,6 @@ public string[] ColorList
set { SetValue(ColorListProperty, value); }
}


public static readonly BindableProperty ColorListDirectionProperty
= BindableProperty.Create(
nameof(ColorListDirection),
Expand Down Expand Up @@ -113,7 +121,6 @@ public double PointerCircleDiameterUnits
set { SetValue(PointerCircleDiameterUnitsProperty, value); }
}


public static readonly BindableProperty PointerCircleBorderUnitsProperty
= BindableProperty.Create(
nameof(PointerCircleBorderUnits),
Expand All @@ -133,14 +140,26 @@ public double PointerCircleBorderUnits
set { SetValue(PointerCircleBorderUnitsProperty, value); }
}


private SKPoint _lastTouchPoint = new SKPoint();
private SKPoint _lastTouchPoint = new SKPoint() { X = int.MaxValue, Y = int.MaxValue};

public ColorPicker()
{
InitializeComponent();
}

/// <summary>
/// I found this algorythm at https://stackoverflow.com/a/33782458/648919
/// It's simple enough but really effective!
/// </summary>
private double ColorDistance(SKColor c1, SKColor c2)
{
int rmean = (c1.Red + c2.Red) / 2;
int r = c1.Red - c2.Red;
int g = c1.Green - c2.Green;
int b = c1.Blue - c2.Blue;
return Math.Sqrt((((512 + rmean) * r * r) >> 8) + 4 * g * g + (((767 - rmean) * b * b) >> 8));
}

private void SkCanvasView_OnPaintSurface(object sender, SKPaintSurfaceEventArgs e)
{
var skImageInfo = e.Info;
Expand Down Expand Up @@ -199,7 +218,7 @@ private void SkCanvasView_OnPaintSurface(object sender, SKPaintSurfaceEventArgs
// Picking the Pixel Color values on the Touch Point

// Represent the color of the current Touch point
SKColor touchPointColor;
SKColor touchPointColor = SKColors.White;

// Efficient and fast
// https://forums.xamarin.com/discussion/92899/read-a-pixel-info-from-a-canvas
Expand All @@ -209,6 +228,70 @@ private void SkCanvasView_OnPaintSurface(object sender, SKPaintSurfaceEventArgs
// get the pixel buffer for the bitmap
IntPtr dstpixels = bitmap.GetPixels();

// If color isn't set by touch, we need to find a color location on bitmap
if (_lastTouchPoint.X.Equals(int.MaxValue) && _lastTouchPoint.Y.Equals(int.MaxValue))
{
// read the surface into the bitmap
var res = skSurface.ReadPixels(skImageInfo, dstpixels, skImageInfo.RowBytes, 0, 0);
touchPointColor = PickedColor.ToSKColor();
var bpp = bitmap.BytesPerPixel;

// Get rid of SKBitmap getters
var pixels = new byte[bitmap.ByteCount];
bitmap.Bytes.CopyTo(pixels, 0);

// Let's try to find our color coordinates
var exactMatch = false;
var minDistance = double.MaxValue;

for (int y = 0; y < skCanvasHeight; y++)
{
for (int x = 0; x < skCanvasWidth; x++)
{
var c = new SKColor(pixels[(y * skCanvasWidth + x) * bpp + 2],
pixels[(y * skCanvasWidth + x) * bpp + 1],
pixels[(y * skCanvasWidth + x) * bpp + 0]);

// Check for exact color mach first
if (c.Equals(touchPointColor))
{
_lastTouchPoint.X = x;
_lastTouchPoint.Y = y;
exactMatch = true;
break;
}
else
{
var dist = ColorDistance(touchPointColor, c);
if (dist < 5)
{
_lastTouchPoint.X = x;
_lastTouchPoint.Y = y;
exactMatch = true;
break;
}
else if (minDistance > dist)
{
minDistance = dist;
_lastTouchPoint.X = x;
_lastTouchPoint.Y = y;

#if false
// Small optimization based on RL tests; btw, it works
// fast enough even without these lines if (minDistance < 15)
{
exactMatch = true;
break;
}
#endif
}
}
}

if (exactMatch) break;
}
}

// read the surface into the bitmap
skSurface.ReadPixels(skImageInfo,
dstpixels,
Expand Down Expand Up @@ -253,8 +336,7 @@ private void SkCanvasView_OnPaintSurface(object sender, SKPaintSurfaceEventArgs
}

// Set selected color
PickedColor = touchPointColor.ToFormsColor();
PickedColorChanged?.Invoke(this, PickedColor);
SetValue(PickedColorProperty, touchPointColor.ToFormsColor());
}

private void SkCanvasView_OnTouch(object sender, SKTouchEventArgs e)
Expand All @@ -272,6 +354,7 @@ private void SkCanvasView_OnTouch(object sender, SKTouchEventArgs e)

// update the Canvas as you wish
SkCanvasView.InvalidateSurface();
PickedColorChanged?.Invoke(this, PickedColor);
}
}

Expand Down
99 changes: 56 additions & 43 deletions XFColorPickerControl/XFColorPickerControl/MainPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,12 @@

<Grid Padding="20">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>

<StackLayout
Grid.Row="0"
Orientation="Vertical"
VerticalOptions="Start">
<StackLayout Grid.Row="0" Orientation="Vertical" VerticalOptions="Start" HorizontalOptions="FillAndExpand">
<Label
Grid.Row="0"
FontSize="Medium"
Expand All @@ -30,29 +28,61 @@
FontSize="Medium"
HorizontalTextAlignment="Center"
Text="This is my Interactive and Responsive Color Picker Control based on SkiaSharp!" />

<Grid Grid.Row="2" HorizontalOptions="FillAndExpand" Padding="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>

<controls:ColorPicker x:Name="TestColorPicker" Margin="44, 10" PickedColorChanged="TestColorPicker_PickedColorChanged"/>

<Picker x:Name="SystemColorPicker" Grid.Column="1" Margin="44, 20" FontSize="Medium" SelectedIndexChanged="SystemColorPicker_SelectedIndexChanged">
<Picker.ItemDisplayBinding>
<Binding Path="Name" />
</Picker.ItemDisplayBinding>
</Picker>

</Grid>
</StackLayout>

<StackLayout
Grid.Row="1"
Orientation="Vertical"
Spacing="15"
VerticalOptions="Center">
<Frame
x:Name="SelectedColorDisplayFrame"
BackgroundColor="White"
CornerRadius="8"
HeightRequest="70"
HorizontalOptions="Center"
WidthRequest="350">
<Label
x:Name="SelectedColorValueLabel"
FontAttributes="Bold"
FontSize="Title"
HorizontalTextAlignment="Center"
Text="#COLOR"
TextColor="SlateGray"
VerticalTextAlignment="Center" />
</Frame>
<StackLayout Orientation="Vertical" Grid.Row="1" Spacing="30" VerticalOptions="Center">

<StackLayout Grid.Row="1" Orientation="Horizontal" Spacing="15" VerticalOptions="Center">

<Frame x:Name="SelectedColorDisplayFrame"
BackgroundColor="White"
CornerRadius="8"
HeightRequest="70"
HorizontalOptions="CenterAndExpand"
WidthRequest="350">

<Label x:Name="SelectedColorValueLabel"
FontAttributes="Bold"
FontSize="Title"
HorizontalTextAlignment="Center"
Text="#COLOR"
TextColor="SlateGray"
VerticalTextAlignment="Center" />
</Frame>

<Frame x:Name="SystemSelectedColorDisplayFrame"
BackgroundColor="White"
CornerRadius="8"
HeightRequest="70"
HorizontalOptions="CenterAndExpand"
WidthRequest="350">
<Label x:Name="SystemSelectedColorValueLabel"
FontAttributes="Bold"
FontSize="Title"
HorizontalTextAlignment="Center"
Text="#COLOR"
TextColor="SlateGray"
VerticalTextAlignment="Center" />
</Frame>

</StackLayout>

<Frame
x:Name="ColorPickerHolderFrame"
CornerRadius="8"
Expand All @@ -61,29 +91,12 @@
WidthRequest="350">
<controls:ColorPicker
x:Name="ColorPicker"
PickedColor="#ff10fede"
ColorListDirection="Horizontal"
GradientColorStyle="DarkToColorsToLightStyle"
PickedColorChanged="ColorPicker_PickedColorChanged"
PointerCircleBorderUnits="0.3"
PointerCircleDiameterUnits="0.7">
<controls:ColorPicker.ColorList>
<x:Array Type="{x:Type x:String}">
<!-- Red -->
<x:String>#ff0000</x:String>
<!-- Yellow -->
<x:String>#ffff00</x:String>
<!-- Green (Lime) -->
<x:String>#00ff00</x:String>
<!-- Aqua -->
<x:String>#00ffff</x:String>
<!-- Blue -->
<x:String>#0000ff</x:String>
<!-- Fuchsia -->
<x:String>#ff00ff</x:String>
<!-- Red -->
<x:String>#ff0000</x:String>
</x:Array>
</controls:ColorPicker.ColorList>
</controls:ColorPicker>
</Frame>
</StackLayout>
Expand Down
50 changes: 44 additions & 6 deletions XFColorPickerControl/XFColorPickerControl/MainPage.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using Xamarin.Forms;
using XFColorPickerControl.Controls;

namespace XFColorPickerControl
{
Expand All @@ -13,9 +12,27 @@ namespace XFColorPickerControl
[DesignTimeVisible(false)]
public partial class MainPage : ContentPage
{
public class ColorItem
{
public string Name { get; set; }
public Color Value { get; set; }
}
public MainPage()
{
InitializeComponent();

var allColors = typeof(Color)
.GetRuntimeFields()
.Where(f => f.FieldType == typeof(Color))
.Select(prop => new ColorItem
{
Name = prop.Name,
Value = (Color)prop.GetValue(null)
})
.ToList();

SystemColorPicker.ItemsSource = allColors;
SystemColorPicker.SelectedIndex = 0;
}

private void ColorPicker_PickedColorChanged(object sender, Color colorPicked)
Expand All @@ -26,9 +43,30 @@ private void ColorPicker_PickedColorChanged(object sender, Color colorPicked)
ColorPickerHolderFrame.BackgroundColor = colorPicked;

if (colorPicked.Luminosity < 0.5)
SelectedColorValueLabel.TextColor = Xamarin.Forms.Color.White;
SelectedColorValueLabel.TextColor = Color.White;
else
SelectedColorValueLabel.TextColor = Xamarin.Forms.Color.SlateGray;
SelectedColorValueLabel.TextColor = Color.SlateGray;
}
}

private void TestColorPicker_PickedColorChanged(object sender, Color e)
{
ColorPicker.PickedColor = e;
}

private void SystemColorPicker_SelectedIndexChanged(object sender, EventArgs e)
{
var color = (SystemColorPicker.SelectedItem as ColorItem).Value;

ColorPicker.PickedColor = color;

// Use the selected color
SystemSelectedColorDisplayFrame.BackgroundColor = color;
SystemSelectedColorValueLabel.Text = color.ToHex();

if (color.Luminosity < 0.5)
SystemSelectedColorValueLabel.TextColor = Color.White;
else
SystemSelectedColorValueLabel.TextColor = Color.SlateGray;
}
}
}