How to merge two png files during runtime and save to disk ?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By AlvaroAV
:warning: Old Version Published before Godot 3 was released.

What I’m doing ?:

  • I’m trying to have a scene for a player to customize the player outfit.
  • The player Sprite is made of 3 sprites: body, eyes & mouth.
  • There are independant png images for the different bodies, eyes and mouths.

The code I’m trying:

# Create Image objects and load the png's
var body = Image()
var eyes = Image()
body.load("res://path_to_png")
eyes.load("res://path_to_png")	
# TRY1 - Draw the eyes over the body
body.blit_rect(eyes, Rect2(0,0,100,100), Vector2(15, 20))

# TRY2 - Brush the eyes as a mask on the body
body.brush_transfer(eyes, eyes, Vector2(15, 20))

# Save the image to new route
body.save_png("res://assets/generated/g_body.png")

TRY1: This almost did the trick but the problem is the eyes are not “drawn” over the body so the background is empty.

Example generated Image:

It’s not quite clear but the space between the eyes is transparent, and what I need is to see the body under the eyes

TRY2: I don’t quite understand the functionality of brush_transfer and couldn’t find any good example, maybe anybody can point me to some tutorial on this. When I save the image using brush_transfer only the body image is shown

##Questions:

  1. Is there any better way to achieve what I’m trying to do ?
  2. Is there any way to get an Image object from a Sprite or Texture ?
  3. Is there a way to set_modulate on a Image object?
  4. Is there any good tutorial besides the official docs about editing images programmatically ?

I started a few days ago with Godot and everything is new to me, I just checked the offical docs and this site. If there is any other good forum or page I can check for help I appreciate any recommendation.

Thanks

Edit

I tried to achieve something like @raymoo suggested using a Viewport.

My goal was:

  • Add an “invisible” Viewport into my scene ( I don’t need the user to see this Viewport)
  • Draw 3 sprites on the viewport (Body, eyes & mouth)
  • Get an Imageobject from the Viewport with: myviewport.get_screen_capture()
  • Save the image to Assetsto use it later

My problem is the get_screen_capture() is allways empty. I tried all the configuration I could and check all the properties of Viewport on the official docs. I also used get_screen_capture() on the main viewport and works fine, it returns a screenshot of the game.

  • Why the “sub” viewport I created return an empty png ?
  • I looked for information about initializing a Viewport programmatically but found nothing. Can someone suggest how a Viewport can be initialized ?
  • Is possible to have this Viewport outside the visible game ? I just need this Viewport to generate a custom image, I don’t need the user to see this

The code I’ve tried with Viewport solution (This code is inside a script that belongs to a Node on my main scene):

var body_texture = preload("res:/path_to_body.png")
var eyes_texture = preload("res://path_to_eyes.png")
var body = Sprite.new()
var eyes  = Sprite.new()
var vps = Viewport.new()

eyes.set_texture(eyes_texture)
body.set_texture(body_texture)
vps.add_child(body)
vps.add_child(eyes)
add_child(vps)

get_viewport().queue_screen_capture()
yield(get_tree(), "idle_frame")
yield(get_tree(), "idle_frame")
yield(get_tree(), "idle_frame")
var result = vps.get_screen_capture()
result.save_png("res://assets/images/generated/result.png")

Maybe a bit hacky but for 1 and 2 you could use Viewport.get_screen_capture() on a Viewport with the things to composite.

raymoo | 2017-03-07 06:36

2 - You can get the ImageTexture resource from the Sprite node, then get the Image data from the texture.

3 - Image is a built-in type, not even a Resource, the modulation is a CanvasItem node property, maybe there is a way to work over the RawArray from the Image or paint each pixel with a mix technique.

4 - Is not a basic nor common thing to work over raw image data so examples of that will be hard to find for now.

eons | 2017-03-07 13:33

Thanks for your info, I tried to achieve something like that but I don’t know how to make a Viewport visible inside my main node. I edited the question with more info

AlvaroAV | 2017-03-07 18:16

To use get_screen_capture you need to queue_screen_capture first and wait a couple of frames.

eons | 2017-03-09 14:46

Thanks for the info @eons but I already added those lines to wait a couple of frames. That’s not the problem, I think the Viewport I’m creating programatically is not being displayed anywhere. I added a sample of the code I’m trying with the Viewport

AlvaroAV | 2017-03-09 15:00

:bust_in_silhouette: Reply From: AlvaroAV

Finally I found the solution to what I was looking for !

I have this structure on my main 2D scene:

The important part for taking a screenshot on a viewport is the next:

Control -> subViewport -> camera2D

The node camera2D has a related script (camera2D.gd) like this:

extends Camera2D

var body_texture = preload("res://path_to_body.png")
var eyes_texture = preload("res://path_to_eyes.png")

func _ready():
	var eyes = Sprite.new()
	var body = Sprite.new()
	body_texture.set_size_override(Vector2(64,64))
	body.set_texture(body_texture)	
	eyes.set_texture(eyes_texture)
	self.add_child(body)
	self.add_child(eyes)

func get_screenshot():
	# get_viewport returns the closest viewport (subViewport)
	get_viewport().queue_screen_capture()
	yield(get_tree(), "idle_frame")
	yield(get_tree(), "idle_frame")
	yield(get_tree(), "idle_frame")
	var result = get_viewport().get_screen_capture()
	result.save_png("res://assets/generated/result.png")	

With this script on the camera2D, I can get a screenshot for the subViewport from anywhere, right now I have this on my main scene to take the screenshot:

onready var camera = get_node("../Control/subViewport/camera2D")
...
...
camera.get_screenshot()  # Done ! The screenshot is saved on the generated folder

With this I’m able to generate new pngs to use on my project. I was doing this to generate the player png’s after the user has customized the appearance (color,eyes…) instead of having png’s for each different customization.

Tips:

  • You can check Render Target -> Enabled to make the ViewPort invisible but still you can get the screenshots

  • With the Render Target -> Enabled you can set custom position on size for subViewport. I don’t know if there is another way to do it, but without that option checked I was unable to change position and size of subViewport

:bust_in_silhouette: Reply From: CakeLover

In Try 1 you can use blend_rect() instead of blit_rect() like this

# Create Image objects and load the png's
var body = Image()
var eyes = Image()
body.load("res://path_to_png")
eyes.load("res://path_to_png")  

# TRY1 - Draw the eyes over the body
body.blend_rect(eyes, Rect2(0,0,100,100), Vector2(15, 20))

# Save the image to new route
body.save_png("res://assets/generated/g_body.png")