What would be the best Node to create a paint drawing application?

Hi! I’m testing different nodes to find the best one for creating a painting application. Which ones would be the most appropriate? I’ve noticed that there are several possible nodes.

I’m actually trying to recreate one of my old games that contains a painting section, but I’m stuck. But before continuing with my game:

-Line2D (Perfect for drawing, but there’s no boundary area for drawing, and I’m not sure about flood filling).

-TextRect (Works well, but the filling is slow and resource-intensive).

-TileMapLayer (I can’t create the drawing script, and I don’t know if I need to add a TileMap).

-Panel (Works well, but the filling is slow and resource-intensive).

-ColorRect (Not tested yet, but shouldn’t be optimized like TextRect and Panel for filling).

There are several buttons, each with their own functionality.

-tiny size brush
-medium size brush
-big size brush
-flood fill
-undo
-redo
-eraser
-erase all the drawing
-save (save the drawing as a local .png file and create a specific folder on the PC to store the drawings.)
-save (save the drawing in memory)
-load (loads the drawing saved in memory when returning to the application.)

TextureRect is what I’d use for a drawing application. At its core you are just stamping colorful shapes onto a texture 60 times a second.

Flood fill will always be expensive in a raster paint program.

Subviewports with clear set to never, then you can use get_texture for saving and undo/redo. All you have to do is show the brush in the subviewport and drag it around with the mouse, then hide when not brushing.

It would also allow you to create shader brushes that could deform pixels in the viewport area and also can create canvas layers with multiple subviewports.

The only thing i dont now how to handle would be zooming.

I created a Godot project containing various examples. This one, which is the most advanced, contains a Panel as well as a DrawButton and FillButton. It is one of the most advanced and the most accomplished, but I wonder if it is still possible to optimize the flood fill further.

I just created an example on Godot to test different ways of creating a painting software with the different nodes.

Also I heard that using shaders would be the best way to optimize flood fill but I don’t really know.

I’m not sure shaders would be the best way. You can’t easily retrieve data from shaders, and you’ll need to do that to write the image to a file.

That is where the subviewport, set to never clear, comes in. But a flood fill algorithm as a shader doesnt seem practical because of a GPUs parallel nature and flood fill is a searching algorithm. You could probably get by on small images without choking the GPU searching a path from the fragment to the click point on every pixel. But i think the gpu threads run stateless so they will not be able to share work. This is both good and very bad for that technique.

People have made fill shaders run in real time every frame, but unless you unlock the frame rate could be very slow.

I was trying to think of a way to make a mask on the CPU for the fill shader to easily test. But at that point you have done all the work already… It would allow you to fill with patterns though.

There’s actually an official paint demo if you haven’t seen it already.

I’ve already tried this demo. However, there’s no flood fill, but changing the background color gives some ideas.

Alternatively, there’s this video that shows how to create a Flood Fill with TileMap/TileMapLayer.

However, it’s for Godot 3 and doesn’t explain how to draw with TileMaps.

Luckily someone in the comments gave the code for Godot 4.

extends TileMap

func startTheFill( worldPos:Vector2 , cellToSet:int=1 ) → void:

setup

var mapPosStart := local_to_map(worldPos)

var cellTypeToReplace := get_cell_tile_data(index,Vector2i(mapPosStart.x, mapPosStart.y))

flooding and filling

set_cell(1, Vector2i( mapPosStart.x, mapPosStart.y), cell_ID, Vector2i(0,0))

var q := [mapPosStart]

var i := 0

while q:

i += 1

var currCell:Vector2= q.pop_front()

if get_cell_tile_data(1, Vector2i(currCell.x-1, currCell.y)) == cellTypeToReplace:

set_cell(1, Vector2i(currCell.x-1, currCell.y), cell_ID,Vector2i(0,0))

q.append(Vector2(currCell.x-1, currCell.y))

if get_cell_tile_data(1, Vector2i(currCell.x+1, currCell.y)) == cellTypeToReplace:

set_cell(1, Vector2i(currCell.x+1, currCell.y), cell_ID,Vector2i(0,0))

q.append(Vector2(currCell.x+1, currCell.y))

if get_cell_tile_data(1, Vector2i(currCell.x, currCell.y-1)) == cellTypeToReplace:

set_cell(1, Vector2i(currCell.x, currCell.y-1), cell_ID,Vector2i(0,0))

q.append(Vector2(currCell.x, currCell.y-1))

if get_cell_tile_data(1, Vector2i(currCell.x, currCell.y+1)) == cellTypeToReplace:

set_cell(1, Vector2i(currCell.x, currCell.y+1), cell_ID, Vector2i(0,0))

q.append(Vector2(currCell.x, currCell.y+1))

The code gives me an error message in Godot 4.3

Parser Error: Identifier “index” not declared in the current scope.

I think using a TileMap (which is deprecated in Godot 4.4) where a tile is a single pixel isn’t the best use of it. The fill logic isn’t restricted to TileMaps, you can run the same logic on an Image.

In fact, wouldn’t just modifying an Image directly be the best way to do the image operations? Or would it be too slow? This plugin written with gdscript uses the Image class for its image operations. It has flood fill as well if you want to look at that.

Thanks! I didn’t know about this plugin. I’ll try it!

I created a small demo yesterday with TileMap, and the filling works well without any latency. However, when you try to fill outside of a frame defined by the tiles, the game crashes, but that’s okay because I want to draw on a small area and not the entire screen.

I’ve watched several YouTube videos about the Sprite Painter add-on, but it seems to only work in the editor and not while the game is running, unless I’m mistaken.

I just want to create a basic painting game.

You’re right that it’s an editor plugin. I suggested it because I thought looking at the code would be helpful.

Finally, I found a way to paint with a quick and efficient flood fill. I used the TileMap and then determined a possible limit where I could paint with the TileMap. This is the most optimized method I’ve found. The lines are square and that’s what I want to do. I’m actually porting one of my old games to Godot.

1 Like