Is it possible to control blending amount with Image.blend_rect() ?

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

I need to blend two images that i use to store data for a 2D map (that i load from the txt Texture var)

base_data=txt.get_data()
base_data.lock()
current_data=other_txt.get_data()
current_data.lock()    
current_data.blend_rect(base_data, Rect2(0,0, size.x, size.y), Vector2(0,0))

The above code works partially,

  1. It does not blend, but simply pasting the base_data into the current_data, effectively it is behaving like blit_rect
  2. even if it blended, this method has no control on blending amount (i need to control how much base_data blends into current_data)

is there a way to achieve this? or do i need to pass through every pixel and set their value “manually”?

:bust_in_silhouette: Reply From: sash-rc

blend_rect() uses alpha from source image (base_data in your case).

In order to achieve your task (as you described) you can use:

  • blit_rect_mask() which uses a mask image, which you can fill() with (half) transparent color.

And faster options like

  • mix two textures in fragment shader on screen
  • overlay two CanvasItem nodes like TextureRect and control transparency of topmost one with self_modulate.

Thanks! Great tips!

Andrea | 2021-08-25 10:59

So, i tried your method, which makes totally sense, but i couldnt make it work (i think because i do not understand correctly how blend and blit works)

to summarize:
the current_data is the “picture” that need to be updated regularly.
On this picture, i need to add base_data, i need to blend it with a value called ratio=0.1. The effect i look for is to slowly fade from current into base (each iteration the current picture get a little bit more similar to the base)
Therefore, i created a new empty picture and called ratio_data.fill(Color(1,1,1,ratio))

Now i tried with:

  • current_data.blit_rect_mask(base_data, ratio_data, rect, vec) => the value goes immediatly back to base_data (no blending based on the ratio)

  • I swapped current data and ratio data current_data.blit_rect_mask(ratio_data, base_data, rect, vec)=> every pixel of current jump to Color(1,1,1,ratio)

  • current_data.blend_rect_mask(base_data, ratio_data, rect, vec) => the value goes to Color(0,0,0,1) (no idea where this comes from)

  • again swapped ratio and base, same result as above, all pixels goes to Color(0,0,0,1)

  • I tried inverting the value of ratio toratio_data=fill(Color(1,1,1,1-ratio), as well as Color(ratio,ratio,ratio,ratio), but same results as above

Is there something i’m doing wrong?
I believe that the mask is used as a boolean, either one pixel is replaced, or it is not, depending if the alfa of the mask is 0 or >0.

Shaders and modulate are not of any use (i’m using the pixel in the image to store data, it’s not just visual).
I tried doing shader computation in the past but there are more issue than solution there.
Compute shaders - hot to get data back - Archive - Godot Forum

Andrea | 2021-08-30 12:07

I believe that the mask is used as a boolean

I just tested, and it appears you’re right: unexpectedly Godot’s implementation is a weird or there’s a bug: mask works in boolean mode.
However if you manually set alpha for one image you could achieve result with blend_rect:

  var img1 : Image = Image.new() 
  var img2 : Image = Image.new()

  var size : Vector2 = Vector2(10, 10)
  var r : Rect2 = Rect2(Vector2.ZERO, size)

  func prepare_images():
    img1.create(size.x,  size.y, false, Image.FORMAT_RGBA8)
    img2.create(size.x,  size.y, false, Image.FORMAT_RGBA8)

    img1.fill(Color.red)
    img1.lock()
    for x in range(size.x): 
      img1.set_pixel(x, x, Color.yellow)
    img1.unlock()

    img2.fill(Color.blue)

  func blend_test(rate : float):
    # -------- apply "mask"
    img2.lock()
    for x in range(size.x):
      for y in range(size.y):
        var cl : Color = img2.get_pixel(x, y)
        cl.a = rate
        img2.set_pixel(x, y, cl)
    img2.unlock()

    img1.blend_rect(img2, r, Vector2.ZERO)

    img1.save_png("res://test.png")

  func _ready() -> void:
    prepare_images()
    blend_test(0.3) # 0.0 .. 1.0

But please note this could be slow. But as alternative, you can render canvas items to viewport texture.

sash-rc | 2021-08-30 19:57

Yeah, I tried blend_rect the second picture without mask by making it half transparent first, didn’t work (can’t remember exactly what the result was, I’ll let you know asap)

On my project i found a workaround: in my case 90% of the pixels are identical between the two images, so i don’t actually need to blend all of them.
I stored the coordinate of the different pixels in an array, and blended them only with a manual set_pixel().
When the blending brings back the pixels to be identical again, i remove them from the array.
It works but the efficiency is strictly depending on how much the two pictures are different

Andrea | 2021-08-30 21:42

The code I posted works for me.

sash-rc | 2021-08-30 22:34

you are right, it worked, i was using a value of ratio that was too small, and the 8bit value of the pixel Color was not precise enough for it.
now i got a new problem to solve :slight_smile:

Andrea | 2021-08-31 16:50