我希望绘制一系列具有颜色渐变的线条,以便颜色从亮白色渐变为黑色或完全透明:
理想情况下,
pygame.draw.lines()
会接受一个列表作为color
参数,每个顶点使用一种颜色,或者端点使用两种颜色。 API中没有这样的东西,如何实现自己的渐变线绘制功能呢?
该线可以是一个像素宽,并且不必进行抗锯齿,但我希望有一种比使用
pygame.gfxdraw.pixel()
实现 Bresenham 算法更好的方法。
这当然是可能的,但 PyGame 没有提供现成的解决方案。
在下面的示例中,我展示了一个
GradientLine
类。 这是通过计算从原始颜色到最终颜色的颜色步长来实现的。 对线条的每个部分使用阶梯颜色。
它假设颜色数量少于像素数量,并进行相应优化。这是一个合理的假设,因为大多数消费者都会表现出:
因此,该类首先生成组成屏幕上线条的像素列表。 当算法遍历像素时,它会寻找“显着”(即:可见)颜色变化。 如果发现颜色变化,则存储线段并开始新线段。 但如果颜色变化不显着,则该像素将被忽略,以创建更长的单色线。
import pygame
# Window size
WINDOW_WIDTH = 800
WINDOW_HEIGHT = 800
BLACK = ( 0, 0, 0)
WHITE = (255, 255, 255)
RED = (220, 0, 0)
GREEN = ( 0, 220, 0)
BLUE = ( 0, 0, 220)
ORANGE = (255, 165, 0)
def clamp( point ):
""" Ensure the point is inside the window """
x, y = point
x = max( 0, x )
x = min( WINDOW_WIDTH-1, x )
y = max( 0, y )
y = min( WINDOW_HEIGHT-1, y )
return x, y
def getLinePath( start_pos, end_pos ):
""" Using Bresenham's Line algorithm, get a list of all the points
along a straight-line path from the start to the end (inclusive).
Note that this includes diagonal movement """
# clamp range to window
x0, y0 = clamp( start_pos )
x1, y1 = clamp( end_pos )
points = []
# ref: https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm#Algorithm_for_integer_arithmetic
dx = abs(x1 - x0)
if x0 < x1:
sx = 1
else:
sx = -1
dy = -abs(y1 - y0)
if y0 < y1:
sy = 1
else:
sy = -1
error = dx + dy
while True:
points.append( ( x0,y0 ) )
if x0 == x1 and y0 == y1:
break
e2 = 2 * error
if e2 >= dy:
if x0 == x1:
break
error = error + dy
x0 = x0 + sx
if e2 <= dx:
if y0 == y1:
break
error = error + dx
y0 = y0 + sy
return points
class GradientLine:
def __init__( self, start_point, end_point, start_colour, end_colour, thickness:int=1 ):
self.lines = []
self.width = thickness
line_pixels = getLinePath( start_point, end_point )
pixel_count = float( len( line_pixels ) )
red_step = float( end_colour[0] - start_colour[0] ) / pixel_count
green_step = float( end_colour[1] - start_colour[1] ) / pixel_count
blue_step = float( end_colour[2] - start_colour[2] ) / pixel_count
red = float( start_colour[0] )
green = float( start_colour[1] )
blue = float( start_colour[2] )
# It is too inefficient to continually draw a huge whack of pixels
# So Identify a bunch of line-segments that are the same colour
start_point = None
for pixel in line_pixels:
if ( start_point == None ):
start_point = pixel
start_red, start_green, start_blue = ( red, green, blue )
else:
# calculate the next colour
# if that colour significantly different, output a line segment
if ( abs( start_red - red ) > 1.0 or \
abs( start_green - green ) > 1.0 or \
abs( start_blue - blue ) > 1.0 ):
colour = ( int( start_red ), int( start_green ), int( start_blue ) )
self.lines.append( ( start_point, pixel, colour ) )
start_point = None
# crawl the gradient
red += red_step
green += green_step
blue += blue_step
def draw( self, surface:pygame.Surface ):
""" Draw a line from start to end, changing colour along the way """
for segment in self.lines:
point_a, point_b, colour = segment
pygame.draw.line( surface, colour, point_a, point_b, self.width )
###
### MAIN
###
pygame.init()
window = pygame.display.set_mode( ( WINDOW_WIDTH, WINDOW_HEIGHT ), pygame.HWSURFACE )
pygame.display.set_caption("Gradient Line")
# Define some Gradient Lines
grad_lines = []
grad_lines.append( GradientLine( ( 0,0 ), ( 799,799 ), BLUE, ORANGE, 5 ) )
grad_lines.append( GradientLine( ( 0,399 ), ( 799,399 ), RED, GREEN, 5 ) )
grad_lines.append( GradientLine( ( 50,150 ), ( 50, 300 ), WHITE, BLACK, 5 ) )
# Main loop
clock = pygame.time.Clock()
running = True
while running:
time_now = pygame.time.get_ticks()
# Handle user-input
for event in pygame.event.get():
if ( event.type == pygame.QUIT ):
running = False
# Paint the window
window.fill( BLACK )
# Draw the walking path to the most recent mouse-click (if any)
for line in grad_lines:
line.draw( window )
pygame.display.flip()
# Clamp FPS
clock.tick(60)
pygame.quit()