+5 votes

How to edit an sprite or a texture frame pixels?

in Engine by (1,091 points)

2 Answers

+3 votes
Best answer

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.

by (814 points)
selected by

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.

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 :)

I updated my answer with code example.

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!

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

0 votes

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
by (246 points)
Welcome to Godot Engine Q&A, where you can ask questions and receive answers from other members of the community.

Please make sure to read How to use this Q&A? before posting your first questions.
Social login is currently unavailable. If you've previously logged in with a Facebook or GitHub account, use the I forgot my password link in the login box to set a password for your account. If you still can't access your account, send an email to webmaster@godotengine.org with your username.