pgzero
pgzero copied to clipboard
Is `numpy` a necessary dependency?
numpy
is a required dependency but it only appears to be used in one place in the code:
https://github.com/lordmauve/pgzero/blob/38cb6021496bd2f9dae0b37ef3ee6304dee71e2f/pgzero/ptext.py#L348
numpy
is a rather large package. Is the dependency really necessary? The same could be implemented in pure Python is there is no significant loss of performance as a result, or a minimal C-extension would just do the job (and could eventually be pushed upstream to pygame
if this is something general for writing games in Python).
It's also used in screen.py
.
ptext is vendored from elsewhere, and needs this for the outline feature (and others?) so this isn't easy to fix. We could submit a patch, or we could actually go ahead and fork it.
It is a huge dependency and I was reluctant to add it in the first place, but the situation we had where it was optional was not healthy because some stuff only worked when it was installed. Requiring it was a simpler solution.
Note that the amount of numpy usage has gone down a lot now we use pyfxr to generate tones in tone.py
.
Looks like the current version of ptext doesn't need numpy. But it has changed significantly, so merging that would need some care.
Thanks. The dependency on numpy in screen.py
is for gradients, and it uses an optional pygame
capability that wraps memory regions in numpy
arrays (getting rid of numpy
in pygame zero would not suppress the need for numpy
in the end). Interestingly the current ptext
also has an implementation of vertical gradients in rectangles:
https://github.com/cosmologicon/pygame-text/blob/master/ptext.py#L487
Overall it seems that gradients are generally useful, and arguably should not require numpy
and be part of pygame
(and in a C-extension module).
It seems that there is no need to go for a C-extension and get decent performances without numpy
. Running the
test below shows a candidate alternative implementation without numpy that is 4.7 times faster:
import timeit
import pygame
# colors.
gradient_start = (0, 50, 100)
gradient_stop = (255, 250, 200)
# modest screen size.
WIDTH = 800
HEIGHT = 600
def blit_gradient(start, stop, dest_surface):
""" New proposed implementation."
surface_compact = pygame.Surface((2, 2))
pixelarray = pygame.PixelArray(surface_compact)
pixelarray[0][0] = start
pixelarray[0][1] = stop
pixelarray[1][0] = start
pixelarray[1][1] = stop
pygame.transform.smoothscale(surface_compact,
pygame.PixelArray(dest_surface).shape,
dest_surface=dest_surface)
def old_blit_gradient(start, stop, dest_surface):
"""The current implementation requiring numpy."""
import numpy as np
pixs = pygame.surfarray.pixels3d(dest_surface)
gradient = np.dstack(
[np.linspace(a, b, HEIGHT) for a, b in zip(start, stop)][:3]
)
pixs[...] = gradient
# Speed benchmark.
# Proposed new implementation.
setup_test = """
import pygame
dest_surface = pygame.Surface((WIDTH, HEIGHT))
"""
test = 'blit_gradient(gradient_start, gradient_stop, dest_surface)'
t_new = min(
timeit.Timer(
test, setup_test,
globals=locals()).repeat(
repeat=5,
number=1000)
)
# Current implementation requiring numpy.
setup_test_old = """
import pygame
import numpy as np
dest_surface = pygame.Surface((WIDTH, HEIGHT))"""
test_old = 'old_blit_gradient(gradient_start, gradient_stop, dest_surface)'
t_current = min(
timeit.Timer(
test_old, setup_test_old,
globals=locals()).repeat(
repeat=5,
number=1000)
)
print(f'Speedup: {t_current/t_new:.2f}x')