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

Silverlight 的图像编辑器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (11投票s)

2011年4月26日

CPOL

5分钟阅读

viewsIcon

36170

downloadIcon

1606

基于 Silverlight 的简单图像编辑器,允许用户在线创建简单的图像

引言

如果您正在使用动画、图形和各种吸引人的服务来创建有趣且引人入胜的 Web 应用程序,那么有时您需要用户在线创建图像。例如,将其发布到其他用户的页面上,或者类似的东西。本文介绍了一个您可以用于此类目的的控件。该控件基于 Silverlight 4。

应用程序概述

正如我之前所说,附件中的此应用程序包含一个允许 Web 用户在线创建简单图像的控件。该控件不创建任何 JPEG 或 PNG,但它会向开发人员返回 ImageSource,开发人员可以根据自己的需要使用此 ImageSource:创建图像、背景等。

以下是提供的工具和功能

  • 圆头画笔
  • 画笔大小
  • 画笔颜色
  • 画笔不透明度
  • 清除和撤销功能

以下屏幕截图显示了编辑器的布局

3.jpg

顶部是“撤销”和“清除”命令按钮。中间是带有图像的画布。底部是颜色、大小、不透明度和画笔预览。

此外,在应用程序中,还有一个“**获取快照**”按钮,演示了将绘制的图像转换为 ImageSource 的功能。底部有一个 Border,其背景就是该 ImageSource。预览示例如下

2.jpg

如何使用

此控件具有以下 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 (画笔光标)上,并且不会有 MouseEntersMouseLeaveMouseMove 事件。

当用户按下鼠标左键时,将执行以下代码

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”。想法如下面的图像所示

5.jpg

要绘制该矩形,我需要知道 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 } }
});

变量 abcd 是描述连接器的矩形的顶点。这里有一个重要的事情。我因此浪费了一些时间。这个重要的事情是 points.Add() 命令的顺序。为了正确填充几何图形,您必须按 逆时针顺序 添加点!否则,即使您选择 NonZero,填充也会像使用 EvenOdd fill 方法一样工作。

好的,我们有什么?一个包含一组椭圆和连接器的路径。用户释放鼠标左键,我们需要处理这些数据。最简单的方法是让这个 Path 保留在 Sheet 中,并绘制下一个图形。但是,有些用户喜欢绘制非常复杂的图像。:) 所以,如果有很多几何图形,您可能会遇到减慢。不好。因此,我决定渲染此 Path 并将渲染的图像设置为 SheetBackground。这显示了良好的结果。没有减慢。

以下方法在用户释放 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;
}

相当简单,不是吗?

我想展示的另一件事是颜色选择器。在应用程序中,它看起来像

4.jpg

我想向您展示构建此类调色板的算法。

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 日:初始帖子
© . All rights reserved.