godotgdscriptgodot3

How to take a screenshot beyond the viewport?


I have a large level design & I'm trying to take a pixel perfect screen shot of a particular area and/or the entire map bound within a region like this:

enter image description here

The region can be changed in size and position so I used a ReferenceRect ( Green box in the image) node to manually change it

So far I've come up with this:

extends ReferenceRect

func capture_image():
    var viewport=get_viewport()
    viewport.set_size_override(true, Vector2(rect_size.x, rect_size.x * viewport.size.y / viewport.size.x ))
    
    var image = viewport.get_texture().get_data()
    image.flip_y()
    image.save_png("res://screenshot.png")
    print("Took Screenshot!")

func _unhandled_input(event):
    if event is InputEventKey: 
        if event.pressed and event.scancode == KEY_SPACE: # try pressing space twice
            capture_image()

This fits the entire game within the screen window but the image generated does not give a pixel perfect screenshot & the image size/resolution remains 1024 x 600 pixels as you can see: enter image description here

I suspect the window size itself would have to be increased to fit the ReferenceRect in order to get a pixel perfect screenshot

So is it possible to achieve this?
A crude method would also work since this is purely for development stage

Note: I'm using godot v3.5


Solution

  • Add this script to the ReferenceRect & extend its borders to encapsulate the area to screenshot & press space:

    extends ReferenceRect
    
    export var zoom=1.0
    
    func capture_image():
        var camera=Camera2D.new()
        camera.anchor_mode=Camera2D.ANCHOR_MODE_FIXED_TOP_LEFT
        camera.current=true
        camera.zoom=Vector2(zoom,zoom)
        self.add_child(camera)
        camera.position=Vector2.ZERO
        
        var tree=get_tree()
        tree.paused = true
        yield(tree, "idle_frame")
        
        var zoom_rect=rect_size/zoom
        var viewport_size=get_viewport().size * zoom
        
        var image=Image.new()
        image.create(zoom_rect.x, zoom_rect.y, false, Image.FORMAT_RGBAH)
        
        while camera.position.y < zoom_rect.y:
            while camera.position.x < zoom_rect.x:
                yield(tree, "idle_frame")
                var segment=get_viewport().get_texture().get_data()
                segment.flip_y()
                image.blit_rect(segment, Rect2(Vector2.ZERO,segment.get_size()), camera.position/zoom)
                camera.position.x+=viewport_size.x
            
            camera.position.x=0
            camera.position.y+=viewport_size.y
        
        image.save_png("res://screenshot.png")
        get_tree().paused = false
        camera.queue_free()
    
    func _unhandled_input(event):
        if event is InputEventKey:
            if event.pressed and event.scancode == KEY_SPACE:
                capture_image()
    

    Note:
    As per the docs:

    The maximum width and height for an Image are MAX_WIDTH and MAX_HEIGHT.

    so the rect_size for the ReferenceRect you attach this script to cannot exceed (16384 x 16384) sucks :/

    On the bright side the limit for this in Godot 4 is 16777216 x 16777216 which is much more better but I haven't gotten to that, so feel free to translate the above code to Godot 4