用于颜色选择器的 WPF 双渐变
WPF double gradient for color picker
我的目标是实现自定义颜色选择器。我不想使用现有的实现,例如 xceed,还有其他原因,因为我在 RGBA FP32^4 space 中工作。我知道 WPF 只能在 int8^4 space 中显示,但我正在使用的坐标在 FP32^4 space 中。它将与具有 10 位显示的 DirectX12 资产互操作。
我需要做的一件事是实现双梯度亮度与饱和度图,来自 {Hue Saturation Luminosity} space。
我不知道如何在 Rectangle
中使用双渐变;所以我认为做到这一点的一种方法是使用单一渐变,例如,
<Rectangle.Fill>
<LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5">
<GradientStop Color="{Binding HueColor}" Offset="1"/>
<GradientStop Color="#00000000" Offset="0"/>
</LinearGradientBrush>
</Rectangle.Fill>
然后添加一个会重叠的水平饱和度梯度滤镜。
使用不透明蒙版在这里没有帮助:
<Rectangle.OpacityMask>
<LinearGradientBrush EndPoint="0.5,0" StartPoint="0.5,1">
<GradientStop Color="#00000000" Offset="0"/>
<GradientStop Color="#FFFFFFFF" Offset="1"/>
</LinearGradientBrush>
</Rectangle.OpacityMask>
我找不到在 WPF 中实现此目的的方法。我想避免使用 cuda 或 directx12,即使准确,也有点矫枉过正。
我不得不用 2 类.
手动实现位图创建
public class HSLfloat
{
public float H { get; set; }
public float S { get; set; }
public float L { get; set; }
public HSLfloat(RGBAfloat rgbafloat)
{
var tol = 0.000001;
var rgb = rgbafloat.ToArrayRGB();
var rgbTuple = rgbafloat.ToArrayTuple();
var Cmax = rgb.Max();
var Cmin = rgb.Min();
var delta = Cmax - Cmin;
L = delta / 2;
var s = Math.Abs(delta) < tol ? 0 : delta / (1 - Math.Abs(2 * L - 1));
var CmaxName = rgbTuple.Where(o => Math.Abs(o.Item1 - Cmax) < tol).Select(o => o.Item2).First();
if (Math.Abs(delta) < tol)
H = 0;
else
{
switch (CmaxName)
{
case 'R':
H = 60 * ((rgbafloat.G - rgbafloat.B) / delta % 6);
break;
case 'G':
H = 60 * ((rgbafloat.B - rgbafloat.R) / delta + 2);
break;
case 'B':
H = 60 * ((rgbafloat.R - rgbafloat.G) / delta + 4);
break;
}
}
}
public HSLfloat(float h, float s, float l)
{
H = h;
S = s;
L = l;
}
public RGBAfloat ToRGBAfloat() => new RGBAfloat(this);
public (float H, float S, float L) ToTuple() => (H, S, L);
}
对于 RGBA space:
public class RGBAfloat
{
public float R { get; set; }
public float G { get; set; }
public float B { get; set; }
public float A { get; set; }
public RGBAfloat()
{
}
public RGBAfloat(double r, double g, double b, double a = 1d)
{
R = (float)r;
G = (float)g;
B = (float)b;
A = (float)a;
}
public RGBAfloat(float r, float g, float b, float a= 1f)
{
R = r;
G = g;
B = b;
A = a;
}
public RGBAfloat(RGBAfloatStruct colorFloatStruct)
{
R = colorFloatStruct.R;
G = colorFloatStruct.G;
B = colorFloatStruct.B;
A = colorFloatStruct.A;
}
public RGBAfloat(HSLfloat hslfloat)
{
var c = (1 - Math.Abs(2 * hslfloat.L - 1)) * hslfloat.S;
var x = c * (1 - Math.Abs((hslfloat.H / 60) % 2 - 1));
var m = hslfloat.L - c / 2;
var quadrant = (int)(hslfloat.H % 360 / 60); // [0-5]
switch (quadrant)
{
default:
case 0:
R = c + m;
G = x + m;
B = 0f + m;
break;
case 1:
R = x + m;
G = c + m;
B = 0f + m;
break;
case 2:
R = 0f + m;
G = c + m;
B = x + m;
break;
case 3:
R = 0f + m;
G = x + m;
B = c + m;
break;
case 4:
R = x + m;
G = 0f + m;
B = c + m;
break;
case 5:
R = c + m;
G = 0f + m;
B = x + m;
break;
}
}
public RGBAfloatStruct ToRGBAFloatStruct() => new RGBAfloatStruct {A = A, R = R, G = G, B = B};
public float[] ToArrayRGB() => new[] {R, G, B};
public float[] ToArray() => new[] {R, G, B, A};
public (float,char)[] ToArrayTuple() => new[] { (R, 'R'), (G, 'G'), (B, 'B') };
public (float R, float G, float B) ToTupleRGB() => (R, G, B);
public (float R, float G, float B, float A) ToTuple() => (R, G, B, A);
public HSLfloat ToHSLfloat() => new HSLfloat(this);
public int ToRGBAint()
{
var rr = (int)(R * 255);
var gg = (int)(G * 255);
var bb = (int)(B * 255);
int color = rr << 16;
color |= gg << 8;
color |= bb << 0;
return color;
}
public WriteableBitmap ToWriteableBitmap(int size = 200)
{
var (h, s, l) = ToHSLfloat().ToTuple();
var writeableBitmap = new WriteableBitmap(size, size, 96, 96, PixelFormats.Bgr32, null);
for (int y = 0; y < size; y++)
for (int x = 0; x < size; x++)
{
s = (float)x / size;
l = (float)(size - y) / size;
var intColor = new HSLfloat(h, s, l).ToRGBAfloat().ToRGBAint();
unsafe
{
var ptr = writeableBitmap.BackBuffer;
ptr += y * writeableBitmap.BackBufferStride;
ptr += x * 4;
*((IntPtr*)ptr) = (IntPtr)intColor;
}
}
return writeableBitmap;
}
}
结果,SL 映射 (r,g,b)= (0.8, 0.5, 0.3)
:
尝试使用视觉画笔。您可以绑定 F32 色调颜色并使用转换器在颜色空间之间进行转换(如第 11 行所示)。您甚至可以摆脱视觉画笔,直接在矩形中使用线性渐变和不透明蒙版,尽管我自己没有尝试过。
<Rectangle Width="200" Height="200">
<Rectangle.Fill>
<VisualBrush TileMode="None">
<VisualBrush.Visual>
<Canvas Background="Black" Width="1" Height="1" SnapsToDevicePixels="True">
<Rectangle Width="1" Height="1" SnapsToDevicePixels="True">
<Rectangle.Fill>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
<LinearGradientBrush.GradientStops>
<GradientStop Color="White" Offset="0" />
<GradientStop Color="{Binding HueColor, Converter={StaticResource color4Color}}" Offset="1" />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Rectangle.Fill>
<Rectangle.OpacityMask>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<LinearGradientBrush.GradientStops>
<GradientStop Color="#FFFFFFFF" Offset="0"/>
<GradientStop Color="#00FFFFFF" Offset="1"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Rectangle.OpacityMask>
</Rectangle>
</Canvas>
</VisualBrush.Visual>
</VisualBrush>
</Rectangle.Fill>
</Rectangle>
我的目标是实现自定义颜色选择器。我不想使用现有的实现,例如 xceed,还有其他原因,因为我在 RGBA FP32^4 space 中工作。我知道 WPF 只能在 int8^4 space 中显示,但我正在使用的坐标在 FP32^4 space 中。它将与具有 10 位显示的 DirectX12 资产互操作。
我需要做的一件事是实现双梯度亮度与饱和度图,来自 {Hue Saturation Luminosity} space。
我不知道如何在 Rectangle
中使用双渐变;所以我认为做到这一点的一种方法是使用单一渐变,例如,
<Rectangle.Fill>
<LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5">
<GradientStop Color="{Binding HueColor}" Offset="1"/>
<GradientStop Color="#00000000" Offset="0"/>
</LinearGradientBrush>
</Rectangle.Fill>
然后添加一个会重叠的水平饱和度梯度滤镜。 使用不透明蒙版在这里没有帮助:
<Rectangle.OpacityMask>
<LinearGradientBrush EndPoint="0.5,0" StartPoint="0.5,1">
<GradientStop Color="#00000000" Offset="0"/>
<GradientStop Color="#FFFFFFFF" Offset="1"/>
</LinearGradientBrush>
</Rectangle.OpacityMask>
我找不到在 WPF 中实现此目的的方法。我想避免使用 cuda 或 directx12,即使准确,也有点矫枉过正。
我不得不用 2 类.
手动实现位图创建public class HSLfloat
{
public float H { get; set; }
public float S { get; set; }
public float L { get; set; }
public HSLfloat(RGBAfloat rgbafloat)
{
var tol = 0.000001;
var rgb = rgbafloat.ToArrayRGB();
var rgbTuple = rgbafloat.ToArrayTuple();
var Cmax = rgb.Max();
var Cmin = rgb.Min();
var delta = Cmax - Cmin;
L = delta / 2;
var s = Math.Abs(delta) < tol ? 0 : delta / (1 - Math.Abs(2 * L - 1));
var CmaxName = rgbTuple.Where(o => Math.Abs(o.Item1 - Cmax) < tol).Select(o => o.Item2).First();
if (Math.Abs(delta) < tol)
H = 0;
else
{
switch (CmaxName)
{
case 'R':
H = 60 * ((rgbafloat.G - rgbafloat.B) / delta % 6);
break;
case 'G':
H = 60 * ((rgbafloat.B - rgbafloat.R) / delta + 2);
break;
case 'B':
H = 60 * ((rgbafloat.R - rgbafloat.G) / delta + 4);
break;
}
}
}
public HSLfloat(float h, float s, float l)
{
H = h;
S = s;
L = l;
}
public RGBAfloat ToRGBAfloat() => new RGBAfloat(this);
public (float H, float S, float L) ToTuple() => (H, S, L);
}
对于 RGBA space:
public class RGBAfloat
{
public float R { get; set; }
public float G { get; set; }
public float B { get; set; }
public float A { get; set; }
public RGBAfloat()
{
}
public RGBAfloat(double r, double g, double b, double a = 1d)
{
R = (float)r;
G = (float)g;
B = (float)b;
A = (float)a;
}
public RGBAfloat(float r, float g, float b, float a= 1f)
{
R = r;
G = g;
B = b;
A = a;
}
public RGBAfloat(RGBAfloatStruct colorFloatStruct)
{
R = colorFloatStruct.R;
G = colorFloatStruct.G;
B = colorFloatStruct.B;
A = colorFloatStruct.A;
}
public RGBAfloat(HSLfloat hslfloat)
{
var c = (1 - Math.Abs(2 * hslfloat.L - 1)) * hslfloat.S;
var x = c * (1 - Math.Abs((hslfloat.H / 60) % 2 - 1));
var m = hslfloat.L - c / 2;
var quadrant = (int)(hslfloat.H % 360 / 60); // [0-5]
switch (quadrant)
{
default:
case 0:
R = c + m;
G = x + m;
B = 0f + m;
break;
case 1:
R = x + m;
G = c + m;
B = 0f + m;
break;
case 2:
R = 0f + m;
G = c + m;
B = x + m;
break;
case 3:
R = 0f + m;
G = x + m;
B = c + m;
break;
case 4:
R = x + m;
G = 0f + m;
B = c + m;
break;
case 5:
R = c + m;
G = 0f + m;
B = x + m;
break;
}
}
public RGBAfloatStruct ToRGBAFloatStruct() => new RGBAfloatStruct {A = A, R = R, G = G, B = B};
public float[] ToArrayRGB() => new[] {R, G, B};
public float[] ToArray() => new[] {R, G, B, A};
public (float,char)[] ToArrayTuple() => new[] { (R, 'R'), (G, 'G'), (B, 'B') };
public (float R, float G, float B) ToTupleRGB() => (R, G, B);
public (float R, float G, float B, float A) ToTuple() => (R, G, B, A);
public HSLfloat ToHSLfloat() => new HSLfloat(this);
public int ToRGBAint()
{
var rr = (int)(R * 255);
var gg = (int)(G * 255);
var bb = (int)(B * 255);
int color = rr << 16;
color |= gg << 8;
color |= bb << 0;
return color;
}
public WriteableBitmap ToWriteableBitmap(int size = 200)
{
var (h, s, l) = ToHSLfloat().ToTuple();
var writeableBitmap = new WriteableBitmap(size, size, 96, 96, PixelFormats.Bgr32, null);
for (int y = 0; y < size; y++)
for (int x = 0; x < size; x++)
{
s = (float)x / size;
l = (float)(size - y) / size;
var intColor = new HSLfloat(h, s, l).ToRGBAfloat().ToRGBAint();
unsafe
{
var ptr = writeableBitmap.BackBuffer;
ptr += y * writeableBitmap.BackBufferStride;
ptr += x * 4;
*((IntPtr*)ptr) = (IntPtr)intColor;
}
}
return writeableBitmap;
}
}
结果,SL 映射 (r,g,b)= (0.8, 0.5, 0.3)
:
尝试使用视觉画笔。您可以绑定 F32 色调颜色并使用转换器在颜色空间之间进行转换(如第 11 行所示)。您甚至可以摆脱视觉画笔,直接在矩形中使用线性渐变和不透明蒙版,尽管我自己没有尝试过。
<Rectangle Width="200" Height="200">
<Rectangle.Fill>
<VisualBrush TileMode="None">
<VisualBrush.Visual>
<Canvas Background="Black" Width="1" Height="1" SnapsToDevicePixels="True">
<Rectangle Width="1" Height="1" SnapsToDevicePixels="True">
<Rectangle.Fill>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
<LinearGradientBrush.GradientStops>
<GradientStop Color="White" Offset="0" />
<GradientStop Color="{Binding HueColor, Converter={StaticResource color4Color}}" Offset="1" />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Rectangle.Fill>
<Rectangle.OpacityMask>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<LinearGradientBrush.GradientStops>
<GradientStop Color="#FFFFFFFF" Offset="0"/>
<GradientStop Color="#00FFFFFF" Offset="1"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Rectangle.OpacityMask>
</Rectangle>
</Canvas>
</VisualBrush.Visual>
</VisualBrush>
</Rectangle.Fill>
</Rectangle>