WPF 控件库——仿制Chrome的ColorPicker

一、观察

  项目中的一个新需求,需要往控件库中添加颜色拾取器控件,因为公司暂时还没有UI设计大佬入住,所以就从网上开始找各种模样的ColorPicker,找来找去我就看上了谷歌浏览器自带的,它长这个样:

  

  看上去不错,可以搞!搞之前得观察一下这里面可能的一些坑。对WPF而言,圆角阴影等效果都是基本操作,这里就不说了。

  首先我们注意到上图中有两个拖动条,一个背景是可见光谱,另一个背景是颜色渐变和方块平铺的叠加。因为需求里没有屏幕取色的功能,所以在拖动条左侧的拾取图标可以去掉,只保留当前颜色预览,这样会多出来一大块空间,可以考虑将圆形的颜色预览区域改成有圆角的矩形。而最上方的颜色拾取区域就比较复杂了,它其实是三层画刷的叠加,第一层是洋红色主色调,第二层是白色到透明的左右渐变,第三层是透明到黑色的上下渐变。由于WPF的带透明通道的颜色渐变并非是标准的,举个例子,假设有一个从透明到黑色的上下渐变层,在渐变层下方是纯红色背景,那么理论上渐变开始的颜色是#FFFF0000,渐变结束的颜色是#FF000000,那么在上下一半处的颜色应该是#FF7F0000(或者是#FF800000,就是简单的相加除以2),但是在WPF中却不是这个值(在专业的图像处理软件中比如PS中的确是#FF7F0000),如果你不信,我们现在就做个实验。

二、实验

  打开Blend,新建个WPF项目,设置窗口尺寸为400*300,为了方便定位中心点,我们需要设置 AllowsTransparency="True" , WindowStyle="None" ,接着把主窗口背景改成纯红色,再添加一层从透明到黑色的上下渐变层,用Border实现,如下图:

  

  我们使用标尺,定位中心点(200,150),如下图:

  

  我们看看在中心点处的颜色是什么,用Blend取色得到的颜色如下:

  

  下面我们在PS中重现以上操作,看看最后的颜色会是什么,打开PS新建400*300,分辨率为72的画布:

  

  新建纯红色填充图层和透明到黑色的上下渐变层,再用标尺定位一下中心点:

  

  最后得到的颜色如下:

  

  我们该相信谁?当然是PS,毕竟人家是图像处理科班出身,所以我们只要用PS做一张从透明到黑色的渐变png就ok了。

三、拖动条背景

  我有个强迫症,那就是能不用png就不用png,除非是万不得已,比如上一节中颜色误差问题。所以我们这里谈谈那两个拖动条的背景该怎么实现。第一个是光谱,简单观察其实就是颜色渐变,只不过里面的 GradientStop 比较多罢了,光谱的XAML代码如下:

1 <LinearGradientBrush x:Key="ColorPickerRainbowBrush"  StartPoint="0,1">
2         <GradientStop Color="#ff0000"/>
3         <GradientStop Color="#ff00ff" Offset="0.167"/>
4         <GradientStop Color="#0000ff" Offset="0.334"/>
5         <GradientStop Color="#00ffff" Offset="0.501"/>
6         <GradientStop Color="#00ff00" Offset="0.668"/>
7         <GradientStop Color="#ffff00" Offset="0.835"/>
8         <GradientStop Color="#ff0000" Offset="1"/>
9     </LinearGradientBrush>

  第二个背景也很简单,就是普通的 DrawingBrush ,不过可能接触过它的人不多,简单的来说当设置属性 TileMode="Tile" 时,它会使用我们提供的单位画笔来平铺整个画布,通过观察google的ColorPicker,我们发现,这里的单位画笔是一深一浅的两个方块,和一条不太明显的分割线组成的,所以最后的代码如下:

 1 <DrawingBrush x:Key="ColorPickerOpacityBrush" Viewport="0,0,12,11" ViewportUnits="Absolute" Stretch="None" TileMode="Tile">
 2         <DrawingBrush.Drawing>
 3             <DrawingGroup>
 4                 <GeometryDrawing Brush="#d0cec7">
 5                     <GeometryDrawing.Geometry>
 6                         <GeometryGroup>
 7                             <RectangleGeometry Rect="0,0,6,5" />
 8                             <RectangleGeometry Rect="6,6,6,5" />
 9                         </GeometryGroup>
10                     </GeometryDrawing.Geometry>
11                 </GeometryDrawing>
12                 <GeometryDrawing Brush="#e7e7e2">
13                     <GeometryDrawing.Geometry>
14                         <RectangleGeometry Rect="0,5,12,1" />
15                     </GeometryDrawing.Geometry>
16                 </GeometryDrawing>
17             </DrawingGroup>
18         </DrawingBrush.Drawing>
19     </DrawingBrush>

  至于拖动条的样式由于篇幅有限我就不贴出来了。

三、算法

1、颜色的进制转换

  因为涉及到颜色的16进制和10进制的相互转换,所以需要写一个简单的算法加以处理。颜色的16进制转10进制.net已经给我们封装在类型 ColorConverter 中了,只要给静态方法 ConvertFromString 传入一个颜色字符串,再将返回值转换为 Color 就能实现我们想要的功能。而从10进制到16进制就太简单了,微软都不屑去做,那只能我们去实现了,只要一行代码: $"#{color.A:X2}{color.R:X2}{color.G:X2}{color.B:X2}" 。要注意的是,在WPF中最好将涉及到UI的数据转换做成转换器,以便在XAML中使用。

2、根据拖动条在光谱上的位置,改变顶部颜色拾取区域的主色调

  该算法用一张gif能简单的说明:

  

  为了实现该算法我们需要先搞清楚光谱的颜色分布,因为之前已经贴过光谱的画刷,所以我们可以给它加个注释:

  

  如上图,我把光谱分成了6块,数一数一共是7条竖线,它们分别对应光谱画刷中的7个 GradientStop ,现在我们已知拖动条的位置和7处节点处对应的颜色,求拖动条所处位置的颜色就非常简单了,因为拖动条是个 Slider 控件,我们可以把它的最大值设为6 Maximum="6" ,并从它的 OnValueChanged 事件中获知它此时的位置,假设此时的值为1.75,那么就相当于是落在了编号为1的方块中,而且是3/4位置处。这时该怎么计算此处的颜色呢?由于编号0和编号1的分割线(左起第二根)处的颜色恰好是第二个 GradientStop 的值#ff00ff(我们用color1代替),又因为第三个 GradientStop 值为#0000ff(我们用color2代替),所以3/4位置处的颜色应该是(color1 -(color1 - color2)* 3 / 4),至此该算法看似完成了,但是谷歌在这基础上多了一个步骤,详细请看最后一小节

3、根据主色调来改变拖动条在光谱上的位置

  对,这个算法就是2的逆过程。什么情况下会用到呢?还是看一下gif吧:

  

  既然是逆过程,我们就要反过来思考,把重点放在颜色上。这次我们要把光谱的10进制代码拿来分析,我们已经知道光谱被7个节点拆分成6块颜色渐变区域,用代码来表示的话就是这样的:

  

  稍加观察即可发现,每一块颜色渐变都只改变三色通道中的一个,比如从(0,0,255)到(0,255,255)改变的是G通道,它从0增加到了255。这说明了什么?这说明光谱上的颜色都是强迫症,它们的三色通道必定有一个值为255,也必定有一个值为0,只有一个通道的值在不停地改变。

  假设我们现在选中了一个颜色#4caf50,接下来该怎么分析它呢?16进制不适合观察,我们先把它转换成10进制:(76,175,80),可以发现,G通道175的值最大,而R通道76的值最小,这说明这个颜色比较喜欢G通道,而讨厌R通道,对B通道则无所谓,那么它在光谱上的表现就是处于R通道值最小,G通道值最大,B通道值无所谓的颜色渐变区域,在哪里呢?通过上图的代码可以判断应该在(0,255,255)到(0,255,0)这块,也就是编号3的这块。至于在块内的相对位置在上一小节中已经给出了计算方法,这里不再赘述。

  这里需要注意的是,有可能我们选取的颜色是形如(0,0,255)或(0,255,255)这种极值数量不唯一的情况,针对这种特殊样本,做好充足的验证即可,也不再赘述。

4、根据鼠标位置来改变选取颜色

  按照惯例,给张gif:

  

  获取鼠标位置很简单,我就不说明了,现在又已知主色调,那么我们可以做出如下示意图:

  

  如图,此时主色调为(255,0,0),假设鼠标位置为中心点,那么选取的颜色是什么?如果不能一步算出,就分而算之。我们先计算左右两边中点的颜色,很简单,利用之前贴出的算法计算后得出左侧中点的颜色为(127,127,127),右侧的为(127,0,0),故中心点的颜色为(127,63,63),或者是(127,64,64),主要看你舍入的规则。

5、根据主色调来改变拾取点位置

  这里的gif和小节3中的一样:

  

  可以看到,选取一个预置的颜色后,不仅仅是光谱位置变了,颜色选取点的位置也变了。假设我们选取了一个预置颜色#4caf50,它的10进制为:(76,175,80),再假设此时我们也知道主色调(也就是颜色拾取区域右上角的颜色),如此一来就和小节3一样了,只不过从原来的一维变成了二维而已。

6、不太明白谷歌的逻辑

  假如给定一个颜色(76,175,80),通过上面5小节的内容,你可能算出来右上角主色调为(0,255,80),但google的ColorPicker却是(0,255,10),这不是个特殊情况,例如再点击一个预置颜色(244,67,54),根据我们的算法主色调应该是(255,67,0),但google的结果是(255,17,0),有兴趣你可以多试试一些预置值。

  所以google的答案到底是如何计算而成的?只要尝试几组数据,你会发现谷歌是这么计算非极值通道的值的255*(min-common)/(min-max)。至于为什么要这么计算,希望了解的园友不吝赐教。

四、截图

  

  

五、源码

  本文所讨论的颜色拾取器源码已经在github开源:https://github.com/NaBian/HandyControl

原文地址:https://www.cnblogs.com/nabian/p/9267646.html

时间: 2024-08-29 10:32:47

WPF 控件库——仿制Chrome的ColorPicker的相关文章

WPF控件库之Menu控件

WPF控件库之Menu(1) Menu 是一个控件,使用该控件可以对那些与命令或事件处理程序相关联的元素以分层方式进行组织.每个 Menu 可以包含多个 MenuItem 控件.每个 MenuItem 都可调用命令或调用 Click 事件处理程序.MenuItem 也可以有多个 MenuItem 元素作为子项,从而构成子菜单. Menu 是一个控件,使用该控件可以对那些与命令或事件处理程序相关联的元素以分层方式进行组织.每个 Menu 可以包含多个 MenuItem 控件.每个 MenuItem

WPF 控件库——可拖动选项卡的TabControl

原文:WPF 控件库--可拖动选项卡的TabControl 一.先看看效果 二.原理 1.选项卡大小和位置 这次给大家介绍的控件是比较常用的TabControl,网上常见的TabControl样式有很多,其中一部分也支持拖动选项卡,但是带动画效果的很少见.这也是有原因的,因为想要做一个不失原有功能,还需要添加动画效果的控件可不是一行代码的事.要做成上图中的效果,我们不能一蹴而就,最忌讳的是一上来就想实现所有效果. 一开始,我们最好先用Blend看看原生的TabControl样式模板部分是如何实现

《Dotnet9》系列-开源C# WPF控件库强力推荐

本系列已介绍三款开源C# WPF控件库,其中一款国外的,另两款是国内的,大家如有比较好的开源C# WPF控件库,欢迎向Dotnet9推荐,您可在本文下方留言,谢谢您对dotnet的关注和支持,让我们期待dotnet更好的明天,以下是Dotnet9已完成的3篇开源C# WPF控件库推荐文章: 1.<Dotnet9>系列-开源C# WPF控件库1<MaterialDesignInXAML>强力推荐. 2.<Dotnet9>系列-开源C# WPF控件库2<Panuon.

(四)开源C# WPF控件库《AduSkin – UI》

微信公众号:[Dotnet9的博客],网站:[Dotnet9],问题或建议:[请网站留言], 如果对您有所帮助:[欢迎赞赏]. (四)开源C# WPF控件库<AduSkin> 追求极致,永臻完美 A Beautiful WPF Control UI 一款简单漂亮的WPF UI,融合部分开源框架的组件,为个人定制的UI,可供学者参考. 阅读导航 关于<AduSkin> 1.1 控件库全貌 1.2 动态修改主题色 1.3 技术交流 基于<AduSkin>控件库衍生的Case

[WPF]是时候将WPF控件库从.Net Framework升级到.NET Core 3.1

1. 升级到Core的好处 去年中我曾考虑将我的控件库项目Kino.Toolkit.Wpf升级到.NET Core,不过很快放弃了,因为当时.NET Core是预览版,编译WPF还需要使用最新的Visual Studio 2019,这样作为一个教学项目不够友好.到了今天.NET Core 3.1都出来了,已经正式支持WPF和Winform,Visual Studio 2019也已经普及,我觉得应该是时候将我的控件库升级到.NET Core.那么现在是WPF正式迁移到.NET Core的好时机吗?

WPF控件库:图片按钮的封装

需求:很多时候界面上的按钮都需要被贴上图片,一般来说: 1.按钮处于正常状态,按钮具有背景图A 2.鼠标移至按钮上方状态,按钮具有背景图B 3.鼠标点击按钮状态,按钮具有背景图C 4.按钮处于不可用状态,按钮具有背景图D 实现起来,毫无疑问,没什么难度.但是过程还是比较繁琐.这里我将这个过程封装为新的控件类:ImageButton ImageButton中有四个属性(支持绑定),分别对应上面A.B.C.D四个背景图的路径. #region 属性 /// <summary> /// 按钮处于正常

WPF 控件库——带有惯性的ScrollViewer*(转)

转:https://blog.csdn.net/ahilll/article/details/82418892 一.先看看效果 二.原理 虽然效果很简单,但是网上的一些资料涉及的代码量非常可观,而且效果也不是很理想,滚动的时候没有一个顺滑感.我这里提供的源码一共120多行,就能实现上图的效果. 本质上我们只要接管ScrollViewer的滚动逻辑,并且把这个逻辑替换成带有惯性的即可,那么如何去接管呢?这里的关键是先屏蔽ScrollViewer的鼠标滚轮事件: 1 protected overri

WPF控件库:文字按钮的封装

需求:封装按钮,按钮上面只显示文字.在鼠标移上去.鼠标点击按钮.以及将按钮设为不可用时按钮的背景色和前景色需要发生变化 实现:继承Button类,封装如下6个属性: #region 依赖属性 /// <summary> /// 当鼠标移到按钮上时,按钮的前景色(这是依赖属性) /// </summary> public static readonly DependencyProperty MouserOverForegroundProperty = DependencyProper

WPF 精修篇 Winform 嵌入WPF控件

原文:WPF 精修篇 Winform 嵌入WPF控件 首先 创建WPF控件库 这样就有了一个WPF界面 在wpf中增加界面等 在winform中增加WPFDLL 重新生成解决方案 在左侧工具栏 出现WPF 控件 拖到窗体 效果 原文地址:https://www.cnblogs.com/lonelyxmas/p/12075801.html