Zirnavis / styles /box.py
Transfer Bot
Moved to Hugging Face automatically
34d3ca9
Raw
History Blame Contribute Delete
13.8 kB
from PIL import Image, ImageDraw, ImageFont, ImageFilter
config = {
"ids": ["instagram_box", "alpha_gradient"],
"name": "Box Styles",
"panels": ["font", "size", "radius", "padding"],
"default_font": "vazir"
}
frontend_template = """
<span style="display: inline-block; vertical-align: middle; margin: -{{PAD_Y}}px calc(5px - {{PAD_X}}px); padding: {{PAD_Y}}px {{PAD_X}}px; background: {{BG_COLOR}}; color: {{TEXT_COLOR}} !important; border-radius: {{RADIUS}}px; position: relative; z-index: {{Z_INDEX}}; white-space: nowrap; transition: all 0.1s;">{{WORD}}</span>
"""
# تابع کمکی برای رسم کادرهای با گوشه گرد با کیفیت بسیار بالا و لبه‌های کاملاً نرم (Anti-aliased)
def draw_aa_rounded_rectangle(img, xy, radius, fill):
x1, y1, x2, y2 = xy
w = int(x2 - x1)
h = int(y2 - y1)
if w <= 0 or h <= 0:
return
# رسم شکل با رزولوشن ۴ برابر برای انجام آنتی‌الیاسینگ (Supersampling)
scale = 4
s_img = Image.new('RGBA', (w * scale, h * scale), fill)
mask_large = Image.new('L', (w * scale, h * scale), 0)
m_draw = ImageDraw.Draw(mask_large)
m_draw.rounded_rectangle([0, 0, w * scale, h * scale], radius=radius * scale, fill=255)
# سازگاری با نسخه‌های مختلف کتابخانه Pillow برای فیلتر تصویری LANCZOS
try:
resampling_filter = Image.Resampling.LANCZOS
except AttributeError:
resampling_filter = Image.LANCZOS
mask_aa = mask_large.resize((w, h), resampling_filter)
aa_img = s_img.resize((w, h), resampling_filter)
# چسباندن تصویر هموار شده روی بوم اصلی
img.paste(aa_img, (int(x1), int(y1)), mask_aa)
def draw_frame(draw, img, width, height, style_config, lines, line_metrics, active_idx, font, color_parser, word_infos=None):
style_name = style_config.name
is_rtl = getattr(style_config, 'text_direction', 'rtl') != 'ltr'
line_height_px = int(style_config.fontSize * 1.1) + int(style_config.paddingY * 2.2)
total_block_height = len(lines) * line_height_px
bottom_reference = height - style_config.marginV
start_y_of_block = bottom_reference - total_block_height
pad_x = style_config.paddingX
pad_y = style_config.paddingY
radius = style_config.radius
if style_name == "instagram_box":
pad_y = max(5, pad_y)
current_line_y = start_y_of_block
space_w = draw.textlength(" ", font=font)
VERTICAL_CORRECTION = int(style_config.fontSize * 0.22)
INSTA_CENTERING_OFFSET = 8 if style_name == "instagram_box" else 0
# محاسبه متریک‌های استاندارد فونت بر اساس کلمه مرجع جهت تراز یکدست عمودی تمام کلمات
ref_word = "سلام"
try:
ref_bbox = draw.textbbox((0, 0), ref_word, font=font, direction=getattr(style_config, 'text_direction', 'rtl'), language=getattr(style_config, 'lang_code', 'fa'))
standard_text_visual_center_y = (ref_bbox[1] + ref_bbox[3]) / 2.0
standard_text_offset_y = ref_bbox[1]
standard_text_h = ref_bbox[3] - ref_bbox[1]
except:
standard_text_visual_center_y = style_config.fontSize / 2.0
standard_text_offset_y = 0
standard_text_h = style_config.fontSize
global_word_counter = 0
words_to_draw = []
temp_y = current_line_y
for line_idx, metrics in enumerate(line_metrics):
line_start_idx = sum(len(m["words"]) for m in line_metrics[:line_idx])
is_active_on_line = (active_idx >= line_start_idx and active_idx < line_start_idx + len(metrics["words"]))
dynamic_line_width = metrics["width"] + (pad_x * 2 if is_active_on_line else 0)
if is_rtl:
cursor_x = (width + dynamic_line_width) / 2 + style_config.x
else:
cursor_x = (width - dynamic_line_width) / 2 + style_config.x
# مرکز عمودی دقیق و هندسی این خط فعلی
line_center_y = temp_y + (line_height_px / 2.0)
for w_i, word in enumerate(metrics["words"]):
g_idx = line_start_idx + w_i
w_len = metrics["word_widths"][w_i]
# استفاده از مقدار ثابت برای جلوگیری از نوسان عمودی کلمات خط
text_y_pos = line_center_y - standard_text_visual_center_y
current_word_padding = pad_x if g_idx == active_idx else 0
if is_rtl:
word_x = cursor_x - w_len - current_word_padding
else:
word_x = cursor_x + current_word_padding
words_to_draw.append({
"text": word,
"x": word_x,
"y": text_y_pos,
"width": w_len,
"global_idx": global_word_counter,
"line_center_y": line_center_y
})
if is_rtl:
cursor_x -= (w_len + space_w + (current_word_padding * 2))
else:
cursor_x += (w_len + space_w + (current_word_padding * 2))
global_word_counter += 1
temp_y += line_height_px
# --- رسم کادر اصلی سفید پشت کل متن برای استایل اینستاگرام ---
if style_name == "instagram_box":
max_w = 0
for m in line_metrics:
if m["width"] > max_w: max_w = m["width"]
cx = width / 2 + style_config.x
# محاسبه مختصات باکس کلی
main_box_x1 = cx - (max_w / 2) - pad_x - 10 # کمی فضای بیشتر (10px)
main_box_x2 = cx + (max_w / 2) + pad_x + 10
# محاسبه دقیق سقف و کف بر اساس آرایه واقعی کلمات ذخیره شده برای تقارن مطلق پیکسلی
first_line_y = words_to_draw[0]["y"] if words_to_draw else (start_y_of_block + pad_y)
last_line_y = words_to_draw[-1]["y"] if words_to_draw else (start_y_of_block + pad_y)
main_box_y1 = first_line_y - int(pad_y * 1.3) + VERTICAL_CORRECTION + INSTA_CENTERING_OFFSET - 8
main_box_y2 = last_line_y + style_config.fontSize + int(pad_y * 1.3) + VERTICAL_CORRECTION + INSTA_CENTERING_OFFSET + 8
main_box_hex = style_config.styleBgColors.get('instagram_box_main_box', '#ffffff')
main_box_col = color_parser(main_box_hex, (255, 255, 255, 255))
# رسم کادر اصلی اینستاگرام با کیفیت بالا و لبه‌های نرم
draw_aa_rounded_rectangle(img, [main_box_x1, main_box_y1, main_box_x2, main_box_y2], radius, main_box_col)
# --- رسم کادرها (Highlight) ---
for item in words_to_draw:
is_active = (item["global_idx"] == active_idx)
if is_active:
box_color = (0,0,0,0)
if style_name == "instagram_box":
bg_hex = style_config.styleBgColors.get('instagram_box', '#1a1a1a')
box_color = color_parser(bg_hex, (26, 26, 26, 255))
elif style_name == "alpha_gradient":
box_color = (124, 77, 255, 255)
rect_y1 = item["y"] - int(pad_y * 1.3) + VERTICAL_CORRECTION + INSTA_CENTERING_OFFSET
rect_y2 = item["y"] + style_config.fontSize + int(pad_y * 1.3) + VERTICAL_CORRECTION + INSTA_CENTERING_OFFSET
bx1, by1 = int(item["x"] - pad_x), int(rect_y1)
bx2, by2 = int(item["x"] + item["width"] + pad_x), int(rect_y2)
# اگر استایل آلفا بود، گرادینت بساز
if style_name == "alpha_gradient":
rw, rh = bx2 - bx1, by2 - by1
grad_img = Image.new('RGBA', (rw, rh), (0,0,0,0))
g_draw = ImageDraw.Draw(grad_img)
base_hex = style_config.styleBgColors.get('alpha_gradient', '#7c4dff')
r1, g1, b1, a1 = color_parser(base_hex, (124, 77, 255, 255))
factor = 0.35
r2 = int(r1 + (255 - r1) * factor)
g2 = int(g1 + (255 - g1) * factor)
b2 = int(b1 + (255 - b1) * factor)
c1 = (r1, g1, b1)
c2 = (r2, g2, b2)
# رسم سایه (Glow) نرم پشت کادر
shadow_pad = 20
shadow_layer = Image.new('RGBA', (rw + shadow_pad*2, rh + shadow_pad*2), (0,0,0,0))
s_draw = ImageDraw.Draw(shadow_layer)
s_draw.rounded_rectangle([shadow_pad, shadow_pad, shadow_pad+rw, shadow_pad+rh], radius=radius, fill=(r1, g1, b1, 100))
shadow_layer = shadow_layer.filter(ImageFilter.GaussianBlur(4))
img.paste(shadow_layer, (bx1 - shadow_pad, by1 - shadow_pad + 4), shadow_layer)
# پر کردن گرادینت روی بوم موقت
for y in range(rh):
for x in range(rw):
ratio = (x + y) / (rw + rh)
r = int(c1[0] * (1-ratio) + c2[0] * ratio)
g = int(c1[1] * (1-ratio) + c2[1] * ratio)
b = int(c1[2] * (1-ratio) + c2[2] * ratio)
g_draw.point((x,y), fill=(r,g,b,255))
# ساخت ماسک با لبه‌های بسیار هموار و بدون پله‌پله شدن گوشه‌ها (Anti-aliased Mask)
scale = 4
mask_large = Image.new('L', (rw * scale, rh * scale), 0)
ImageDraw.Draw(mask_large).rounded_rectangle([0, 0, rw * scale, rh * scale], radius=radius * scale, fill=255)
try:
resampling_filter = Image.Resampling.LANCZOS
except AttributeError:
resampling_filter = Image.LANCZOS
mask = mask_large.resize((rw, rh), resampling_filter)
img.paste(grad_img, (bx1, by1), mask)
else:
# رسم کادر فعال استایل اینستاگرام با کیفیت بالا و لبه‌های نرم
draw_aa_rounded_rectangle(img, [bx1, by1, bx2, by2], radius, box_color)
# --- رسم متن ---
for item in words_to_draw:
is_active = (item["global_idx"] == active_idx) and getattr(style_config, 'useActiveColor', True)
text_color = (255, 255, 255, 255)
if style_name == "instagram_box":
custom_hex = style_config.styleColors.get('instagram_box', '#000000')
u_r, u_g, u_b, u_a = color_parser(custom_hex, (0, 0, 0, 255))
if is_active:
active_hex = style_config.styleActiveColors.get('instagram_box', '#ffffff')
text_color = color_parser(active_hex, (255, 255, 255, 255))
else:
is_past = item["global_idx"] < active_idx
fade_surr = getattr(style_config, 'fadeSurrounding', False)
if is_past and fade_surr:
factor = (u_a / 255.0 * 0.35)
else:
factor = (u_a / 255.0) if is_past else (u_a / 255.0 * 0.35)
f_r = int((u_r * factor) + (255 * (1 - factor)))
f_g = int((u_g * factor) + (255 * (1 - factor)))
f_b = int((u_b * factor) + (255 * (1 - factor)))
text_color = (f_r, f_g, f_b, 255)
elif style_name == "alpha_gradient":
base_alpha_color = (255, 255, 255, 255)
custom_color = style_config.styleColors.get('alpha_gradient')
if custom_color:
base_alpha_color = color_parser(custom_color, base_alpha_color)
r, g, b, a = base_alpha_color
if is_active:
active_hex = style_config.styleActiveColors.get('alpha_gradient', '#ffffff')
text_color = color_parser(active_hex, (255, 255, 255, 255))
elif item["global_idx"] < active_idx:
if getattr(style_config, 'fadeSurrounding', False):
text_color = (r, g, b, int(a * 0.35))
else:
text_color = (r, g, b, a)
else:
future_alpha = int(a * 0.35)
text_color = (r, g, b, future_alpha)
if word_infos and item["global_idx"] < len(word_infos):
w_obj = word_infos[item["global_idx"]]
if hasattr(w_obj, 'color') and w_obj.color:
text_color = color_parser(w_obj.color, text_color)
# محاسبه مرکز هندسی کادر رسم شده برای این کلمه جهت تراز بی‌نقص
rect_y1 = item["y"] - int(pad_y * 1.3) + VERTICAL_CORRECTION + INSTA_CENTERING_OFFSET
rect_y2 = item["y"] + style_config.fontSize + int(pad_y * 1.3) + VERTICAL_CORRECTION + INSTA_CENTERING_OFFSET
rect_center_y = (rect_y1 + rect_y2) / 2.0
# تراز عمودی کاملاً هم‌سطح بر اساس مقادیر ثابت مرجع به جای مقادیر پویای کلمه‌ای
final_text_y = rect_center_y - standard_text_offset_y - (standard_text_h / 2.0)
draw.text((item["x"], final_text_y), item["text"], font=font, fill=text_color, direction=getattr(style_config, 'text_direction', 'rtl'), language=getattr(style_config, 'lang_code', 'fa'))