In this section, we will explore how to perform operations across different layers of images. These operations include multiplicative and additive techniques to blend, overlay, and manipulate images in creative ways. We'll focus on three specific operations:

Superimposing One Image on Another using Arithmetic Operations

We can use a simple arithmetic to combine two images. Here's the code for the 4 operations:

add_blended_image = image1 + image2
sub_blended_image = image1 - image2
mul_blended_image = image1 * image2
div_blended_image = image1 / image2

We will see the superpose image like below,

image.png

In this case, we can see that the image produced by additive blending is overexposed, appearing very bright (almost white). This occurs because the addition method increases pixel values, pushing them toward white. A similar issue can arise with division, as pixel values are normalized between 0 and 1, and division tends to increase these values. In contrast, subtraction and multiplication typically reduce pixel values, leading to a darker image.

To address this issue, more sophisticated blending techniques, such as Overlay and similar methods, are commonly used (and are supported by most image editing software like Gimp and Photoshop).

Overlay Blend Algorithm and Code

As an additional resource, I recommend reading the following Wikipedia article. You can also try implementing the algorithms described there for further learning.

https://en.wikipedia.org/wiki/Blend_modes

Superimposing One Image on Another with Weights

To blend two images, we can superimpose them using a weighted sum for each image. This process is controlled by a weighting factor that determines the contribution of each image in the final composite.

A common approach is to define a weight map across the image dimensions, which allows the weights to vary smoothly from one side of the image to the other.

# Create a horizontal gradient for width
x_grad = np.linspace(0, 1, image1.shape[1])  # Horizontal gradient (west-east direction)
# Repeat the gradient along the height to match the dimensions of the image
weight1 = np.tile(x_grad, (image1.shape[0], 1))
weight2 = 1 - weight1  # Inverse gradient

# Create the blended image using the weight masks
blended_image = image1 * weight1[..., np.newaxis] + image2 * weight2[..., np.newaxis]

image.png

Superimposing One Image on Another with Diagnal Gradients

Here, we explore the concept of superimposing one image onto another using a diagonal gradient. Rather than applying a simple linear or horizontal blending technique, we employ a more sophisticated approach where the blending weights vary both horizontally and vertically across the image. This allows us to blend the two images smoothly along a diagonal direction, creating a transition from one image to another in a diagonal fashion, giving a more dynamic effect.

# Create a horizontal gradient for width
x_grad = np.linspace(0, 1, image1.shape[1])  # Horizontal gradient (west-east direction)
y_grad = np.linspace(0, 1, image2.shape[0])  # Horizontal gradient (west-east direction)
# Repeat the gradient along the height to match the dimensions of the image
x_weight, y_weight = np.meshgrid(x_grad, y_grad)
# Note: this is a quadratic function, not a linear function. 
weight1 = x_weight * y_weight 
weight2 = 1 - weight1  # Inverse gradient

# Create the blended image using the weight masks
blended_image = image1 * weight1[..., np.newaxis] + image2 * weight2[..., np.newaxis]

The code uses two 1D gradients, one for the x-axis and another for the y-axis, which are combined using np.meshgrid to create a 2D grid. This grid represents the weights for blending the images. The multiplication of the two gradients effectively creates a diagonal gradient because the combination of the horizontal and vertical components forms a weight map that increases from the top-left corner (0, 0) to the bottom-right corner (1, 1). The diagonal gradient means that the blending effect transitions smoothly along this diagonal axis.