Skip to content

Commit a4cee12

Browse files
committed
blitting now works
1 parent eb98202 commit a4cee12

File tree

3 files changed

+101
-35
lines changed

3 files changed

+101
-35
lines changed

pygame_matplotlib/backend_pygame.py

Lines changed: 97 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,19 @@
88
matplotlib.use("'module://pygame_matplotlib.backend_pygame'")
99
"""
1010
from __future__ import annotations
11+
from logging import setLoggerClass
12+
from typing import List
1113
import matplotlib
14+
from matplotlib.artist import Artist
1215
from matplotlib.pyplot import figure
16+
from matplotlib.axes import Axes
1317
import numpy as np
1418
from matplotlib.transforms import Affine2D
1519
import pygame
1620
from pygame import surface
21+
from pygame import time
22+
from pygame.draw import arc
23+
from pygame.event import wait
1724
import pygame.image
1825
from pygame import gfxdraw
1926
from matplotlib._pylab_helpers import Gcf
@@ -36,6 +43,7 @@ class FigureSurface(pygame.Surface, Figure):
3643
"""
3744

3845
canvas: FigureCanvasPygame
46+
animated_artists: List[Artist]
3947

4048
def __init__(self, *args, **kwargs):
4149
"""Create a FigureSurface object.
@@ -44,7 +52,7 @@ def __init__(self, *args, **kwargs):
4452
"""
4553
Figure.__init__(self, *args, **kwargs)
4654
pygame.Surface.__init__(self, self.bbox.size)
47-
self.fill("white")
55+
# self.fill("white")
4856

4957
def set_bounding_rect(self, rect: pygame.Rect):
5058
"""Set a bounding rectangle around the figure."""
@@ -56,9 +64,52 @@ def set_bounding_rect(self, rect: pygame.Rect):
5664
# Redraw the figure
5765
self.canvas.draw()
5866

59-
#def draw(self, *args, **kwargs):
60-
# print(*args, **kwargs)
61-
# return super().draw(self, *args, **kwargs)
67+
def draw(self, renderer):
68+
return super().draw(renderer)
69+
70+
def get_interactive_artists(self, renderer):
71+
"""Get the interactive artists.
72+
73+
Custom method used for the blitting.
74+
Modification of _get_draw_artists
75+
Also runs apply_aspect.
76+
"""
77+
artists = self.get_children()
78+
79+
for sfig in self.subfigs:
80+
artists.remove(sfig)
81+
childa = sfig.get_children()
82+
for child in childa:
83+
if child in artists:
84+
artists.remove(child)
85+
86+
artists.remove(self.patch)
87+
artists = sorted(
88+
(artist for artist in artists if artist.get_animated()),
89+
key=lambda artist: artist.get_zorder(),
90+
)
91+
92+
for ax in self._localaxes.as_list():
93+
locator = ax.get_axes_locator()
94+
95+
if locator:
96+
pos = locator(ax, renderer)
97+
ax.apply_aspect(pos)
98+
else:
99+
ax.apply_aspect()
100+
101+
for child in ax.get_children():
102+
if child.get_animated():
103+
artists.append(child)
104+
if hasattr(child, "apply_aspect"):
105+
locator = child.get_axes_locator()
106+
if locator:
107+
pos = locator(child, renderer)
108+
child.apply_aspect(pos)
109+
else:
110+
child.apply_aspect()
111+
112+
return artists
62113

63114

64115
class RendererPygame(RendererBase):
@@ -78,7 +129,7 @@ def rect_from_bbox(self, bbox: Bbox) -> pygame.Rect:
78129
raise NotImplementedError()
79130

80131
def draw_path(self, gc, path, transform, rgbFace=None):
81-
132+
"""Draw a path using pygame functions."""
82133
if rgbFace is not None:
83134
color = tuple(
84135
[int(val * 255) for i, val in enumerate(rgbFace) if i < 3]
@@ -103,11 +154,7 @@ def draw_path(self, gc, path, transform, rgbFace=None):
103154

104155
previous_point = (0, 0)
105156
poly_points = []
106-
# print(path)
107157
for point, code in path.iter_segments(transform):
108-
# previous_point = point
109-
# print(point, code)
110-
111158
if code == Path.LINETO:
112159
draw_func(
113160
self.surface, color, previous_point, point, linewidth
@@ -253,13 +300,7 @@ def copy_from_bbox(self, bbox):
253300
copy.bbox = bbox
254301
return copy
255302

256-
def restore_region(self, region: pygame.Surface, bbox: Bbox, xy):
257-
rect = (
258-
pygame.Rect(0, 0, *self.surface.get_size())
259-
if bbox is None
260-
else self.rect_from_bbox(bbox)
261-
)
262-
region.blit(self.surface, rect)
303+
263304

264305

265306
class GraphicsContextPygame(GraphicsContextBase):
@@ -347,6 +388,7 @@ class methods button_press_event, button_release_event,
347388
figure : `matplotlib.figure.Figure`
348389
A high-level Figure instance
349390
"""
391+
blitting: bool = False
350392

351393
# File types allowed for saving
352394
filetypes = {
@@ -358,6 +400,7 @@ class methods button_press_event, button_release_event,
358400
}
359401
figure: FigureSurface
360402
renderer: RendererPygame
403+
main_display: pygame.Surface
361404

362405
def __init__(self, figure=None):
363406
super().__init__(figure)
@@ -372,8 +415,14 @@ def copy_from_bbox(self, bbox):
372415
return renderer.copy_from_bbox(bbox)
373416

374417
def restore_region(self, region, bbox=None, xy=None):
375-
renderer = self.get_renderer()
376-
return renderer.restore_region(region, bbox, xy)
418+
rect = (
419+
pygame.Rect(*(
420+
0, 0 if xy is None else xy
421+
), *self.get_renderer().surface.get_size())
422+
if bbox is None
423+
else self.rect_from_bbox(bbox)
424+
)
425+
self.figure.blit(region, rect)
377426

378427
def draw(self):
379428
"""
@@ -386,13 +435,21 @@ def draw(self):
386435
"""
387436
self.renderer = self.get_renderer(cleared=True)
388437
self.renderer.surface = self.figure
389-
self.figure.draw(self.renderer)
438+
if self.blitting:
439+
# Draw only interactive artists
440+
artists = self.figure.get_interactive_artists(self.renderer)
441+
for a in artists:
442+
a.draw(self.renderer)
443+
else:
444+
# Full redraw of the figure
445+
self.figure.draw(self.renderer)
446+
390447

391448
def blit(self, bbox=None):
392-
# self._png_is_old = True
393449
self.renderer = self.get_renderer(cleared=False)
394450
self.renderer.surface = self.figure
395-
self.figure.draw(self.renderer)
451+
self.blitting = True
452+
396453

397454

398455
def get_renderer(self, cleared=False) -> RendererPygame:
@@ -419,11 +476,13 @@ def get_default_filetype(self):
419476
def flush_events(self):
420477
self.main_display.blit(self.figure, (0, 0))
421478
pygame.display.update()
479+
self.pygame_quit_event_check()
480+
481+
def pygame_quit_event_check(self):
422482
events = pygame.event.get()
423483
for event in events:
424484
if event.type == pygame.QUIT:
425485
pygame.quit()
426-
show_fig = False
427486

428487
def start_event_loop(self, interval: float):
429488
FPS = 60
@@ -443,20 +502,30 @@ class FigureManagerPygame(FigureManagerBase):
443502
444503
For non-interactive backends, the base class is sufficient.
445504
"""
446-
447-
def show(self, block):
448-
# do something to display the GUI
449-
pygame.init()
505+
canvas: FigureCanvasPygame
506+
def get_main_display(self):
507+
if hasattr(self.canvas, 'main_display'):
508+
if (self.canvas.figure.get_size() == self.canvas.main_display.get_size()):
509+
# If the main display exist and its size has not changed
510+
return self.canvas.main_display
450511
main_display = pygame.display.set_mode(
451512
self.canvas.figure.get_size(), # Size matches figure size
452513
)
453-
514+
main_display.fill('white')
454515
self.canvas.main_display = main_display
516+
return main_display
517+
518+
def show(self, block):
519+
# do something to display the GUI
520+
pygame.init()
521+
main_display = self.get_main_display()
455522

456523
FPS = 60
457524
FramePerSec = pygame.time.Clock()
458525

459-
self.canvas.figure.canvas.draw()
526+
if not self.canvas.blitting:
527+
# Draw if we are not blitting
528+
self.canvas.draw()
460529
main_display.blit(self.canvas.figure, (0, 0))
461530
pygame.display.update()
462531

test_blitting.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,10 @@
4141
# update the artist, neither the canvas state nor the screen have changed
4242
ln.set_ydata(np.sin(x + (j / 100) * np.pi))
4343
# re-render the artist, updating the canvas state, but not the screen
44-
print(ax.get_renderer_cache())
45-
print('Draw starts')
4644
ax.draw_artist(ln)
47-
print('Draw ends')
4845
# copy the image to the GUI state, but screen might not be changed yet
4946
fig.canvas.blit(fig.bbox)
50-
print(fig._cachedRenderer)
5147
# flush any pending GUI events, re-painting the screen if needed
5248
fig.canvas.flush_events()
5349
# you can put a pause in if you want to slow things down
54-
plt.pause(1)
50+
plt.pause(.01)

test_blitting_class.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,11 +94,12 @@ def update(self):
9494
bm = BlitManager(fig.canvas, [ln, fr_number])
9595
# make sure our window is on the screen and drawn
9696
plt.show(block=False)
97-
plt.pause(.1)
97+
plt.pause(1)
9898

9999
for j in range(1000):
100100
# update the artists
101101
ln.set_ydata(np.sin(x + (j / 100) * np.pi))
102102
fr_number.set_text("frame: {j}".format(j=j))
103103
# tell the blitting manager to do its thing
104-
bm.update()
104+
bm.update()
105+
# plt.pause(1)

0 commit comments

Comments
 (0)