Silverlight 的图像编辑器






4.93/5 (11投票s)
基于 Silverlight 的简单图像编辑器,允许用户在线创建简单的图像
引言
如果您正在使用动画、图形和各种吸引人的服务来创建有趣且引人入胜的 Web 应用程序,那么有时您需要用户在线创建图像。例如,将其发布到其他用户的页面上,或者类似的东西。本文介绍了一个您可以用于此类目的的控件。该控件基于 Silverlight 4。
应用程序概述
正如我之前所说,附件中的此应用程序包含一个允许 Web 用户在线创建简单图像的控件。该控件不创建任何 JPEG 或 PNG,但它会向开发人员返回 ImageSource
,开发人员可以根据自己的需要使用此 ImageSource
:创建图像、背景等。
以下是提供的工具和功能
- 圆头画笔
- 画笔大小
- 画笔颜色
- 画笔不透明度
- 清除和撤销功能
以下屏幕截图显示了编辑器的布局
顶部是“撤销”和“清除”命令按钮。中间是带有图像的画布。底部是颜色、大小、不透明度和画笔预览。
此外,在应用程序中,还有一个“**获取快照**”按钮,演示了将绘制的图像转换为 ImageSource
的功能。底部有一个 Border
,其背景就是该 ImageSource
。预览示例如下
如何使用
此控件具有以下 public
属性和方法可供外部使用
BrushColor
- 依赖属性。画笔颜色BrushSize
- 依赖属性。画笔大小BrushAlpha
- 依赖属性。画笔不透明度Clear()
- 清除画布HideTools()
- 隐藏底部工具ShowTools()
- 显示底部工具Undo()
- 可用时撤销GetImage()
- 获取当前图像
可撤销操作的最大数量存储在 EditorCanvas
类中的常量 maxUndo
中。如果有人需要将其设为属性 - 尽管去做吧。:)
HideTools()
和 ShowTools()
使用在相关 XAML 文件中定义的动画。
代码
让我们看看源代码。
首先,我想描述图像是如何绘制的。有 3 层
<Grid x:Name="Sheet" Background="White" SizeChanged="Sheet_SizeChanged">
<Grid.Clip>
<RectangleGeometry />
</Grid.Clip>
</Grid>
<Canvas x:Name="CursorCanvas" Background="Transparent" Cursor="None">
<Ellipse x:Name="Cursor" Canvas.ZIndex="100" Visibility="Collapsed"
Opacity="{Binding BrushAlpha}" Width="{Binding BrushSize}"
Height="{Binding BrushSize}">
<Ellipse.Fill>
<SolidColorBrush Color="{Binding BrushColor}" />
</Ellipse.Fill>
</Ellipse>
<Path Stroke="Black" Canvas.ZIndex="101" StrokeThickness="1" x:Name="Cross"
VerticalAlignment="Center" HorizontalAlignment="Center"
Visibility="{Binding ElementName=Cursor, Path=Visibility}">
<Path.Data>
<GeometryGroup>
<LineGeometry StartPoint="3,0" EndPoint="8,0"/>
<LineGeometry StartPoint="-3,0" EndPoint="-8,0"/>
<LineGeometry StartPoint="0,3" EndPoint="0,8"/>
<LineGeometry StartPoint="0,-3" EndPoint="0,-8"/>
</GeometryGroup>
</Path.Data>
</Path>
</Canvas>
<Canvas x:Name="InputCanvas" Background="Transparent" Cursor="None"
MouseLeftButtonDown="InputCanvas_MouseLeftButtonDown"
MouseLeftButtonUp="InputCanvas_MouseLeftButtonUp" MouseEnter="InputCanvas_MouseEnter"
MouseMove="InputCanvas_MouseMove" MouseLeave="InputCanvas_MouseLeave">
</Canvas>
Sheet
- 这是一个grid
,包含已绘制的几何图形。CursorCanvas
- 包含画笔光标和十字光标InputCanvas
- 收集所有鼠标输入。我们需要它,因为否则鼠标将始终停留在CursorCanvas
内的Ellipse
(画笔光标)上,并且不会有MouseEnters
、MouseLeave
和MouseMove
事件。
当用户按下鼠标左键时,将执行以下代码
InputCanvas.CaptureMouse();
geometry = new GeometryGroup();
geometry.FillRule = FillRule.Nonzero;
figure = new Path();
figure.Fill = new SolidColorBrush(BrushColor) { Opacity = BrushAlpha };
Sheet.Children.Add(figure);
首先,我们需要捕获鼠标,以避免在用户移出控件后绘图结束。接下来,我们创建一个具有选定颜色和不透明度的路径,并将其添加到 Sheet
。Geometry 变量包含当前正在绘制的几何图形。
当用户移动鼠标时,Ellipses
会添加到“MouseMove
”事件发生的Locations。Ellipses
通过矩形连接。我称这些矩形为“connectors
”。想法如下面的图像所示
要绘制该矩形,我需要知道 ellipse
的大小及其中心坐标。我知道这一点。所以这很容易。以下代码绘制了两个 ellipse
之间的单个连接器
Point a, b, c, d;
double x1 = mousePosition.X;
double y1 = mousePosition.Y;
double x2 = prevMousePosition.Value.X;
double y2 = prevMousePosition.Value.Y;
double l = BrushSize / 2;
PathGeometry conntector = new PathGeometry();
conntector.FillRule = FillRule.Nonzero;
double alpha = Math.Atan2(y2 - y1, x2 - x1);
double beta = Math.PI / 2 - alpha;
a = new Point(x1 - l * Math.Cos(beta), y1 + l * Math.Sin(beta));
b = new Point(x2 - l * Math.Cos(beta), y2 + l * Math.Sin(beta));
c = new Point(x2 + l * Math.Cos(beta), y2 - l * Math.Sin(beta));
d = new Point(x1 + l * Math.Cos(beta), y1 - l * Math.Sin(beta));
PointCollection points = new PointCollection();
points.Add(d);
points.Add(c);
points.Add(b);
conntector.Figures.Add(new PathFigure()
{
IsClosed = true,
IsFilled = true,
StartPoint = a,
Segments = { new PolyLineSegment() { Points = points } }
});
变量 a
、b
、c
、d
是描述连接器的矩形的顶点。这里有一个重要的事情。我因此浪费了一些时间。这个重要的事情是 points.Add()
命令的顺序。为了正确填充几何图形,您必须按 逆时针顺序 添加点!否则,即使您选择 NonZero
,填充也会像使用 EvenOdd fill
方法一样工作。
好的,我们有什么?一个包含一组椭圆和连接器的路径。用户释放鼠标左键,我们需要处理这些数据。最简单的方法是让这个 Path
保留在 Sheet
中,并绘制下一个图形。但是,有些用户喜欢绘制非常复杂的图像。:) 所以,如果有很多几何图形,您可能会遇到减慢。不好。因此,我决定渲染此 Path
并将渲染的图像设置为 Sheet
的 Background
。这显示了良好的结果。没有减慢。
以下方法在用户释放 leftbutton
时调用
private void EndFigure()
{
mouseLeftButton = false;
InputCanvas.ReleaseMouseCapture();
prevMousePosition = null;
Sheet.Background = new ImageBrush() { ImageSource = ConvertToImage() };
Sheet.Children.Clear();
}
ConvertToImage()
是一个将 Sheet
的当前外观转换为 ImageSource
的方法。它也在 GetImage()
方法中使用,以后我将不再对其进行描述。所以,这是它
private ImageSource ConvertToImage()
{
WriteableBitmap bitmap = new WriteableBitmap(Sheet, new TranslateTransform());
bitmap.Render(Sheet, new TranslateTransform());
return bitmap;
}
相当简单,不是吗?
我想展示的另一件事是颜色选择器。在应用程序中,它看起来像
我想向您展示构建此类调色板的算法。
int x,y, count;
x = y = count = 0;
for (int r = 0; r <= 255; r += 51)
{
for (int g = 0; g <= 255; g += 51)
{
for (int b = 0; b <= 255; b += 51)
{
Border brd = new Border()
{
Background = new SolidColorBrush
(Color.FromArgb(255, (byte)r, (byte)g, (byte)b)),
BorderThickness = new Thickness(0),
Margin = new Thickness(1),
Width = 15,
Height = 15,
Cursor = Cursors.Hand
};
brd.MouseLeftButtonDown += delegate
{
SelectedColor = ((SolidColorBrush)brd.Background).Color;
ppColors.IsOpen = false;
};
count++;
cnvColors.Children.Add(brd);
Canvas.SetLeft(brd, x);
Canvas.SetTop(brd, y);
if (count >= 6)
{
y = 0;
x += 17;
count = 0;
}
else
{
y += 17;
}
}
}
}
我的想法是遍历所有可能的 R、G 和 B 值组合,并为容器添加具有该 RGB 的边框。由于 R、G 和 B 是字节,其最大值为 255。因此,颜色的总数为 255*255*255 = 16581375……嗯,太多了。:) 所以,让我们减少步数:并将组件值增加 51 而不是 1。因此,对于一个颜色分量,我们将只有 255/51=5 个值。
颜色总数为 5*5*5 = 125。对于此应用程序来说足够了。为了使调色板看起来不错,我选择了偏移值,使得相同的颜色在同一列和同一行。
结果
在此应用程序中,我尝试创建一个易于使用且易于修改的控件,它允许您在应用程序中创建简单的图像编辑器。您可以按原样使用它,或仅重新使用其中的某些部分:例如 ColorPicker
。
此外,在这里您可以获得有关使用几何图形、鼠标捕获以及通过将大量控件渲染到图像中进行优化的有趣信息。
历史
- 2011 年 4 月 26 日:初始帖子