treasurehunting2/PySDL2-0.9.5/sdl2/ext/draw.py
2017-05-13 11:00:53 +02:00

160 lines
5.6 KiB
Python

"""Drawing routines for software surfaces."""
import ctypes
from .compat import isiterable, UnsupportedError
from .array import to_ctypes
from .color import convert_to_color
from .. import surface, pixels, rect
from .algorithms import clipline
from .sprite import SoftwareSprite
__all__ = ["prepare_color", "fill", "line"]
def _get_target_surface(target):
"""Gets the SDL_surface from the passed target."""
if isinstance(target, surface.SDL_Surface):
rtarget = target
elif isinstance(target, SoftwareSprite):
rtarget = target.surface
else:
raise TypeError("unsupported target type")
return rtarget
def prepare_color(color, target):
"""Prepares the passed color for the passed target."""
color = convert_to_color(color)
pformat = None
# Software surfaces
if isinstance(target, pixels.SDL_PixelFormat):
pformat = target
elif isinstance(target, surface.SDL_Surface):
pformat = target.format.contents
elif isinstance(target, SoftwareSprite):
pformat = target.surface.format.contents
if pformat is None:
raise TypeError("unsupported target type")
if pformat.Amask != 0:
# Target has an alpha mask
return pixels.SDL_MapRGBA(pformat, color.r, color.g, color.b, color.a)
return pixels.SDL_MapRGB(pformat, color.r, color.g, color.b)
def fill(target, color, area=None):
"""Fills a certain rectangular area on the passed target with a color.
If no area is provided, the entire target will be filled with
the passed color. If an iterable item is provided as area (such as a list
or tuple), it will be first checked, if the item denotes a single
rectangular area (4 integer values) before assuming it to be a sequence
of rectangular areas.
"""
color = prepare_color(color, target)
rtarget = _get_target_surface(target)
varea = None
if area is not None and isiterable(area):
# can be either a single rect or a list of rects)
if len(area) == 4:
# is it a rect?
try:
varea = rect.SDL_Rect(int(area[0]), int(area[1]),
int(area[2]), int(area[3]))
except:
# No, not a rect, assume a seq of rects.
pass
if not varea: # len(area) == 4 AND varea set.
varea = []
for r in area:
varea.append(rect.SDL_Rect(r[0], r[1], r[2], r[3]))
if varea is None or isinstance(varea, rect.SDL_Rect):
surface.SDL_FillRect(rtarget, varea, color)
else:
varea, count = to_ctypes(varea, rect.SDL_Rect)
varea = ctypes.cast(varea, ctypes.POINTER(rect.SDL_Rect))
surface.SDL_FillRects(rtarget, varea, count, color)
def line(target, color, dline, width=1):
"""Draws one or multiple lines on the passed target.
dline can be a sequence of four integers for a single line in the
form (x1, y1, x2, y2) or a sequence of a multiple of 4 for drawing
multiple lines at once, e.g. (x1, y1, x2, y2, x3, y3, x4, y4, ...).
"""
if width < 1:
raise ValueError("width must be greater than 0")
color = prepare_color(color, target)
rtarget = _get_target_surface(target)
# line: (x1, y1, x2, y2) OR (x1, y1, x2, y2, ...)
if (len(dline) % 4) != 0:
raise ValueError("line does not contain a valid set of points")
pcount = len(dline)
SDLRect = rect.SDL_Rect
fillrect = surface.SDL_FillRect
pitch = rtarget.pitch
bpp = rtarget.format.contents.BytesPerPixel
frac = pitch / bpp
clip_rect = rtarget.clip_rect
left, right = clip_rect.x, clip_rect.x + clip_rect.w - 1
top, bottom = clip_rect.y, clip_rect.y + clip_rect.h - 1
if bpp == 3:
raise UnsupportedError(line, "24bpp are currently not supported")
if bpp == 2:
pxbuf = ctypes.cast(rtarget.pixels, ctypes.POINTER(ctypes.c_uint16))
elif bpp == 4:
pxbuf = ctypes.cast(rtarget.pixels, ctypes.POINTER(ctypes.c_uint32))
else:
pxbuf = rtarget.pixels # byte-wise access.
for idx in range(0, pcount, 4):
x1, y1, x2, y2 = dline[idx:idx + 4]
if x1 == x2:
# Vertical line
if y1 < y2:
varea = SDLRect(x1 - width // 2, y1, width, y2 - y1)
else:
varea = SDLRect(x1 - width // 2, y2, width, y1 - y2)
fillrect(rtarget, varea, color)
continue
if y1 == y2:
# Horizontal line
if x1 < x2:
varea = SDLRect(x1, y1 - width // 2, x2 - x1, width)
else:
varea = SDLRect(x2, y1 - width // 2, x1 - x2, width)
fillrect(rtarget, varea, color)
continue
if width != 1:
raise UnsupportedError(line, "width > 1 is not supported")
if width == 1:
# Bresenham
x1, y1, x2, y2 = clipline(left, top, right, bottom, x1, y1, x2, y2)
x1, y1, x2, y2 = int(x1), int(y1), int(x2), int(y2)
if x1 is None:
# not to be drawn
continue
dx = abs(x2 - x1)
dy = -abs(y2 - y1)
err = dx + dy
sx, sy = 1, 1
if x1 > x2:
sx = -sx
if y1 > y2:
sy = -sy
while True:
pxbuf[int(y1 * frac + x1)] = color
if x1 == x2 and y1 == y2:
break
e2 = err * 2
if e2 > dy:
err += dy
x1 += sx
if e2 < dx:
err += dx
y1 += sy