Best way to optimize constant changes to an Image

Godot Version

4.3 beta 2 (all of my code is in C#)

Question

Hi, I’m trying to make a drawing app. My strategy so far was to process a brush stroke to an Image, and save that image into an ImageTexture, which then I draw it to the screen.

But there are bottlenecks that I’m unable to find workarounds for.

The main problem is with the Image object, the constant SetPixel and GetPixel methods. For every pixel in the stroke I’m modifying, I need to GetPixel from the stroke, GetPixel from the background, and finally blend them and SetPixel it. For every pixel in a stroke, which when you draw a line, happens every frame, is quite heavy.
I’m aware that I can reduce the update count, distance between two strokes and all, but the cost above is so big that, even if these help, it is not even close to being enough.
I don’t have any in-depth experience with optimizations, but I’m guessing the problem is the constant array access, and perhaps the extra method calling overhead caused by C#?
I can’t parallel it, the document says that parallelizing something that modifies anything related to GPU will actually make it slower.
Can writing the code in GDScript make it faster? Or because I’ll be accessing the code from C#, it will create even more overhead?
Can I perhaps expose a method with GDExtension to partially update an Image instead of a pixel? The problem is I don’t have much control of how background and brush stroke pixels blend if I don’t have each pixel.

What should I do? I appreciate your help!

Have you tried blend_rec function?

It won’t be faster in gdscript.

I guess I would approach it differently.

You have two viewports one is set to never clear. Your brush is a shape and solid color. The brush is visible in the main viewport but it is not visible inside the drawing subviewport.

When the user clicks the subviewport shape becomes visible rendering to the subviewport. The user drags and a brush stroke happens. User releases mouse click and an image of the subviewport is saved.

Then if the user presses CTRL -z you can load a previous image, if no previous image clear the subviewport.

There is no need to blend in this case as it will happen in real time.