Zirnavis100 / styles /ai_dynamic.py
Transfer Bot
Moved to Hugging Face automatically
8e64a96
Raw
History Blame Contribute Delete
9.38 kB
from PIL import Image, ImageDraw, ImageFont, ImageFilter
import math
# --- استایل «طراحی هوشمند» (AI Dynamic) ---
# هوش مصنوعی (Gemini) هرگز کد اجرا نمی‌کند؛ فقط یک شیء طراحی محدود (ai_design) با
# چند فیلد از پیش تعریف‌شده برمی‌گرداند (entry, exit, timing, stagger, glow, رنگ‌ها).
# این استایل دیگر هیچ کادر/باکس/پس‌زمینه‌ای دور متن یا دور کلمات نمی‌سازد؛ تمرکز فقط
# روی حرکت ورود/خروج کلمات و رنگ متن است (و در صورت درخواست، درخشش نور اطراف متن).
# مقادیر آن قبلاً در app.py (_clamp_ai_design) کاملاً اعتبارسنجی و clamp شده‌اند.
# این فایل صرفاً همان پارامترها را می‌خواند و واقعاً فریم را رسم می‌کند؛ به همین دلیل
# خروجی همیشه امن، پایدار و بدون امکان کرش است.
config = {
"ids": ["ai_dynamic"],
"name": "طراحی هوشمند (AI)",
"panels": ["font", "size", "position"],
"default_font": "vazir"
}
frontend_template = """
<span class="ai-dynamic-word" style="
display: inline-block; vertical-align: middle; margin: 0 4px;
color: {{COLOR}} !important; opacity: {{OPACITY}}; font-family: inherit;
">{{WORD}}</span>
"""
DEFAULT_DESIGN = {
"entry": "fall",
"exit": "fade",
"timing": "smooth",
"stagger": 0.04,
"glow": "none",
"primaryColor": "#FFFFFF",
"activeColor": "#FFD700",
"glowColor": "#7C4DFF",
"font": "vazir",
"fontSize": 60,
}
def _get_design(style_config):
raw = getattr(style_config, 'ai_design', None) or {}
if not isinstance(raw, dict):
raw = {}
d = dict(DEFAULT_DESIGN)
for k in DEFAULT_DESIGN:
if k in raw and raw[k] is not None:
d[k] = raw[k]
# سازگاری با طراحی‌های قدیمی‌تر که ممکن است هنوز shapeColor داشته باشند
if "glowColor" not in raw and "shapeColor" in raw:
d["glowColor"] = raw["shapeColor"]
return d
def _ease(name, p):
p = max(0.0, min(1.0, p))
if name == "linear":
return p
if name == "punch":
# شروع تند و توقف نرم با کمی سرریز (overshoot) خفیف
return 1 - pow(1 - p, 3)
if name == "elastic":
if p <= 0 or p >= 1:
return p
return pow(2, -10 * p) * math.sin((p * 10 - 0.75) * (2 * math.pi) / 3) + 1
# smooth (پیش‌فرض)
return 1 - (1 - p) * (1 - p)
def draw_frame(draw, img, width, height, style_config, lines, line_metrics, active_idx, font, color_parser, word_infos=None):
is_rtl = getattr(style_config, 'text_direction', 'rtl') != 'ltr'
design = _get_design(style_config)
primary_color = color_parser(design["primaryColor"], (255, 255, 255, 255))
active_color = color_parser(design["activeColor"], (255, 215, 0, 255))
glow_color = color_parser(design["glowColor"], (124, 77, 255, 255))
line_height_px = int(style_config.fontSize * 1.5)
total_block_height = len(lines) * line_height_px
bottom_reference = height - style_config.marginV
start_y_of_block = bottom_reference - total_block_height
current_render_time = getattr(style_config, 'current_render_time', 0.0)
text_start_y = start_y_of_block
vertical_corr = int(style_config.fontSize * 0.1)
current_line_y = text_start_y - vertical_corr
space_w = draw.textlength(" ", font=font)
global_word_counter = 0
entry = design["entry"]
exit_type = design["exit"]
timing = design["timing"]
stagger = max(0.0, min(0.12, float(design["stagger"]) if isinstance(design["stagger"], (int, float)) else 0.04))
glow = design["glow"]
ANIM_DUR = 0.3
EXIT_DUR = 0.3
glow_blur_radius = {"none": 0, "soft": 4, "strong": 8}.get(glow, 0)
glow_intensity = {"none": 0, "soft": 160, "strong": 230}.get(glow, 0)
for line_idx, metrics in enumerate(line_metrics):
cursor_x = (width + metrics["width"]) / 2 + (style_config.x or 0) if is_rtl else (width - metrics["width"]) / 2 + (style_config.x or 0)
text_y_pos = current_line_y
for w_i, word in enumerate(metrics["words"]):
w_len = metrics["word_widths"][w_i]
word_x = (cursor_x - w_len) if is_rtl else cursor_x
should_draw = False
dx, dy, alpha_factor = 0.0, 0.0, 1.0
is_active_word = False
w_info = None
extra_delay = stagger * w_i # تاخیر آبشاری بین کلمات پشت‌سرهم داخل یک خط
if word_infos and global_word_counter < len(word_infos):
w_info = word_infos[global_word_counter]
if current_render_time >= w_info.start:
should_draw = True
time_passed = max(0.0, (current_render_time - w_info.start) - extra_delay)
progress = min(1.0, time_passed / ANIM_DUR)
eased = _ease(timing, progress)
if entry == "fall":
dy = -70 * (1 - eased)
elif entry == "rise":
dy = 65 * (1 - eased)
elif entry == "pop":
dy = -18 * (1 - eased)
elif entry == "slide_left":
dx = 85 * (1 - eased)
elif entry == "slide_right":
dx = -85 * (1 - eased)
# "fade" فقط آلفا دارد
alpha_factor = max(0.0, min(1.0, eased))
is_active_word = current_render_time >= w_info.start and current_render_time < w_info.end
# --- افکت خروج بعد از پایان گفتار این کلمه ---
if current_render_time >= w_info.end and exit_type != "none":
time_after = current_render_time - w_info.end
exit_progress = min(1.0, time_after / EXIT_DUR)
exit_eased = _ease("smooth", exit_progress)
if exit_type == "fade":
alpha_factor = max(0.4, 1.0 - exit_eased * 0.6)
elif exit_type == "shrink":
alpha_factor = max(0.35, 1.0 - exit_eased * 0.65)
dy += 6 * exit_eased
elif exit_type == "fall":
dy += 25 * exit_eased
alpha_factor = max(0.0, 1.0 - exit_eased)
elif exit_type == "rise":
dy -= 25 * exit_eased
alpha_factor = max(0.0, 1.0 - exit_eased)
else:
should_draw = False
else:
if active_idx != -1:
should_draw = (global_word_counter <= active_idx)
is_active_word = (global_word_counter == active_idx)
else:
should_draw = False
if should_draw and alpha_factor > 0.01:
this_color = primary_color
if getattr(style_config, 'useActiveColor', True) and is_active_word:
this_color = active_color
if word_infos and global_word_counter < len(word_infos):
w_obj = word_infos[global_word_counter]
if hasattr(w_obj, 'color') and w_obj.color:
this_color = color_parser(w_obj.color, this_color)
final_alpha = int(this_color[3] * alpha_factor)
final_color = (this_color[0], this_color[1], this_color[2], final_alpha)
draw_x = word_x + dx
draw_y = text_y_pos + dy
# --- درخشش نور نرم اطراف متن (بلور واقعی)؛ دیگر هیچ کادر/باکسی رسم نمی‌شود ---
if glow_blur_radius > 0:
pad = glow_blur_radius * 3 + 8
layer_w = int(w_len) + pad * 2
layer_h = int(style_config.fontSize * 1.3) + pad * 2
if layer_w > 0 and layer_h > 0:
glow_layer = Image.new('RGBA', (layer_w, layer_h), (0, 0, 0, 0))
glow_draw = ImageDraw.Draw(glow_layer)
glow_a = int(glow_intensity * alpha_factor)
glow_draw.text((pad, pad), word, font=font, fill=(*glow_color[:3], glow_a),
direction=getattr(style_config, 'text_direction', 'rtl'), language=getattr(style_config, 'lang_code', 'fa'))
blurred = glow_layer.filter(ImageFilter.GaussianBlur(glow_blur_radius))
img.paste(blurred, (int(draw_x - pad), int(draw_y - pad)), blurred)
draw.text((draw_x, draw_y), word, font=font, fill=final_color,
direction=getattr(style_config, 'text_direction', 'rtl'), language=getattr(style_config, 'lang_code', 'fa'))
cursor_x += (-(w_len + space_w) if is_rtl else (w_len + space_w))
global_word_counter += 1
current_line_y += line_height_px