File size: 2,816 Bytes
ad281af
 
 
 
d6469b6
ad281af
 
 
d6469b6
 
 
ad281af
d6469b6
ad281af
 
 
d6469b6
 
 
 
ad281af
d6469b6
 
 
 
ad281af
d6469b6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ad281af
d6469b6
 
 
 
 
 
 
 
ad281af
d6469b6
 
 
ad281af
d6469b6
 
 
ad281af
 
 
d6469b6
 
 
 
ad281af
 
 
 
 
d6469b6
ad281af
 
d6469b6
ad281af
d6469b6
ad281af
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
import gradio as gr
import numpy as np
from PIL import Image

def create_color_ghost(cover, reveal):
    if cover is None or reveal is None:
        return None

    # 1. Ensure images are RGB
    img1_raw = cover.convert('RGB')
    img2_raw = reveal.convert('RGB')

    # 2. Resize reveal to match cover
    if img1_raw.size != img2_raw.size:
        img2_raw = img2_raw.resize(img1_raw.size, Image.LANCZOS)

    # 3. Convert to Float Arrays (0.0 - 1.0)
    # This time we keep the 3 color channels (Height, Width, 3)
    rgb1 = np.array(img1_raw, dtype=float) / 255.0
    rgb2 = np.array(img2_raw, dtype=float) / 255.0

    # 4. Create Grayscale copies just to calculate the Alpha Channel
    # We use the luminance of the pixels to decide transparency
    gray1 = np.mean(rgb1, axis=2) 
    gray2 = np.mean(rgb2, axis=2)

    # 5. Squash the contrast (Math Trick)
    # We squash the target values so they don't clip when we combine them
    rgb1 = rgb1 * 0.5
    rgb2 = 0.5 + (rgb2 * 0.5)
    
    # We also squash the grayscale versions for the Alpha calculation
    gray1 = gray1 * 0.5
    gray2 = 0.5 + (gray2 * 0.5)

    # 6. Calculate the Alpha Channel based on Luminance
    # Formula: Alpha = 1 - Light_Image + Dark_Image
    alpha = 1.0 - gray2 + gray1
    
    # Clip alpha to avoid errors
    alpha = np.clip(alpha, 0.001, 0.999)

    # 7. Reconstruct the Color Output
    # We define the pixel color based on the "Dark Background" version (rgb1)
    # P = Target / Alpha
    
    # We have to reshape alpha to (H, W, 1) so we can divide the RGB array (H, W, 3) by it
    alpha_3d = alpha[:, :, np.newaxis]
    
    out_rgb = np.divide(rgb1, alpha_3d, out=np.zeros_like(rgb1), where=alpha_3d > 0.001)

    # 8. Convert back to 0-255 Integers
    final_rgb_int = (out_rgb * 255).clip(0, 255).astype(np.uint8)
    final_alpha_int = (alpha * 255).clip(0, 255).astype(np.uint8)

    # 9. Stack them into an RGBA Image
    # Concatenate the Color channels with the Alpha channel
    rgba_data = np.dstack((final_rgb_int, final_alpha_int))

    return Image.fromarray(rgba_data, 'RGBA')

# --- Gradio UI ---
with gr.Blocks(title="Color Ghost Generator") as demo:
    gr.Markdown("# 👻 Color Ghost Image Generator")
    gr.Markdown("Create a magic image that changes when clicked (Supports Color).")
    
    with gr.Row():
        with gr.Column():
            img_dark = gr.Image(type="pil", label="Image seen in DARK Mode (Cover)")
            img_light = gr.Image(type="pil", label="Image seen in LIGHT Mode (Reveal)")
            btn = gr.Button("Generate Ghost", variant="primary")
        
        with gr.Column():
            output_img = gr.Image(label="Result (Download to test)", type="pil")

    btn.click(fn=create_color_ghost, inputs=[img_dark, img_light], outputs=output_img)

demo.launch()