How to edit an image pixels?

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

How to edit an sprite or a texture frame pixels?

:bust_in_silhouette: Reply From: GlaDOSik

Sprite has a Texture property - it’s usually ImageTexture data type. It’s a resource with function get_data() which returns Image object. And this class has function get_pixel(int x, int y, int mipmap_level=0) and put_pixel( int x, int y, Color color, int mipmap_level=0 ). If you don’t need to read pixels, you could use shaders. It will be much faster.

Example (add this script to TextureFrame)

extends TextureFrame

var editableImage
var imageSize = Vector2(800, 600)
var imageFormat = Image.FORMAT_RGBA
var imageTexture

func _ready():
	#Image is a built-in type
	editableImage = Image(imageSize.x, imageSize.y, false, imageFormat)	
	
	imageTexture = ImageTexture.new()
	imageTexture.create(imageSize.x,imageSize.y, imageFormat, 0)	
	imageTexture.set_data(editableImage)
	set_texture(imageTexture)
	set_process_input(true)
	
func _input(event):
	if(event.type == InputEvent.MOUSE_MOTION):
		editableImage.put_pixel(event.x,event.y, Color(1.0,1.0,1.0))
		imageTexture.set_data(editableImage)

Unfortunately, this code is pretty slow for bigger images (300x200 is fine, 800x600 lags on my computer). And as you can see, you can’t draw an uninterrupted line. You would have to rasterize the line between the last point and current point. But even Bresenham’s line algorithm would slow this code even more.

If you really want to make a paint application in Godot (I don’t think it’s a good idea - I’m working on my own pixel art editor in Java), you should probably use CanvasItem’s functions draw_line(), draw_circle(), etc. It will be way faster. At “save image” prompt, get a root viewport and call queue_screen_capture(). Wait for a while and call get_screen_capture(). It returns the image you can save by save_png( String path=0 ) call.

Doesn’t change at all.

get_node("TextureFrame").get_texture().get_data().put_pixel(2,2, Color(1,1,1))

Is it just that? I tried putting it in _draw() and _ready(), and even tried putting update() after it. What’s wrong?

By the way, i’m trying to do a kind of Paint.

mateusak | 2016-05-08 18:39

Does it works with image_texture.set_data() after modifying pixels?

If you want to make a painting game, I suggest you use an accumulation technique with RenderTexture: draw things on it (Sprite, 3D mesh, _draw() method, anything Godot can draw…), but never clear it, so what you draw will stay “printed”.
It will be much faster than moving data between the graphic card and RAM everytime you set a pixel :slight_smile:

Zylann | 2016-05-08 20:03

I updated my answer with code example.

GlaDOSik | 2016-05-08 20:17

Thank you very much! I know it’s not a good idea, but for my purpose it’s just enough (maximum 32x32 image). Thank you too Zylann!

mateusak | 2016-05-08 21:50

You forgot to call lock() and unlock() functions. It maked code much faster

Robotex | 2021-07-02 18:33

:bust_in_silhouette: Reply From: Robotex

This code works well and fast:

extends TextureRect

var imageTexture : ImageTexture = null
var dynImage : Image = null

func _ready():
	imageTexture = ImageTexture.new()
	dynImage = Image.new()
	
	dynImage.create(rect_size.x,rect_size.y,false,Image.FORMAT_RGB8)
	dynImage.fill(Color(1,1,1,1))
	
	imageTexture.create_from_image(dynImage)
	self.texture = imageTexture
	
func _process(delta):
	update_image()
	
func update_image() -> void:
	dynImage.lock()
	#do your drawing magic here
	for i in range(rect_size.x):
		for j in range(rect_size.y):
			dynImage.set_pixel(i, j, Color(0, 1, 0, 1))
	dynImage.unlock()

	imageTexture.set_data(dynImage)
	self.texture = imageTexture