micropython icon indicating copy to clipboard operation
micropython copied to clipboard

Create built in image effects.

Open ntoll opened this issue 8 years ago • 14 comments

Pass in a single image and get back a list of images to animate a referenced effect:

  • [ ] sparkle - active pixels in the original image are dimmed / brightened in a random fashion.
  • [ ] throb - the throbbing effect seen when you import love
  • Wipe effects - the image fades in from the specified direction, until all the pixels are fully on:
    • [ ] wipe_left
    • [ ] wipe_right
    • [ ] wipe_up
    • [ ] wipe_down
  • [ ] radar - a circular motion is created using the active pixels and a rotating "bar" of bright pixels that gradually dim to darkness.
  • Fade in/out - the whole image fades in / out to / from dark to bright:
    • [ ] fade_in
    • [ ] fade_out

Perhaps these functions could live under microbit.display.fx..? E.g.

>>> twinkle_happy = display.fx.sparkle(Image.HAPPY)
>>> twinkle_happy
[ ... a shortish list of Image objects ... ] 
>>> display.animate(twinkle_happy, 50)

ntoll avatar Nov 07 '15 15:11 ntoll

Perhaps these functions could live under microbit.display.fx..?

Or just be methods of an Image instance, eg:

>>> twinkle_happy = Image.HAPPY.sparkle()

dpgeorge avatar Nov 07 '15 23:11 dpgeorge

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))

markshannon avatar Nov 11 '15 09:11 markshannon

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".

deshipu avatar Nov 11 '15 09:11 deshipu

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 and animate do already behind the scenes; they create an iterator of images which is then passed to display.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?

markshannon avatar Nov 12 '15 21:11 markshannon

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).

deshipu avatar Nov 12 '15 21:11 deshipu

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?

markshannon avatar Nov 22 '15 17:11 markshannon

Of course it would work. It would wipe the image from both sides at the same time, narrowing it, so to say.

deshipu avatar Nov 22 '15 17:11 deshipu

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.

deshipu avatar Nov 22 '15 17:11 deshipu

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)?

markshannon avatar Nov 22 '15 18:11 markshannon

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

deshipu avatar Nov 22 '15 18:11 deshipu

Of course you need to have a check if the thing is iterable.

deshipu avatar Nov 22 '15 18:11 deshipu

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?

markshannon avatar Nov 22 '15 18:11 markshannon

being repeated -> repeated for ever

markshannon avatar Nov 22 '15 18:11 markshannon

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?

deshipu avatar Nov 22 '15 18:11 deshipu