import cv2 as cv
import numpy as np

# Function to create a Gaussian pyramid
def generate_gaussian_pyramid(img, levels):
    GP = [img]
    for i in range(1, levels):
        img = cv.pyrDown(GP[i-1])
        GP.append(img)
    return GP

# Function to create a Laplacian pyramid
def generate_laplacian_pyramid(GP):
    levels = len(GP)
    LP = [GP[-1]]
    for i in range(levels - 1, 0, -1):
        size = (GP[i-1].shape[1], GP[i-1].shape[0])
        GE = cv.pyrUp(GP[i], dstsize=size)
        L = cv.subtract(GP[i-1], GE)
        LP.append(L)
    LP.reverse()  # reverse to have the smallest image at the end
    return LP

# Function to blend two Laplacian pyramids
def blend_pyramids(LPA, LPB, mask_pyramid):
    blended_pyramid = []
    for LA, LB, mask in zip(LPA, LPB, mask_pyramid):
        blended = LA * mask + LB * (1 - mask)
        blended_pyramid.append(blended)
    return blended_pyramid

# Function to reconstruct an image from a Laplacian pyramid
def reconstruct_from_laplacian_pyramid(LP):
    img = LP[-1]
    for i in range(len(LP) - 2, -1, -1):
        size = (LP[i].shape[1], LP[i].shape[0])
        img = cv.pyrUp(img, dstsize=size)
        img = cv.add(img, LP[i])
    return img

# Function to pad an image to the center of a target shape
def pad_to_center(image, target_shape):
    pad_y = (target_shape[0] - image.shape[0]) // 2
    pad_x = (target_shape[1] - image.shape[1]) // 2
    padded_image = cv.copyMakeBorder(image, pad_y, pad_y, pad_x, pad_x, cv.BORDER_CONSTANT, value=[0, 0, 0])
    return padded_image

# Function to blur the edge of the eye region
def blur_edge_of_eye(image, eye_bbox, blur_radius):
    top_left = (eye_bbox[0] - blur_radius, eye_bbox[1] - blur_radius)
    bottom_right = (eye_bbox[2] + blur_radius, eye_bbox[3] + blur_radius)
    eye_region = image[top_left[1]:bottom_right[1], top_left[0]:bottom_right[0]]
    blurred_eye_region = cv.GaussianBlur(eye_region, (0, 0), blur_radius)
    image[top_left[1]:bottom_right[1], top_left[0]:bottom_right[0]] = blurred_eye_region
    return image

# Load the source and target images
source_image_path = 'source image.png'
target_image_path = 'target image.jpg'
source_image = cv.imread(source_image_path)
target_image = cv.imread(target_image_path)

# Assume the eye region is located at the center of the source image
source_center = (source_image.shape[1] // 2, source_image.shape[0] // 2)
eye_size = (source_image.shape[1] // 4, source_image.shape[0] // 4)

# Calculate the bounding box for the eye region
eye_bbox = [source_center[0] - eye_size[0] // 2, source_center[1] - eye_size[1] // 2,
            source_center[0] + eye_size[0] // 2, source_center[1] + eye_size[1] // 2]

# Crop the eye region from the source image
eye = source_image[eye_bbox[1]:eye_bbox[3], eye_bbox[0]:eye_bbox[2]]

# Pad the eye image to the size of the target image
eye_padded = pad_to_center(eye, target_image.shape)

# Create a mask for the eye region
mask = np.zeros(target_image.shape[:2], dtype=np.float32)
cv.ellipse(mask, (source_center[0], source_center[1]),
           (eye_size[0]//2, eye_size[1]//2), 0, 0, 360, 1, -1)

# Pad the mask to the size of the target image
mask_padded = pad_to_center(mask, target_image.shape[:2])

# Define the number of pyramid levels
levels = 3

# Split the channels for the padded eye and target images
source_channels = cv.split(eye_padded)
target_channels = cv.split(target_image)

# Split the channels for the mask
mask_channels = [mask_padded.astype(np.float32) for _ in range(3)]  # Create a list of the mask for each channel

blended_channels = []

for i in range(3):  # Process each channel (B, G, R)
    # Create Gaussian and Laplacian pyramids for each channel of source and target images
    GP_eye_channel = generate_gaussian_pyramid(source_channels[i], levels)
    LP_eye_channel = generate_laplacian_pyramid(GP_eye_channel)
    GP_target_channel = generate_gaussian_pyramid(target_channels[i], levels)
    LP_target_channel = generate_laplacian_pyramid(GP_target_channel)
    
    # Create Gaussian pyramid for the mask channel
    mask_pyramid_channel = [mask_channels[i]]
    for j in range(1, levels):
        size = (LP_eye_channel[j].shape[1], LP_eye_channel[j].shape[0])
        mask_down = cv.pyrDown(mask_pyramid_channel[j-1], dstsize=size)
        mask_pyramid_channel.append(mask_down)
    
    # Blend the pyramids of the current channel
    blended_channel_pyramid = blend_pyramids(LP_eye_channel, LP_target_channel, mask_pyramid_channel)
    
    # Reconstruct the blended image from the pyramid for the current channel
    blended_channel = reconstruct_from_laplacian_pyramid(blended_channel_pyramid)
    blended_channels.append(blended_channel)

# Merge the channels back together
blended_image = cv.merge(blended_channels)

# Apply the blurring to the edges of the eye region in the blended image
blurred_blended_image = blur_edge_of_eye(blended_image, eye_bbox, blur_radius=2)

# Save the final blended image
final_result_path = 'blended image.jpg'
cv.imwrite(final_result_path, blurred_blended_image)

# If you want to display the image
# cv.imshow('Blended Image', blurred_blended_image)
# cv.waitKey(0)
# cv.destroyAllWindows()


