65.9K
CodeProject 正在变化。 阅读更多。
Home

Adobe 渐变拾色器克隆

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.97/5 (49投票s)

2009年10月22日

CPOL

4分钟阅读

viewsIcon

106128

downloadIcon

3559

关于实现渐变管理器的一篇文章

Gradient Editor

引言

在设计图形编辑器时,很大一部分工作都关乎可用性。控件响应用户操作的方式会极大地影响艺术家使用你的应用程序。每次你改变工作流程,用户都必须重新适应功能。

那么,在设计图形控件时,为什么总是要从零开始呢?多年来,Adobe(r) Photoshop 已经成为图形编辑器工作方式的一种行业标准。本文将渐变编辑功能引入到你的标准 .NET LinearGradientPathGradient 画笔中。

背景

基本有两种常用的方式来建模渐变停止点。第一种方式(也是 Adobe Illustrator 的渐变处理方式)如下:

illustrator gradient

在这里,每个渐变停止点都有一个位置(0-100%)和一个关联的透明度值。
这样,颜色和 Alpha 值对每个渐变停止点都是专门的。

另一种方式是将渐变视为 Adobe Photoshop 那样:

photoshop gradient

在这里,颜色渐变和 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 的调用,它会处理具有修改的中心聚焦值的渐变停止点,如下所示:

modified focus point

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

interpolation curve

现在,最后一步是添加第一个和最后一个点,因为 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

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

gradienteditpanel

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

gradientcollectioneditor

最后,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日:初始版本
© . All rights reserved.