【问题标题】:Pygame draw anti-aliased thick linePygame绘制抗锯齿粗线
【发布时间】:2015-08-15 04:22:35
【问题描述】:

我曾经在 pygame 中像这样画线(给定一些起点和终点):pygame.draw.line(window, color_L1, X0, X1, 2),其中 2 定义了线的粗细。

由于.draw 不支持抗锯齿,所以我搬到了.gfxdrawpygame.gfxdraw.line(window, X0[0], X0[1], X1[0], X1[1], color_L1)

但是,这不允许我定义线条的粗细。我怎么能同时拥有厚度和抗锯齿?

【问题讨论】:

  • 根据线的端点和宽度定义适当的偏移线,并使用它们来定义表示粗线的多边形。然后用pygame.gfxdraw.aapolygon()画出来。
  • 我的线在每一步都在旋转,所以我不确定将它的末端定义为多边形,在这种情况下 +-1 不起作用。
  • 按照我建议的方式绘制粗线是抵消退化多边形的一种情况。通常涉及相当多的数学。见An algorithm for inflating/deflating (offsetting, buffering) polygons

标签: python pygame line antialiasing


【解决方案1】:

这是一个稍长的代码,但可能会对某人有所帮助。 它使用向量并在连接两点的直线的每一侧创建一个笔划。

def make_vector(pointA,pointB): #vector between two points
    x1,y1,x2,y2 = pointA[0],pointA[1],pointB[0],pointB[1]
    x,y = x2-x1,y2-y1
    return x,y

def normalize_vector(vector): #sel explanatory
    x, y = vector[0], vector[1]
    u = math.sqrt(x ** 2 + y ** 2)
    try:
        return x / u, y / u
    except:
        return 0,0

def perp_vectorCL(vector): #creates a vector perpendicular to the first clockwise
    x, y = vector[0], vector[1]
    return y, -x

def perp_vectorCC(vector): #creates a vector perpendicular to the first counterclockwise
    x, y = vector[0], vector[1]
    return -y, x

def add_thickness(point,vector,thickness): #offsets a point by the vector
    return point[0] + vector[0] * thickness, point[1] + vector[1] * thickness

def draw_line(surface,fill,thickness, start,end): #all draw instructions
    x,y = make_vector(start,end)
    x,y = normalize_vector((x,y))


    sx1,sy1 = add_thickness(start,perp_vectorCC((x,y)),thickness//2)
    ex1,ey1 = add_thickness(end,perp_vectorCC((x,y)),thickness//2)
    pygame.gfxdraw.aapolygon(surface,(start,end,(ex1,ey1),(sx1,sy1)),fill)
    pygame.gfxdraw.filled_polygon(surface, (start, end, (ex1, ey1), (sx1, sy1)), fill)

    sx2, sy2 = add_thickness(start, perp_vectorCL((x, y)), thickness // 2)
    ex2, ey2 = add_thickness(end, perp_vectorCL((x, y)), thickness//2)
    pygame.gfxdraw.aapolygon(surface, (start, end, (ex2, ey2), (sx2, sy2)), fill)
    pygame.gfxdraw.filled_polygon(surface, (start, end, (ex2, ey2), (sx2, sy2)), fill)

【讨论】:

    【解决方案2】:

    您的回答完成了工作,但我认为这将是一种更好/更易读的方式。这是对您的回答的捎带,尽管如此感谢您。

    from math import atan2, cos, degrees, radians, sin
    
    def Move(rotation, steps, position):
        """Return coordinate position of an amount of steps in a direction."""
        xPosition = cos(radians(rotation)) * steps + position[0]
        yPosition = sin(radians(rotation)) * steps + position[1]
        return (xPosition, yPosition)
    
    def DrawThickLine(surface, point1, point2, thickness, color):
        angle = degrees(atan2(point1[1] - point2[1], point1[0] - point2[0]))
    
        vertices = list()
        vertices.append(Move(angle-90, thickness, point1))
        vertices.append(Move(angle+90, thickness, point1))
        vertices.append(Move(angle+90, thickness, point2))
        vertices.append(Move(angle-90, thickness, point2))
    
        pygame.gfxdraw.aapolygon(surface, vertices, color)
        pygame.gfxdraw.filled_polygon(surface, vertices, color)
    

    请记住,这将厚度更多地视为半径而不是直径。如果你想让它更像一个直径,你可以将变量的每个实例除以 2。

    所以无论如何,这会计算矩形的所有点并将其填充。它通过转到每个点并通过旋转 90 度并向前移动来计算两个相邻点来实现这一点。

    【讨论】:

      【解决方案3】:

      您还可以使用 pygame.draw.aalines() 函数通过在原始线周围绘制 +/- 1-N 像素的线副本来做一些修改(是的,这不是超级有效,但它可以在捏)。例如,假设我们有一个要绘制的线段列表 (self._segments) 和宽度 (self._LINE_WIDTH):

      for segment in self._segments:
        if len(segment) > 2:
          for i in xrange(self._LINE_WIDTH):
            pygame.draw.aalines(self._display, self._LINE_COLOR, False,
                                ((x,y+i) for x,y in segment))
            pygame.draw.aalines(self._display, self._LINE_COLOR, False,
                                ((x,y-i) for x,y in segment))
            pygame.draw.aalines(self._display, self._LINE_COLOR, False,
                                ((x+i,y) for x,y in segment))
            pygame.draw.aalines(self._display, self._LINE_COLOR, False,
                                ((x-i,y) for x,y in segment))
      

      【讨论】:

        【解决方案4】:

        经过多次尝试和错误,最佳方法如下:

        1. 首先,我们根据线的X0_{x,y} 起点和X1_{x,y} 终点定义形状的中心点:

          center_L1 = (X0+X1) / 2.
          
        2. 然后求直线的斜率(角度):

          length = 10  # Total length of line
          thickness = 2
          angle = math.atan2(X0[1] - X1[1], X0[0] - X1[0])
          
        3. 使用斜率和形状参数,您可以计算出盒子末端的以下坐标:

          UL = (center_L1[0] + (length/2.) * cos(angle) - (thickness/2.) * sin(angle),
                center_L1[1] + (thickness/2.) * cos(angle) + (length/2.) * sin(angle))
          UR = (center_L1[0] - (length/2.) * cos(angle) - (thickness/2.) * sin(angle),
                center_L1[1] + (thickness/2.) * cos(angle) - (length/2.) * sin(angle))
          BL = (center_L1[0] + (length/2.) * cos(angle) + (thickness/2.) * sin(angle),
                center_L1[1] - (thickness/2.) * cos(angle) + (length/2.) * sin(angle))
          BR = (center_L1[0] - (length/2.) * cos(angle) + (thickness/2.) * sin(angle),
                center_L1[1] - (thickness/2.) * cos(angle) - (length/2.) * sin(angle))
          
        4. 使用计算出的坐标,我们绘制一个未填充的抗锯齿多边形(感谢@martineau),然后按照 pygame 的 gfxdraw 用于绘制形状的模块的 documentation 中的建议填充它。

          pygame.gfxdraw.aapolygon(window, (UL, UR, BR, BL), color_L1)
          pygame.gfxdraw.filled_polygon(window, (UL, UR, BR, BL), color_L1)
          

        【讨论】:

        【解决方案5】:

        我建议使用填充矩形,如下所示:https://www.pygame.org/docs/ref/gfxdraw.html#pygame.gfxdraw.rectangle

        您的代码如下所示:

        thickLine = pygame.gfxdraw.rectangle(surface, rect, color)
        

        然后记得填充表面。大致如下:

        thickLine.fill()
        

        【讨论】:

        • 非常感谢!我和box 都试过了,但我找不到如何定义起点和终点,因为我只有行的开头和结尾的坐标。
        • 问题在于我的线在旋转,所以一个简单的 +-1 是不够的,因为你最终会得到一个不跟随另一端旋转的恒定边。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-11-03
        • 2010-12-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多