用任意形状的笔画画

时间:2014-12-17 17:43:49

标签: c# .net graphics drawing

我需要使用自定义光圈在位图上绘制线条。在下面的示例中,有两行。左边是水平的。右边是对角线 - 向下和向右: Example image 在示例图像中,光圈和路径以红色显示。生成的几何图形以黑色显示。上述两个示例都使用相同的光圈,但沿不同的路径擦拭。

我希望能够使用任意形状的笔进行绘制,即使上面的示例仅显示矩形。

我尝试使用System.Drawing.2D命名空间,但它似乎没有做我需要它做的事情。在笔上使用自定义端盖似乎很有希望,但端盖随着线的方向旋转。此外,获得正确的线宽似乎具有挑战性。failed end-caps example

我已经考虑过一遍又一遍地绘制光圈,以路径上的不同点为中心,但这似乎并不符合要求。最小化光圈的数量也很困难。

我最好的想法是尝试将'line'绘制为填充形状。起初,我认为凸包算法就是答案 - 只需在绘制开始时和绘制结束时获取孔径的顶点,并将它们通过凸包算法来找到“外部”顶点。这适用于我上面的第一个例子,但是星形光圈表明这个解决方案是不完整的。它只适用于光圈自身凸起的情况。 enter image description here

简单地将所有顶点放入凸包算法中会导致以蓝色突出显示的区域被填充,但我只需要填充黑色区域。

4 个答案:

答案 0 :(得分:1)

这是一个丑陋,不优雅的解决方案,但您可以使用表示笔划方向和长度的向量,并沿着该向量多次重绘您的形状。 (你必须选择一个绘制它的间隔,它不会留下间隙。)如果你想要别名输出,这只会产生正确的结果;它会搞砸GDI +所做的任何抗锯齿,因为带有中间调的像素可能会被吸引,使它们比它们更暗。

答案 1 :(得分:1)

实际上,您正在使用您提出的解决方案 - 使用各个光圈段创建平行四边形。但是,当你试图用除了实心刷之外的任何东西填充它时,你会遇到问题,因为你实际上是在做重复填充。

最好的办法是创建所有这些多边形,就像你说的那样,然后将它们组合在一起。你最终会得到一个代表你想要的轮廓的单一多边形,然后你可以用你想要的任何方式填充它。

如果您使用的是WPF,只需使用包含所有多边形作为子项的GeometryGroup,然后将该组用作CombinedGeometry中具有“联合”或“排除”模式的第一个几何图形,为第二个参数传递null。

如果你没有使用WPF,你可以尝试像Clipper这样的库,它有一个用C#/ .NET编写的版本,但更多的是用于任意绘图而不仅仅是WPF。

希望这有帮助!

答案 2 :(得分:0)

我所获得的最好的想法涉及一些多余的绘图,但我认为它会起作用。

将光圈多边形视为线段列表。对于列表中的每个线段,使用移动开始时光圈的线段绘制平行四边形,并在移动结束时使用光圈的相同线段 Parallelogram Draw

上图显示了构成恒星的十个线段中的三个。对于某些笔形状,段之间可能存在大量重叠,但此方法导致所有正确的像素都被填充。请注意,大部分绿色区域已由蓝色区域和红色区域,右下角只有一小部分是绿色的。我认为,冗余填充这些像素是浪费,但会产生正确的图像。

其他想法:

可能有一种方法可以在不重复填充相同区域的情况下执行此操作,并且可能涉及光线投射。注意右下方的红线和绿线的交点如何落在不属于原始光圈的顶点上。您需要沿着移动方向从右侧多边形的最右侧顶点绘制一条光线,并找到与绿线相交的位置,以便正确绘制最终形状。

答案 3 :(得分:0)

我在WPF中解决了这个问题。我想要一个可以用曲线和孔做复杂形状的解决方案。我得到的最接近的是在原始形状上使用GetFlattenedPathGeometry,它将曲线转换为线段,然后运行上述方法。这是一个冗余的计算,但结合平行四边形,然后在开始和结束点“标记”原始形状,导致最终几何图形中的几何形状相对较少。

public Geometry translateGeo(Geometry baseGeo, Point translate)
{
    // sweeps a geometry linearly from current location through vector

    Debug.WriteLine("Translating Geometry");
    Debug.WriteLine("Original Outline Verticies: " + CountVerticies(baseGeo.GetFlattenedPathGeometry()));

    Geometry sweptPathGeo = baseGeo.Clone();
    Geometry capGeo = baseGeo.Clone();
   
    capGeo.Transform = new TranslateTransform(translate.X, translate.Y);
    sweptPathGeo = new CombinedGeometry(GeometryCombineMode.Union, sweptPathGeo, capGeo);
    sweptPathGeo = sweptPathGeo.GetFlattenedPathGeometry();


    geometry = sweptPathGeo.Clone();


    PathGeometry pathGeo = baseGeo.GetFlattenedPathGeometry();

    foreach (PathFigure figure in pathGeo.Figures)
    {
        Point startPoint = figure.StartPoint;
        //Debug.WriteLine(startPoint.X + ", " + startPoint.Y);
        foreach (PathSegment segment in figure.Segments)
        {
            PolyLineSegment polySegment = segment as PolyLineSegment;
            if (polySegment != null)
            {
                foreach (Point point in polySegment.Points)
                {
                    sweptPathGeo = new CombinedGeometry(GeometryCombineMode.Union, sweptPathGeo, getShadow(startPoint, point, translate));
                    startPoint = point;
                }
            }

            LineSegment lineSegment = segment as LineSegment;
            if (lineSegment != null)
            {
                sweptPathGeo = new CombinedGeometry(GeometryCombineMode.Union, sweptPathGeo, getShadow(startPoint, lineSegment.Point, translate));
                startPoint = lineSegment.Point;
            }
        }
    }

    //sweptPathGeo = sweptPathGeo.GetOutlinedPathGeometry();
    Debug.WriteLine("Finale Outline Verticies: " + CountVerticies(sweptPathGeo.GetFlattenedPathGeometry()));
    return sweptPathGeo;

}
public Geometry getShadow(Point startPoint, Point endPoint, Point translate)
{

    PointCollection points = new PointCollection();
    points.Add(startPoint);
    points.Add(endPoint);
    points.Add(new Point(endPoint.X + translate.X, endPoint.Y + translate.Y));
    points.Add(new Point(startPoint.X + translate.X, startPoint.Y + translate.Y));
    points.Add(startPoint);

    Polygon poly = new Polygon();
    poly.Points = points;
    poly.Arrange(geometry.Bounds);
    poly.Measure(geometry.Bounds.Size);

        PathGeometry returnGeo = poly.RenderedGeometry.GetOutlinedPathGeometry();

    return returnGeo;
    //foreach (Point point in points) Debug.WriteLine(point.X + ", " + point.Y);

}
private int CountVerticies(PathGeometry geo)
{
    int verticies = 0;
    foreach (PathFigure figure in geo.Figures)
    {
        Point startPoint = figure.StartPoint;
        verticies += 1;
        foreach (PathSegment segment in figure.Segments)
        {
            PolyLineSegment polySegment = segment as PolyLineSegment;
            if (polySegment != null) verticies += polySegment.Points.Count;
            
            LineSegment lineSegment = segment as LineSegment;
            if (lineSegment != null) verticies += 1;
        }
    }
    return verticies;
}