Forum Archive

Image from scene

Webmaster4o

I made a cool animation with scene. Is it possible to get a snapshot of the scene from within the program? Or should I rewrite in PIL. The code is here if that helps. It's loosely based on the "particles" example script.

from scene import *
from math import sin, cos

def polar2cart(r,theta):
    x = r*cos(theta)
    y = r*sin(theta)
    return x, y


class Particle():
    def __init__(self, location):
        self.location = location
        self.alpha=1

class ParticleSwirl(Scene):
    def setup(self):
        self.particles = set()
        self.theta=0

    def draw(self):
        background(0,0,0)
        global Particle
        self.theta += 1

        if self.theta % 2 == 0:
            self.particles.add(Particle((0,self.theta)))

        dead = set()
        for particle in self.particles:
            a=particle.alpha
            r,g,b = a,255*a,255*a
            fill(r,g,b,a)
            r, t = tuple(particle.location)
            x, y = polar2cart(r, t)

            drawx, drawy = x+self.size.w/2, y+self.size.h/2
            ellipse(drawx, drawy, 15,15)

            particle.alpha -= 0.006
            particle.location = (r+2,t+0.01)

            if particle.alpha <= 0:
                dead.add(particle)

        self.particles -= dead



run(ParticleSwirl())
JonB

ui views have a draw_snapshot method.. call inside an ImageContext, then you can get access to the image.

. perhaps you could run your scene on a sceneview?

Webmaster4o

I'll try

ccc

For working code, check out: https://forum.omz-software.com/topic/1523/controlling-screenshots/4

Webmaster4o

Why doesn't this work:

class SnapshotView(ui.View):
    def __init__(self):
        self.snapshots = []
    def draw(self):
        self.snapshots.append(self.draw_snapshot())

If I close the view and call view.snapshots, I get [None]

Phuket2

@Webmaster4o, I looked up here in the forum. I found something, but modified it to work in the class. Not sure it's exactly what you need, but will give you the next steps.

# coding: utf-8

import ui
import console
class SnapshotView(ui.View):
    def __init__(self):
        self.frame = (0,0,540,576)
        self.snapshots = []
        lb = ui.Label(frame = (0,0,  self.width, 44))
        lb.text = 'Testing'
        lb.alignment = ui.ALIGN_CENTER
        self.add_subview(lb)
        self.background_color = 'white'

    #def draw(self):
        #self.screenshot_action()

    # copied from @omz post, made some changes to
    # work in your class
    def take_screenshot(self):
        with ui.ImageContext(self.width, self.height) as c:
            self.draw_snapshot()
            self.snapshots.append(c.get_image())

if __name__ == '__main__':
    ssv = SnapshotView()
    ssv.present('sheet')
    ssv.take_screenshot()
    ssv.snapshots[0].show()
Phuket2

Ok, turns out exact same post @ccc pointed to.

Webmaster4o

Ok, got it working. Turns out with a scene view, draw() is not automatically called in the main view every time it changes. I can't subclass a scene view, so automatic snapshots every time it updates won't work. Adding

while 1:
    view.take_snapshot().show()

leads to a crash pretty quickly, I'm assuming from large memory consumption. I was, however, able to capture this snapshot successfully:

If I want to compile a recording of the whole animation, I guess I'll use PIL to draw frames, as I have in previous animations.

Phuket2

@Webmaster4o , I am not sure what you have done in the past. But rather than save them in a list, could you just write a method to save them to disk. If it's not fast enough realtime, maybe write to disk when your list has 60 or whatever items in it. Then flush it.
If you implemented an approach like that, maybe it would be better to create a list of a fixed length rather than calling append n the list everytime. I am not sure, but every cycle is going to count I guess.

JonB

show() shows it in the console. IIRC that method returns a ui.Image... check the docs, there is a way to save, or else convert to a PIL image and save.

Webmaster4o

I know, what save does, and I know how to convert it to PIL. That code was just simple code for me to demonstrate that the snapshots were being captured. If you're wondering, you can convert to PIL like so:

from io import BytesIO
from PIL import Image

i = view.take_snapshot()
pil_image = Image.open(BytesIO(i.to_png()))
Webmaster4o

I think it's sort of strange that SceneView doesn't inherit from ui.View* and can't be subclassed. Recently we've been able to add custom attributes to all ui.View children like buttons, but not SceneViews. Would it be easy to change SceneView to fit into UI better? How is it recognized as something that can be added as a subview if it doesn't inherit?

*I'm assuming it doesn't inherit, I think scene is missing from the Standard Library folder.

omz

SceneView does inherit from ui.View:

>>> issubclass(scene.SceneView, ui.View)
True

Adding custom attributes should also be possible; this was broken in one of the previous builds though.

Webmaster4o

Why can't I subclass it, then?

class myclass(scene.SceneView):
   def myfunc(self):
        pass
TypeError: Error when calling the metaclass bases
    type '_scene.SceneView' is not an acceptable base type
omz

You can't subclass ui.Button either. Why would you want to do that?

Webmaster4o

Oh. So I add attributes like SceneView().arbitrary_attribute = 5