deep-glow-api / engine.py
earncoding's picture
Create engine.py
b429812 verified
Raw
History Blame Contribute Delete
7.8 kB
import numpy as np
from rembg import remove, new_session
from PIL import Image, ImageFilter, ImageOps, ImageChops, ImageColor, ImageEnhance
from moviepy.editor import ImageClip, CompositeVideoClip, ColorClip
import math
# Load AI Model
session = new_session("u2net")
class IconAnimator:
def __init__(self, input_path, output_path):
self.input_path = input_path
self.output_path = output_path
def hex_to_rgb(self, hex_color):
try:
return ImageColor.getcolor(hex_color, "RGB")
except:
return (255, 255, 255)
def get_edges(self, img, thickness=2):
"""Extracts the outline of the shape to create a 'Neon Tube' effect"""
# Get alpha channel
a = img.split()[-1]
# Dilate (expand) and Erode (shrink) to find edges
# Simple trick: Edge = Alpha - Eroded_Alpha
eroded = a.filter(ImageFilter.MinFilter(3))
edges = ImageChops.difference(a, eroded)
# Boost visibility
edges = edges.point(lambda p: 255 if p > 50 else 0)
return edges
def process_image(self, remove_bg=True, keep_original=False, icon_color_hex="#FFFFFF"):
with open(self.input_path, 'rb') as i:
input_data = i.read()
# 1. Background Removal
if remove_bg:
try:
# alpha_matting=False is faster and safer for icons
output_data = remove(input_data, session=session, alpha_matting=False)
from io import BytesIO
img = Image.open(BytesIO(output_data)).convert("RGBA")
except:
img = Image.open(self.input_path).convert("RGBA")
else:
img = Image.open(self.input_path).convert("RGBA")
# 2. Resize (High Quality)
if max(img.size) > 512:
ratio = 512 / max(img.size)
new_size = (int(img.width * ratio), int(img.height * ratio))
img = img.resize(new_size, Image.LANCZOS)
# 3. Create Visual Style
if keep_original:
# Just sharpen it a bit
img = ImageEnhance.Sharpness(img).enhance(1.2)
base_icon = img
else:
# Create a "Neon Tube" look
# A. Solid Fill
r, g, b, alpha = img.split()
target_rgb = self.hex_to_rgb(icon_color_hex)
solid_fill = Image.new("RGB", img.size, target_rgb)
fill_layer = Image.merge("RGBA", (*solid_fill.split(), alpha))
# B. White Edge (The Tube)
edge_mask = self.get_edges(img)
white_fill = Image.new("RGB", img.size, (255, 255, 255))
edge_layer = Image.merge("RGBA", (*white_fill.split(), edge_mask))
# Composite Edge on top of Fill
base_icon = Image.alpha_composite(fill_layer, edge_layer)
return base_icon
def create_deep_glow(self, img, color_hex):
"""
Creates a 'Deep Glow' by stacking 4 layers of varying blur radii.
This mimics professional compositing software.
"""
padding = 100
w, h = img.size
new_size = (w + padding*2, h + padding*2)
# 1. Prepare Base Glow Shape
canvas = Image.new("RGBA", new_size, (0, 0, 0, 0))
canvas.paste(img, (padding, padding), img)
# Colorize to Glow Color
r, g, b, a = canvas.split()
target_rgb = self.hex_to_rgb(color_hex)
glow_fill = Image.new("RGB", new_size, target_rgb)
glow_base = Image.merge("RGBA", (*glow_fill.split(), a))
# 2. Generate Stacked Blurs (The "Deep" Effect)
# Radii: Tight, Mid, Wide, Atmosphere
radii = [5, 15, 40, 80]
opacities = [0.8, 0.6, 0.4, 0.2]
composite_glow = Image.new("RGBA", new_size, (0, 0, 0, 0))
for radius, opacity in zip(radii, opacities):
# Blur
layer = glow_base.filter(ImageFilter.GaussianBlur(radius))
# Adjust Opacity
r, g, b, a = layer.split()
a = a.point(lambda p: int(p * opacity))
layer = Image.merge("RGBA", (r, g, b, a))
# Additive Blend (Screen)
composite_glow = ImageChops.add(composite_glow, layer)
return composite_glow, padding
def create_reflection(self, img):
reflection = ImageOps.flip(img)
width, height = img.size
# Non-linear fade for more realistic floor
gradient = Image.new('L', (1, height), color=0xFF)
for y in range(height):
# Exponential falloff
factor = (y / height) ** 0.5
alpha = 120 - int(factor * 255)
if alpha < 0: alpha = 0
gradient.putpixel((0, y), alpha)
alpha_mask = gradient.resize((width, height))
r, g, b, a = reflection.split()
a = ImageChops.multiply(a, alpha_mask)
reflection.putalpha(a)
return reflection
def generate_animation(self, icon_color="#FFFFFF", glow_color="#00E5FF", speed=2.0, intensity=1.2,
use_original_colors=False, add_reflection=True, remove_bg=True):
# 1. Prepare Base
base_pil = self.process_image(remove_bg, use_original_colors, icon_color)
w, h = base_pil.size
# 2. Dimensions & Reflection
reflection_h = 0
reflection_img = None
if add_reflection:
reflection_img = self.create_reflection(base_pil)
reflection_h = int(h * 0.6)
pad = 100
final_w = w + (pad * 2)
final_h = h + (pad * 2) + reflection_h
# 3. Create The Deep Glow Layer
glow_pil, _ = self.create_deep_glow(base_pil, glow_color)
# 4. Setup Video Clips
clips_to_render = []
# Floor (Invisible Transparency Fix)
bg_clip = ColorClip(size=(final_w, final_h), color=(0,0,0), duration=speed).set_opacity(0)
clips_to_render.append(bg_clip)
# A. The Glow Animation (Pulse)
glow_clip = ImageClip(np.array(glow_pil)).set_duration(speed).set_position((0, 0))
def pulse_glow(t):
# A heartbeat rhythm: fast up, slow down
# Sine wave shifted to be always positive
wave = math.sin(2 * math.pi * t / speed)
# Map -1..1 to 0..1
norm = (wave + 1) / 2
# Intensity Modulation: Base 0.5, Max varies by intensity
return 0.5 + (0.5 * norm * intensity)
glow_clip.mask = glow_clip.mask.fl(lambda gf, t: gf(t) * pulse_glow(t))
clips_to_render.append(glow_clip)
# B. The Reflection
if add_reflection and reflection_img:
ref_clip = ImageClip(np.array(reflection_img)).set_duration(speed)
ref_clip = ref_clip.set_position((pad, pad + h - 5))
clips_to_render.append(ref_clip)
# C. The Base Icon (Float Animation)
base_clip = ImageClip(np.array(base_pil)).set_duration(speed)
# Add a subtle "Hover" effect (bobbing up and down)
def hover_pos(t):
y_offset = 5 * math.sin(2 * math.pi * t / speed)
return (pad, pad + y_offset)
base_clip = base_clip.set_position(hover_pos)
clips_to_render.append(base_clip)
# 5. Composite
final = CompositeVideoClip(clips_to_render, size=(final_w, final_h), bg_color=None)
# 6. Export WEBM (High Quality)
final.write_videofile(
self.output_path,
fps=24,
codec='libvpx-vp9',
ffmpeg_params=['-pix_fmt', 'yuva420p', '-auto-alt-ref', '0'],
logger=None
)
return self.output_path