Skip to content

Commit 91d6db6

Browse files
committed
WIP Pillow util module
1 parent 1070046 commit 91d6db6

File tree

2 files changed

+197
-0
lines changed

2 files changed

+197
-0
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
"""Showcase what the output of pymunk.pyglet_util draw methods will look like.
2+
3+
See pygame_util_demo.py for a comparison to pygame.
4+
"""
5+
6+
__docformat__ = "reStructuredText"
7+
8+
from PIL import Image, ImageDraw, ImageColor, ImageShow
9+
10+
import pymunk
11+
import pymunk.pillow_util
12+
13+
from .shapes_for_draw_demos import fill_space
14+
15+
img = Image.new("RGB", (1000, 700), ImageColor.getrgb("white"))
16+
draw = ImageDraw.Draw(img)
17+
18+
space = pymunk.Space()
19+
draw_options = pymunk.pillow_util.DrawOptions(img)
20+
captions = fill_space(space)
21+
22+
23+
24+
labels = []
25+
26+
draw.text((5,5),"Demo example of shapes drawn by pillow_util.draw()", fill=(100,100,100))
27+
28+
for caption in captions:
29+
x, y = caption[0]
30+
y = y - 10
31+
draw.text((x,y),caption[1], fill=(50,50,50))
32+
33+
space.debug_draw(draw_options)
34+
35+
img.save("pillow_util_demo.png")
36+
ImageShow.show(img)

pymunk/pillow_util.py

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
# ----------------------------------------------------------------------------
2+
# pymunk
3+
# Copyright (c) 2007-2025 Victor Blomqvist
4+
#
5+
# Permission is hereby granted, free of charge, to any person obtaining a copy
6+
# of this software and associated documentation files (the "Software"), to deal
7+
# in the Software without restriction, including without limitation the rights
8+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
# copies of the Software, and to permit persons to whom the Software is
10+
# furnished to do so, subject to the following conditions:
11+
#
12+
# The above copyright notice and this permission notice shall be included in
13+
# all copies or substantial portions of the Software.
14+
#
15+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
# SOFTWARE.
22+
# ----------------------------------------------------------------------------
23+
24+
"""This submodule contains helper functions to help with quick prototyping
25+
using pymunk together with Pillow/PIL.
26+
27+
Intended to help with debugging and prototyping, not for actual production use
28+
in a full application. The methods contained in this module is opinionated
29+
about your coordinate system and not in any way optimized.
30+
"""
31+
32+
__docformat__ = "reStructuredText"
33+
34+
__all__ = [
35+
"DrawOptions",
36+
]
37+
38+
from typing import Sequence
39+
40+
from PIL import ImageDraw, Image
41+
42+
import pymunk
43+
from pymunk.space_debug_draw_options import SpaceDebugColor
44+
from pymunk.vec2d import Vec2d
45+
46+
47+
class DrawOptions(pymunk.SpaceDebugDrawOptions):
48+
def __init__(self, im: Image.Image) -> None:
49+
"""Draw a pymunk.Space on a pygame.Surface object.
50+
51+
This class should work both with Pygame and Pygame-CE.
52+
53+
Typical usage::
54+
55+
>>> import pymunk
56+
>>> surface = pygame.Surface((10,10))
57+
>>> space = pymunk.Space()
58+
>>> options = pymunk.pygame_util.DrawOptions(surface)
59+
>>> space.debug_draw(options)
60+
61+
You can control the color of a shape by setting shape.color to the color
62+
you want it drawn in::
63+
64+
>>> c = pymunk.Circle(None, 10)
65+
>>> c.color = pygame.Color("pink")
66+
67+
See pygame_util.demo.py for a full example
68+
69+
70+
>>> space = pymunk.Space()
71+
>>> space.gravity = (0, -1000)
72+
>>> body = pymunk.Body()
73+
>>> body.position = (0, 0) # will be positioned in the top left corner
74+
>>> space.debug_draw(options)
75+
76+
>>> body = pymunk.Body()
77+
>>> body.position = (0, 0)
78+
>>> # Body will be position in bottom left corner
79+
80+
:Parameters:
81+
im : Image.Image
82+
Image that the objects will be drawn on
83+
"""
84+
self.image = im
85+
self.draw = ImageDraw.Draw(im)
86+
super(DrawOptions, self).__init__()
87+
88+
def draw_circle(
89+
self,
90+
pos: Vec2d,
91+
angle: float,
92+
radius: float,
93+
outline_color: SpaceDebugColor,
94+
fill_color: SpaceDebugColor,
95+
) -> None:
96+
self.draw.circle(pos, radius, fill_color.as_int(), outline_color.as_int(), 1)
97+
98+
circle_edge = pos + Vec2d.from_polar(radius, angle)
99+
p2 = circle_edge
100+
line_r = 2 if radius > 20 else 1
101+
self.draw.line([pos, p2], outline_color.as_int(), line_r)
102+
103+
104+
def draw_segment(self, a: Vec2d, b: Vec2d, color: SpaceDebugColor) -> None:
105+
self.draw.line([a,b], color.as_int())
106+
107+
def draw_fat_segment(
108+
self,
109+
a: tuple[float, float],
110+
b: tuple[float, float],
111+
radius: float,
112+
outline_color: SpaceDebugColor,
113+
fill_color: SpaceDebugColor,
114+
) -> None:
115+
p1 = a
116+
p2 = b
117+
118+
r = round(max(1, radius * 2))
119+
self.draw.line([a,b], fill_color.as_int(), r)
120+
121+
if r > 2:
122+
orthog = [abs(p2[1] - p1[1]), abs(p2[0] - p1[0])]
123+
if orthog[0] == 0 and orthog[1] == 0:
124+
return
125+
scale = radius / (orthog[0] * orthog[0] + orthog[1] * orthog[1]) ** 0.5
126+
orthog[0] = round(orthog[0] * scale)
127+
orthog[1] = round(orthog[1] * scale)
128+
points = [
129+
(p1[0] - orthog[0], p1[1] - orthog[1]),
130+
(p1[0] + orthog[0], p1[1] + orthog[1]),
131+
(p2[0] + orthog[0], p2[1] + orthog[1]),
132+
(p2[0] - orthog[0], p2[1] - orthog[1]),
133+
]
134+
self.draw.polygon(points, fill_color.as_int())
135+
self.draw.circle((round(p1[0]), round(p1[1])), radius-1, fill_color.as_int())
136+
self.draw.circle((round(p2[0]), round(p2[1])), radius-1, fill_color.as_int())
137+
138+
def draw_polygon(
139+
self,
140+
verts: Sequence[tuple[float, float]],
141+
radius: float,
142+
outline_color: SpaceDebugColor,
143+
fill_color: SpaceDebugColor,
144+
) -> None:
145+
ps = [v for v in verts]
146+
ps += [ps[0]]
147+
148+
self.draw.polygon(ps, fill_color.as_int())
149+
150+
if radius > 0:
151+
for i in range(len(verts)):
152+
a = verts[i]
153+
b = verts[(i + 1) % len(verts)]
154+
self.draw_fat_segment(a, b, radius, outline_color, outline_color)
155+
156+
def draw_dot(
157+
self, size: float, pos: tuple[float, float], color: SpaceDebugColor
158+
) -> None:
159+
self.draw.circle(pos, size, color.as_int(), width=0)
160+
161+

0 commit comments

Comments
 (0)