Recently, I implemented two fragment shaders for interactive Poisson Blending (seamless blending). 

Here is my real-time demo and code on ShaderToy:

Press 1 for normal mode, press 2 for mixed gradients, press space to clear the canvas.

Technical Brief

I followed a simplified routine of the Poisson Image Editing paper [P. Pérez, M. Gangnet, A. Blake. Poisson image editing. ACM Transactions on Graphics (SIGGRAPH’03)]:

  • Let’s name the bottom image as “BASE”, and the image to overlay as “SRC”, the final image as “RESULT”, then the color in the boundary should be the same
    • RESULT(u, v) = BASE(u, v) ∀(u, v) ∈ ∂ SRC   
  • Inside the mask region, just add the gradient of “SRC” (or the bigger gradient of “SRC” and “BASE”, if we want to mix the gradients) into the current result image:
    • RESULT(u, v) = RESULT(u, v) + ∇ SRC(u, v);
    • alternatively, 
    • RESULT(u, v) = RESULT(u, v) + max{ ∇ SRC(u, v), ∇ BASE(u, v) );

We can use jump flood algorithm to speed this procedure up: 

Implementation Details

I used one frame buffer to store the drawing of the user. 

  • iChannel0 stores the frame buffer itself
  • iChannel1 stores the keyboard response for interaction

This is a very simple drawing shader which can also be adapted for other drawing applications.

The second frame buffer to used to iterate the Poisson blending process:

  • iChannel0 stores the previous frame buffer itself
  • iChannel1 stores the mask buffer
  • iChannel2 stores the base image
  • iChannel3 stores the source image to blend

Sometimes, the texture is not loaded in the first few frames in ShaderToy, so I sample the last pixel of this framebuffer to test whether it is initialized with correct image.

The main fragment shader is for showing the result:

  • iChannel0 stores the previous frame buffer
Ideally, the iteration should stop once the change of the colors is small enough; however, I do not want to use another pixel to store this global error variable. So just 200 iterations will induce beautiful blended image.

Here is another result:


Finally, here is a mysterious result with wrong mixing of gradients 🙂