How to draw a mask on a Sprite ?

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

Hi,

I want the user to draw a mask on a Sprite. The sprite is initially invisible and as the user clicks on the sprite it reveal itself, like “painting” the Sprite.

I’m using the lightmask demo, my first attempt was to duplicate a light as long as the user clicks but it makes the game very slow.

I want to draw inside the Light2D’s texture. Is that possible ? Is it possible to draw a texture programatically ?

I’m trying:

func _input(event):
if ( event.type == InputEvent.MOUSE_BUTTON ):
	var tex = get_texture()
	tex.draw(get_node("brushSprite").get_texture() , event.pos )
	set_texture(tex)
	update()

But this isn’t working.
Any help will be greatly appreciated.

Cheers.

Well, I tried pretty much everything. I learnt that drawing on a CanvasItem is pretty easy, but couldn’t convert that output on a working texture for my light.

The closer I got to is loading the image and painting Rect2 of 1 pixel size depending on mouse position.

I’ve seen there is a brush method in Image, but like the draw method on Texture, both are poorly documented, I tried using them with different parameters, no luck whatsover.

macramole | 2016-03-26 16:43

Ok, I’m getting closer:

var fondo
var fondoData
var brushData
var whiteData

func _ready():
	set_process_input(true)
	fondo = ResourceLoader.load("res://assets/empty.png")
	fondoData = fondo.get_data()
	whiteData = ResourceLoader.load("res://assets/white.png").get_data()
	brushData = ResourceLoader.load("res://assets/brush_bw.png").get_data()
func _input(event):
	if Input.is_mouse_button_pressed(1):
			var realPos = event.pos
			realPos.x -= brushData.get_width() / 2
			realPos.y -= brushData.get_height() / 2
			
			fondoData.brush_transfer( whiteData, brushData, realPos )
			get_node("cabeza/luces/luz").get_texture().set_data(fondoData)

fondoData.brush_transfer works like this:

  • fondoData is the background image.
  • whiteData is an image with same size that you want to put over fondoData
  • brushData is a grayscale image that will be used as brush
  • realPos is the position where you want to paint

pretty simple actually, and this is working, but still very slow (although faster that previous attempts).

Another way that works is not using lightmask and instead of whiteData use the image data. same bad performance tho

Maybe shaders its the answer…

macramole | 2016-03-26 19:11

:bust_in_silhouette: Reply From: macramole

Using _process instead of _input and disabling mipmapping and filter did the trick

var fondo
var fondoData
var brushData
var whiteData

func _ready():
    set_process(true)
    whiteData = ResourceLoader.load("res://assets/cuerpo.png").get_data()
    brushData = ResourceLoader.load("res://assets/brush_bw.png").get_data()
func _process(event):
    if Input.is_mouse_button_pressed(1):
            var realPos = event.pos
            realPos.x -= brushData.get_width() / 2
            realPos.y -= brushData.get_height() / 2
            
            fondoData = get_node("TextureFrame").get_texture().get_data()
            fondoData.brush_transfer( whiteData, brushData, realPos )
            get_node("TextureFrame").get_texture().set_data(fondoData)

Thank you so much for posting a solution to your problem. You’re the hero this community needs :slight_smile:

I still can’t quite figure it out. Could you post an example scene?

ugly_cat | 2016-04-09 08:45

Haha, thanks, you can download my source code here: Human Emotions by macramole

macramole | 2016-04-10 18:36

:bust_in_silhouette: Reply From: sleepprogger

I had a similar problem and found out you can use a Viewport with renderTexture enabled to generate masks from code.
See this question: Mask a sprite with a dynamic mask - Archive - Godot Forum