Can I get all white colored pixels in an image?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By MaaaxiKing

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

:bust_in_silhouette: Reply From: MaaaxiKing
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 get_pixel: Image must be locked with ‘lock()’ before using get_pixel().
<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?

:bust_in_silhouette: Reply From: djmick

This video should help.

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

MaaaxiKing | 2021-02-04 09:39

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.

djmick | 2021-02-04 13:39

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?

MaaaxiKing | 2021-02-04 15:42

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

djmick | 2021-02-04 17:11

:bust_in_silhouette: Reply From: yrtv

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

:bust_in_silhouette: Reply From: Zylann

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

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?

MaaaxiKing | 2021-02-04 18:13

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 Texture — Godot Engine (stable) documentation in English
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.

Zylann | 2021-02-04 18:17

Thank you so much!

MaaaxiKing | 2021-02-04 18:18