Programmatically generate SVG (vector) images, animations, and interactive Jupyter widgets

Add FrameAnimationContext for Spritesheets (#92)

Changed files
+85 -8
drawsvg
examples
+10 -3
README.md
···
with draw.frame_animate_jupyter(draw_frame, delay=0.05) as anim:
# Or:
-
#with draw.animate_video('example6.gif', draw_frame, duration=0.05
-
# ) as anim:
# Add each frame to the animation
for i in range(20):
anim.draw_frame(i/10)
···
anim.draw_frame(i/10)
```
-
![Example output image](https://raw.githubusercontent.com/cduck/drawsvg/master/examples/example6.gif)
### Asynchronous Frame-based Animation in Jupyter
```python
···
with draw.frame_animate_jupyter(draw_frame, delay=0.05) as anim:
# Or:
+
#with draw.frame_animate_video('example6.gif', draw_frame, duration=0.05) as anim:
+
# Or:
+
#with draw.frame_animate_spritesheet('example6.png', draw_frame, row_length=10) as anim:
# Add each frame to the animation
for i in range(20):
anim.draw_frame(i/10)
···
anim.draw_frame(i/10)
```
+
GIF:
+
+
![Example output gif](https://raw.githubusercontent.com/cduck/drawsvg/master/examples/example6.gif)
+
+
Spritesheet (usable in most 2D game engines):
+
+
![Example output spritesheet](https://raw.githubusercontent.com/cduck/drawsvg/master/examples/example6.png)
### Asynchronous Frame-based Animation in Jupyter
```python
+1
drawsvg/__init__.py
···
FrameAnimation,
frame_animate_video,
frame_animate_jupyter,
)
from .native_animation import (
SyncedAnimationConfig,
···
FrameAnimation,
frame_animate_video,
frame_animate_jupyter,
+
frame_animate_spritesheet,
)
from .native_animation import (
SyncedAnimationConfig,
+31 -5
drawsvg/frame_animation.py
···
def save_video(self, file, **kwargs):
video.save_video(self.frames, file, **kwargs)
class FrameAnimationContext:
def __init__(self, draw_func=None, out_file=None,
-
jupyter=False, pause=False, clear=True, delay=0, disable=False,
-
video_args=None, _patch_delay=0.05):
self.jupyter = jupyter
self.disable = disable
if self.jupyter and not self.disable:
from IPython import display
···
if exc_value is None:
# No error
if self.out_file is not None and not self.disable:
-
self.anim.save_video(self.out_file, **self.video_args)
def frame_animate_video(out_file, draw_func=None, jupyter=False, **video_args):
···
Example:
```
-
with animate_video('video.mp4') as anim:
while True:
...
anim.draw_frame(...)
···
return FrameAnimationContext(draw_func=draw_func, out_file=out_file,
jupyter=jupyter, video_args=video_args)
def frame_animate_jupyter(draw_func=None, pause=False, clear=True, delay=0.1,
**kwargs):
···
Example:
```
-
with animate_jupyter(delay=0.5) as anim:
while True:
...
anim.draw_frame(...)
···
def save_video(self, file, **kwargs):
video.save_video(self.frames, file, **kwargs)
+
def save_spritesheet(self, file, **kwargs):
+
video.save_spritesheet(self.frames, file, **kwargs)
+
class FrameAnimationContext:
def __init__(self, draw_func=None, out_file=None,
+
jupyter=False, spritesheet=False, pause=False,
+
clear=True, delay=0, disable=False, video_args=None,
+
_patch_delay=0.05):
self.jupyter = jupyter
+
self.spritesheet = spritesheet
self.disable = disable
if self.jupyter and not self.disable:
from IPython import display
···
if exc_value is None:
# No error
if self.out_file is not None and not self.disable:
+
if self.spritesheet:
+
self.anim.save_spritesheet(self.out_file, **self.video_args)
+
else:
+
self.anim.save_video(self.out_file, **self.video_args)
def frame_animate_video(out_file, draw_func=None, jupyter=False, **video_args):
···
Example:
```
+
with frame_animate_video('video.mp4') as anim:
while True:
...
anim.draw_frame(...)
···
return FrameAnimationContext(draw_func=draw_func, out_file=out_file,
jupyter=jupyter, video_args=video_args)
+
def frame_animate_spritesheet(out_file, draw_func=None, jupyter=False,
+
**video_args):
+
'''
+
Returns a context manager that stores frames and saves a spritesheet when
+
the context exits.
+
+
Example:
+
```
+
with frame_animate_spritesheet('sheet.png', row_length=10) as anim:
+
while True:
+
...
+
anim.draw_frame(...)
+
```
+
'''
+
return FrameAnimationContext(draw_func=draw_func, out_file=out_file,
+
jupyter=jupyter, spritesheet=True,
+
video_args=video_args)
+
def frame_animate_jupyter(draw_func=None, pause=False, clear=True, delay=0.1,
**kwargs):
···
Example:
```
+
with frame_animate_jupyter(delay=0.5) as anim:
while True:
...
anim.draw_frame(...)
+43
drawsvg/video.py
···
print()
print(f'Converting to video')
imageio.mimsave(file, frames, **kwargs)
···
print()
print(f'Converting to video')
imageio.mimsave(file, frames, **kwargs)
+
+
def save_spritesheet(frames, file, row_length=None, verbose=False, **kwargs):
+
'''
+
Save a series of drawings as a bitmap spritesheet
+
+
Arguments:
+
frames: A list of `Drawing`s or a list of `numpy.array`s.
+
file: File name or file like object to write the spritesheet to. The
+
extension determines the output format.
+
row_length: The length (in frames) of one row in the spritesheet.
+
If not provided, all frames go on one row.
+
align_bottom: If frames are different sizes, align the bottoms of each
+
frame in the video.
+
align_right: If frames are different sizes, align the right edge of each
+
frame in the video.
+
bg: If frames are different sizes, fill the background with this color.
+
(default is white: (255, 255, 255, 255))
+
**kwargs: Other arguments to imageio.imsave().
+
+
'''
+
np, imageio = delay_import_np_imageio()
+
if not isinstance(frames[0], np.ndarray):
+
frames = render_svg_frames(frames, verbose=verbose, **kwargs)
+
kwargs.pop('align_bottom', None)
+
kwargs.pop('align_right', None)
+
kwargs.pop('bg', None)
+
+
cols = row_length if row_length is not None else len(frames)
+
rows = (len(frames) - 1) // cols + 1
+
+
if rows * cols > len(frames): # Unfilled final row
+
empty_frame = np.zeros(frames[0].shape, dtype=frames[0].dtype)
+
frames.extend([empty_frame] * (rows * cols - len(frames)))
+
+
block_arrangement = []
+
for row in range(rows):
+
next_row_end = (row+1)*cols
+
block_arrangement.append([
+
[frame] for frame in frames[row*cols:next_row_end]
+
])
+
+
spritesheet = np.block(block_arrangement)
+
imageio.imsave(file, spritesheet, **kwargs)
examples/example6.png

This is a binary file and will not be displayed.