import cv2 as cv
import numpy as np

# 가우시안 피라미드 생성 함수
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

# 라플라시안 피라미드 생성 함수
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()  # 가장 작은 이미지부터 큰 순서로 정렬
    return LP

# 두 개의 라플라시안 피라미드를 블렌딩하는 함수
def blend_pyramids(LPA, LPB, mask_pyramid):
    blended_pyramid = []
    for LA, LB, mask in zip(LPA, LPB, mask_pyramid):
        # 마스크는 적용되는 이미지와 동일한 채널 수를 가져야 함
        mask_3channel = np.expand_dims(mask, axis=-1)
        blended = LA * mask_3channel + LB * (1.0 - mask_3channel)
        blended_pyramid.append(blended)
    return blended_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

# 이미지를 중앙에 패딩하는 함수
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

# 원본 이미지와 대상 이미지를 로드
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)

# 소스 이미지 중앙에 대략적으로 위치하는 눈 부분을 가정함
source_center = (source_image.shape[1] // 2, source_image.shape[0] // 2)
eye_size = (source_image.shape[1] // 4, source_image.shape[0] // 4)

# 눈 부분의 경계 상자 계산
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]

# 소스 이미지에서 눈 부분을 크롭함
eye = source_image[eye_bbox[1]:eye_bbox[3], eye_bbox[0]:eye_bbox[2]]

# 눈 이미지를 대상 이미지와 같은 크기로 패딩
eye_padded = pad_to_center(eye, target_image.shape)

# 대상 이미지 중앙에 눈이 위치할 것으로 계산
target_center_x = target_image.shape[1] // 2
target_center_y = target_image.shape[0] // 2

# 눈 부분에 해당하는 마스크 생성
mask = np.zeros(target_image.shape[:2], dtype=np.float32)
cv.ellipse(mask, (target_center_x, target_center_y),
           (eye_size[0]//2, eye_size[1]//2), 0, 0, 360, 1, -1)

# 마스크를 대상 이미지 크기에 맞게 패딩
mask_padded = pad_to_center(mask, target_image.shape[:2])

# 패딩된 마스크에 대한 가우시안 피라미드 생성
mask_pyramid = generate_gaussian_pyramid(mask_padded.astype(np.float32), 3)

# 눈과 대상 이미지에 대한 가우시안 및 라플라시안 피라미드 생성
levels = 3  # 피라미드의 레벨 수 정의
GP_eye_padded = generate_gaussian_pyramid(eye_padded, levels)
LP_eye_padded = generate_laplacian_pyramid(GP_eye_padded)
GP_target = generate_gaussian_pyramid(target_image, levels)
LP_target = generate_laplacian_pyramid(GP_target)

# 라플라시안 피라미드 레벨의 크기에 맞게 마스크 피라미드 조정
mask_pyramid = [mask_padded.astype(np.float32)]
for i in range(1, levels):
    size = (LP_eye_padded[i].shape[1], LP_eye_padded[i].shape[0])
    mask_down = cv.pyrDown(mask_pyramid[i-1], dstsize=size)
    mask_pyramid.append(mask_down)

# 패딩된 피라미드를 블렌딩
blended_pyramid = blend_pyramids(LP_eye_padded, LP_target, mask_pyramid)

# 블렌딩된 피라미드에서 이미지 재구성
result = reconstruct_from_laplacian_pyramid(blended_pyramid)

# Gaussian blur 
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

blurred_result_image = blur_edge_of_eye(result, eye_bbox, blur_radius=3)

# 이미지 저장
final_result_path = 'blended_image.jpg'
cv.imwrite(final_result_path, blurred_result_image)