Adobe 渐变拾色器克隆






4.97/5 (49投票s)
关于实现渐变管理器的一篇文章

引言
在设计图形编辑器时,很大一部分工作都关乎可用性。控件响应用户操作的方式会极大地影响艺术家使用你的应用程序。每次你改变工作流程,用户都必须重新适应功能。
那么,在设计图形控件时,为什么总是要从零开始呢?多年来,Adobe(r) Photoshop 已经成为图形编辑器工作方式的一种行业标准。本文将渐变编辑功能引入到你的标准 .NET LinearGradient
和 PathGradient
画笔中。
背景
基本有两种常用的方式来建模渐变停止点。第一种方式(也是 Adobe Illustrator 的渐变处理方式)如下:

在这里,每个渐变停止点都有一个位置(0-100%)和一个关联的透明度值。
这样,颜色和 Alpha 值对每个渐变停止点都是专门的。
另一种方式是将渐变视为 Adobe Photoshop 那样:

在这里,颜色渐变和 Alpha 渐变是分开的,这使得应用各种难以在 Illustrator 中创建的图案变得容易。
因此,我决定按照第二种方式来实现我的渐变管理器。但问题是,.NET GDI+ 包装器无法处理分离的颜色和 Alpha 渐变。只有带有 Alpha 通道的颜色才能放入 ColorBlend
对象,因此只有第一种实现是可能的……
我必须找到一种方法将每通道的两个渐变合并在一起,最终形成了这个算法。
渐变转换算法
首先,有一个新的数据结构用于存储渐变,它可以处理不同类型的停止点。
/// <summary>
/// ColorBlend object
/// </summary>
public class Gradient:ICloneable
{
/// <summary>
/// class for holding a gradient point
/// </summary>
public abstract class Point : IComparable<double>
/// <summary>
/// class for holding points and updating
/// controls connected to this colorblend
/// </summary>
public class PointList<T> : CollectionBase<Gradient, T> where T : Point
public static implicit operator ColorBlend(Gradient blend)
}
public class AlphaPoint : Gradient.Point, IComparable<AlphaPoint>,ICloneable
public class ColorPoint : Gradient.Point, IComparable<ColorPoint>,ICloneable
这个类可以像任何 ColorBlend
对象一样使用,因为它通过调用转换运算符隐式转换为 ColorBlend
,该运算符会调用渐变转换算法并缓存结果,以便更有效地使用。
现在,算法的第一步是排序颜色和 Alpha 点的列表,以便可以通过修改后的二分查找算法进行高效搜索和插值。
/// <summary>
/// creates color blend out of color point data
/// </summary>
/// <returns></returns>
private ColorBlend CreateColorBlend()
{
//sort all points
ColorPoint[] colpoints = _colors.SortedArray();
AlphaPoint[] alphapoints = _alphas.SortedArray();
//...
}
//generic interval searching in O(log(n))
private void SearchPos<T>(T[] list, T pos, out int a, out int b) where T : IComparable<T>
{
int start = a = 0, end = b = list.Length - 1;
while (end >= start)
{
int mid = start + (end - start) / 2;
switch (list[mid].CompareTo(pos))
{
case 0://found point
a = b = mid;
return;
case 1: end = mid - 1; b = mid; break;//search left
default: start = mid + 1; a = mid; break;//search right
}
}
//found interval
}
现在,对于每个 Alpha 点和颜色点,将它们添加到输出值的列表中,同时对颜色通道和 Alpha 通道进行插值。如果当前处理的点是一个 Alpha 点,在 Alpha 点列表中查找其位置将得到该点的原始位置,而如果在颜色数组中查找其位置,则很可能得到一个区间,除非在该精确位置有一个颜色点。
当搜索遇到单个点而不是区间时,将跳过插值。
//adds a new position to the list
private void AddPosition(ColorPoint[] colpoints, AlphaPoint[] alphapoints,
SortedList<float, Color> positions, double pos)
{
if (positions.ContainsKey((float)pos))
return;
int alpha_a, alpha_b;
int color_a, color_b;
//evaluate positions
SearchPos<AlphaPoint>(alphapoints,
new AlphaPoint(0, pos), out alpha_a, out alpha_b);
SearchPos<ColorPoint>(colpoints,
new ColorPoint(Color.Black, pos), out color_a, out color_b);
//interpolate
positions.Add((float)pos, Color.FromArgb(
Interpolate(alphapoints, alpha_a, alpha_b, pos),
Interpolate(colpoints, color_a, color_b, pos)));
}
// interpolates alpha list
private byte Interpolate(AlphaPoint[] list, int a, int b, double pos)
{
if (b < a)
return 0;
if (a == b) return (byte)(list[a].Alpha * 255.0);
//compute involving focus position
return (byte)XYZ.ClipValue(
(list[a].Alpha + FocusToBalance(list[a].Position,
list[b].Position, list[b].Focus, pos)
* (list[b].Alpha - list[a].Alpha)) * 255.0, 0.0, 255.0);
}
注意对 FocusToBalance
的调用,它会处理具有修改的中心聚焦值的渐变停止点,如下所示:

这基本上是对插值函数的修改,这里的插值是线性的。

现在,最后一步是添加第一个和最后一个点,因为 GDI+ 的 ColorBlend
要求第一个停止点在 0% 位置,最后一个在 100% 位置。此外,如果渐变中没有任何点,则生成一些默认值。
//add first/last point
if (positions.Count < 1 || !positions.ContainsKey(0f))
positions.Add(0f, positions.Count < 1 ?
Color.Transparent : positions.Values[0]);
if (positions.Count < 2 || !positions.ContainsKey(1f))
positions.Add(1f, positions.Count < 2 ?
Color.Transparent : positions.Values[positions.Count - 1]);
最终的渐变现在存储在 SortedList<span class="code-keyword"><float,Color>
中,并已准备好在 ColorBlend
对象中使用。
GradientEdit 控件
这个类的主要目的不是允许在代码中简单地创建渐变(实际上使用标准的 colorblend
对象要简单得多),而是为这个目的创建一个用户控件,因此有几个控件可以在任何用户界面上使用。

GradientEdit
是用于编辑单个渐变对象的控件,并提供 selectionchange 事件。
可以通过点击空白区域来创建停止点,并通过将它们拖出控件区域来删除它们。

GradientEditPanel
将编辑单个停止点所需的所有控件封装到一个用户控件中。
位置和 Alpha 值可以通过 SpinCombo
控件进行编辑,停止点可以被删除,并且
颜色可以选择屏幕外的颜色,或者通过显示文章中描述的颜色对话框来选择:Adobe Color Picker Clone。

最后,GradientEditPanel
junto con un editor de colecciones se encapsulan en gradientcollectioneditor
, que se utiliza como un cuadro de diálogo emergente.
它支持使用默认的 XML 导出器从文件加载和保存预设。
未来的版本将能够读取 Adobe Photoshop/Illustrator 的 .grd 集合。
Using the Code
你可以通过两种方式使用渐变。首先,你可以在代码中创建任何渐变,并像使用 colorblend 一样使用它。
private Gradient grd;
private GradientCollection coll;
/// <summary>
/// constructor
/// </summary>
public MyPictureBox(){
//
coll = new GradientCollection();
//
grd = new Gradient();
grd.Alphas.Add(new AlphaPoint(128, 0.0));
grd.Alphas.Add(new AlphaPoint(255, 1.0));
grd.Colors.Add(new ColorPoint(Color.Red, 0.0));
grd.Colors.Add(new ColorPoint(Color.Blue, 1.0));
}
protected override void OnPaint(PaintEventArgs e)
{
using (LinearGradientBrush lnbrs = new LinearGradientBrush(
new Point(0, 0), new Point(Math.Max(1, this.Width), 0),
Color.Transparent, Color.Black))
{
//implicit conversion here
lnbrs.InterpolationColors = grd;
e.Graphics.FillRectangle(lnbrs, this.ClientRectangle);
}
}
其次,你可以使用 GradientCollectionEditor
来编辑一个或多个渐变。
protected override void OnClick(EventArgs e)
{
using (GradientCollectionEditor edit = new GradientCollectionEditor())
{
//normally, you would use edit.Gradients.Load(...)
foreach (Gradient g in coll)
edit.Gradients.Add(g);
edit.SelectedGradient = grd;
//
if (edit.ShowDialog() == DialogResult.OK)
{
//normally, you would use edit.Gradients.Save(...)
coll.Clear();
foreach (Gradient g in edit.Gradients)
coll.Add(g);
grd = edit.SelectedGradient;
//
this.Refresh();
}
}
}
你甚至可以以 XML 格式加载和保存渐变。请查看随附的 XMLFormat.xsd 模式,它给出了 .grdx 文件的结构。加载和保存工作如下:
try
{
//save
coll.Save("%TEMP%/default.grdx");
//load
coll.Clear();
coll.Load("%TEMP%/default.grdx");
}
catch (Exception ex)
{
MessageBox.Show(ex.StackTrace);
}
请注意,XMLFormat
导入/导出器本身不验证文档,它只会在发生读取错误时抛出异常。
在未来的版本中,还将有一个 SVG 格式化器。
结论
还有一些问题需要修复,例如实现一个正确的 gamma 校正,目前还没有启用。我认为这仍然是一个非常有用的组件,你可以自由地在你的项目中使用它。在 DrawingEx
命名空间中还有一些其他工具,例如颜色按钮、带有量化器的 32 bpp 真彩色图标编码器、离散余弦变换器以及一些 3D 辅助类。
请尽情使用,并告诉我遇到的 bug...
历史
- 2009年10月20日:初始版本