Open ntoll opened 8 years ago
Perhaps these functions could live under microbit.display.fx..?
Or just be methods of an Image instance, eg:
>>> twinkle_happy = Image.HAPPY.sparkle()
I would suggest that each "effect" function should take a single image and return an iterators of images.
They could be methods on images, but iterators are not the simplest of concepts, so its best to wrap them in a utility functions.
Something like:
display.FX.sparkle(img)
which would call display.animate(_FX.sparkle(img))
I think that effects should rather be objects using the iterator protocol and generating the frames on the fly, than functions returning lists (that saves memory).
It would be great if they could accept both a single image (in which case they would use it for the base of each frame), or an iterable of images (in which case the effect would be added to each image from the iterable) -- that would let us compose animations.
The display.animate
function duplicates what display.print
already does for text, maybe those two could be merged into a single display.show
that takes either an image or an iterable of images or characters.
Not sure if that is a good idea, but there could also be a "magical" attribute that display.show
looks for, similar to how __repr__
works for print, which would allow making any user object "displayable".
On 11/11/15 09:46, Radomir Dopieralski wrote:
I think that effects should rather be objects using the iterator protocol and generating the frames on the fly, than functions returning lists (that saves memory). Indeed, I think we are in agreement.
It would be great if they could accept both a single image (in which case they would use it for the base of each frame), or an iterable of images (in which case the effect would be added to each image from the iterable) -- that would let us compose animations. Here we disagree. I think it would make the interface overly complex and potentially confusing. You can already compose iterables and functions:
display.show(chain(map(fx.sparkle, list_of_images)))
(A more readable form is left as an exercise :) )The |display.animate| function duplicates what |display.print| already does for text, maybe those two could be merged into a single |display.show| that takes either an image or an iterable of images or characters. Indeed. In fact that is what
scroll
andanimate
do already behind the scenes; they create an iterator of images which is then passed todisplay.print/show
Not sure if that is a good idea, but there could also be a "magical" attribute that |display.show| looks for, similar to how |repr| works for print, which would allow making any user object "displayable".
__image__
presumably?
Here we disagree. I think it would make the interface overly complex and potentially confusing. You can already compose iterables and functions:
display.show(chain(map(fx.sparkle, list_of_images)))
(A more readable form is left as an exercise :) )
That doesn't do the same thing. It would play an animation of each image sparkling, one after the other. However, if fx.sparkle would accept an iterator and then use each image as the base for each of its frames, you could have something like:
display.show(fx.shake(fx.sparkle(Image.HEART)))
which would display an animation of a heart both shaking and sparkling at the same time. You can't do that in any other way (except for some trivial cases, where you could maybe use the + operator to overlay one list of images on another, but most animations are more than just adding an image on top).
I don't see how effects could be composable in the way you describe.
What would fx.wipe_left(fx.wipe_right(some_image))
do, and how would it work?
Of course it would work. It would wipe the image from both sides at the same time, narrowing it, so to say.
For the effects that cannot take a different image for the base of each frame, they could fall back to just using the first frame. But most effects are easily composable by just applying all previous steps to the next base image.
Here is a simple implementation of wipe_left
implementing the Image -> List(Image)
interface.
WIPE_LEFT = Image("99999876543210000:"*5)
def wipe_left(img):
for i in range(12, -1, -1):
yield WIPE_LEFT.crop(i, 0, 5, 5) * img
How would you implement the List(Image) -> List(Image)
?
WIPE_LEFT = Image("99999876543210000:"*5)
def wipe_left(imgs):
for i in range(12, -1, -1):
yield WIPE_LEFT.crop(i, 0, 5, 5) * next(imgs)
yield from imgs
Of course you need to have a check if the thing is iterable.
Presumable you would just convert a single image to a sequence of the same image being repeated? Would all effects need to be infinite sequences?
being repeated
-> repeated for ever
Well, this is something to be discussed -- how to handle sequences of different lengths. As you see, here I just apply the effect to the first 12 frames, and then return all the rest of frames unchanged. It may make sense to do something else, depending on the effect. For instance, for a fade-out effect, it may make sense to return empty frames.
I don't think returning infinite sequences is wise, since you still need to display it -- and the display function needs to know when to stop.
Note also, that if the imgs
iterator at any moment ends, raising StopIteration
, that will also end the wipe_left
iterator, so the resulting sequence is as most as long as the original sequence. This may not always be true -- you may have effects such as "slow motion" or "fast forward", that actually add or remove frames. For other effects, it would probably make sense to specify with an additional parameter how many frames the effect should take, and maybe even how many frames should be skipped from the beginning?
Pass in a single image and get back a list of images to animate a referenced effect:
import love
Perhaps these functions could live under
microbit.display.fx
..? E.g.