0 votes

I want to change the color of every white pixel in an Image.

Godot version 3.2.3.stable
in Engine by (377 points)

4 Answers

+2 votes
Best answer

Dealing with textures is an entirely different matter, they live on your graphics card, not your RAM.

So here is what happens in your code:

texture_normal.get_data().lock()`

get_data() downloads a copy of the texture from the graphics card and returns it as an Image. Then you lock it, but the image is not stored anywhere after that, so it gets destroyed.

for row in texture_normal.get_height():
    for column in texture_normal.get_width():
        print(texture_normal.get_data().get_pixel(column, row))

Then you iterate every pixel, and for each pixel, you are calling get_data(). This once again downloads a copy of the entire texture as an Image, which might be very slow, (unless Godot caches it?). Then you use get_pixel() but because this is another instance of Image, it will likely cause an error because that copy was never locked.

if texture_normal.get_data().get_pixel(column, row) == ColorN("white"):
    texture_normal.get_data().set_pixel(column, row, ColorN("red"))

Here, there is more of the same. The texture is downloaded again and again, and more errors could occur.

    texture_normal.get_data().unlock()

And one last copy of it, which you try to unlock but it won't work because it was never locked in the first place.


Basically, you are working on downloaded copies of a texture, and that texture might not even be modifiable in the first place, if it's a StreamTexture for example. Textures imported in your project are StreamTexture by default.

If you want a texture to be modifiable, you need to use ImageTexture. This type of texture allows you to create it from an Image.
There doesn't seem to be an import option to make PNG files import as an ImageTexture, so it seems like you will have to replace your texture with a new instance of an ImageTexture.

# Get the image ONCE
var image = texture_normal.get_data()

# Lock it
image.lock()

for row in image.get_height():
    for column in image.get_width():
        print(image.get_pixel(column, row))
        if image.get_pixel(column, row) == ColorN("white"):
            image.set_pixel(column, row, ColorN("red"))

image.unlock()

# At this point, you have an `Image` with replaced pixels.
# If you want to draw it on screen using a sprite or a mesh instance,
# you can create an ImageTexture from it.

texture_normal = ImageTexture.new()
texture_normal.create_from_image(image)

# Then don't forget to update the `texture` property of whatever uses that texture
get_node("Sprite").texture = texture_normal
by (27,778 points)
selected by

Okay, that works so far but it is extremely blurry and I want to see the pixels good since it is pixelart. Flags is 7, whatever that means, how can I change this? Or should I change lossy_quality which I also tried yet but did not change something?

Turn off all flags if you want blocky pixels:

texture_normal.create_from_image(image, 0)

7 is the default value of a bitmask (a number whose bits have each a specific meaning), see https://docs.godotengine.org/en/stable/classes/class_texture.html#enum-texture-flags
By default it is equivalent to Texture.FLAG_MIPMAPS | Texture.FLAG_REPEAT | Texture.FLAG_FILTER. If you want none of them, it would be 0.

Thank you so much!

0 votes
texture_normal.get_data().lock()
        for row in texture_normal.get_height():
            for column in texture_normal.get_width():
                print(texture_normal.get_data().get_pixel(column, row))
                if texture_normal.get_data().get_pixel(column, row) == ColorN("white"):
                    texture_normal.get_data().set_pixel(column, row, ColorN("red"))
        texture_normal.get_data().unlock()

That should work but for some reason, I get "E 0:00:00.451 getpixel: Image must be locked with 'lock()' before using getpixel().
<C++ Error> Condition "!ptr" is true. Returned: Color()
<C++ Source> core/image.cpp:2412 @ get_pixel()
switch.gd:13 @ _ready()" even if I have locked the image. So new question, what is wrong?

by (377 points)
edited by
0 votes
by (538 points)

I do not understand what this video should have to do with my problem, sorry.

You asked how to turn every pixel white, and the video shows you how to make a hit flash effect by turning every pixel white. You wouldn't have to make the hit flash, just follow the part where he turns every pixel white.

Ah OK, you understood my question wrong. I do not want to change the color of every pixel in Image to white but I do want to change all white pixels in Image to red. Can you follow?

Oh I'm sorry, I definitely understood that wrong. I honestly have no idea, sorry.

0 votes

Ok, this video: https://www.youtube.com/watch?v=bjuUtddnUok&t=20700s can help.

Part of this talk about portal effect will help you find every pixel GPU think looks kinda white(well pink in the video).

Portal part direct link: https://youtu.be/bjuUtddnUok?t=21715

by (536 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.