| """ |
| Template: showcase_arabic |
| تمبلت عرض المنتجات بالعربي |
| خلفية بنفسجية + نصوص عربية + badge ذهبي |
| """ |
|
|
| from PIL import Image, ImageDraw |
| from .base import BaseTemplate, RenderRequest |
|
|
|
|
| class ShowcaseArabic(BaseTemplate): |
|
|
| NAME = "showcase_arabic" |
| DESCRIPTION = "تمبلت عرض منتج احترافي — خلفية بنفسجية وبادج ذهبي" |
| AUTHOR = "your_name" |
|
|
| |
| DEFAULT_BG_LEFT = (98, 70, 180) |
| DEFAULT_BG_RIGHT = (15, 15, 45) |
|
|
| |
| def _create_bg(self, w, h, cl, cr): |
| img = Image.new('RGBA', (w, h)) |
| d = ImageDraw.Draw(img) |
| for x in range(w): |
| if x < w * 0.6: |
| r2 = x / (w * 0.6) |
| c = ( |
| int(cl[0] * (1 - r2 * 0.3)), |
| int(cl[1] * (1 - r2 * 0.3)), |
| int(cl[2] * (1 - r2 * 0.1)), |
| 255 |
| ) |
| else: |
| r2 = (x - w * 0.6) / (w * 0.4) |
| c = ( |
| int(cl[0] * 0.7 * (1-r2) + cr[0] * r2), |
| int(cl[1] * 0.7 * (1-r2) + cr[1] * r2), |
| int(cl[2] * 0.9 * (1-r2) + cr[2] * r2), |
| 255 |
| ) |
| d.line([(x, 0), (x, h)], fill=c) |
| return img |
|
|
| def _draw_badge(self, draw, cx, cy, r, lines, font): |
| if r < 5: |
| return |
| draw.ellipse([cx-r-8, cy-r-8, cx+r+8, cy+r+8], fill=(160, 130, 10)) |
| draw.ellipse([cx-r, cy-r, cx+r, cy+r ], fill=(200, 160, 20)) |
| total_h = sum(font.getbbox(l)[3] for l in lines) + (len(lines)-1) * 4 |
| y = cy - total_h // 2 |
| for line in lines: |
| b = font.getbbox(line) |
| draw.text((cx - (b[2]-b[0])//2, y), line, font=font, fill=(255, 255, 255)) |
| y += b[3] + 4 |
|
|
| |
| def make_frame(self, t, req: RenderRequest, product_img, logo_img): |
| W, H, D = req.width, req.height, req.duration |
|
|
| cl = self.parse_color(req.bg_left, self.DEFAULT_BG_LEFT) |
| cr = self.parse_color(req.bg_right, self.DEFAULT_BG_RIGHT) |
|
|
| |
| frame = self._create_bg(W, H, cl, cr) |
|
|
| |
| ov = Image.new('RGBA', (W, H), (0, 0, 0, 0)) |
| od = ImageDraw.Draw(ov) |
| od.ellipse([W*.45, -H*.3, W*1.2, H*1.3], fill=(30, 20, 70, 80)) |
| od.ellipse([W*.55, H*.3, W*1.1, H*1.1], fill=(20, 15, 50, 60)) |
| frame = Image.alpha_composite(frame, ov) |
|
|
| ft = self.load_font(72) |
| fd = self.load_font(48) |
| fc = self.load_font(30) |
|
|
| |
| ip = min(1.0, t / (D * 0.4)) |
| io = int((1 - self.ease_out(ip)) * -W * 0.5) |
| if product_img: |
| pw = int(W * 0.42) |
| ph = int(product_img.height * pw / product_img.width) |
| rs = product_img.resize((pw, ph), Image.LANCZOS) |
| px = int(W * 0.02) + io |
| py = (H - ph) // 2 |
| frame.paste(rs, (px, py), rs if rs.mode == 'RGBA' else None) |
|
|
| tl = Image.new('RGBA', (W, H), (0, 0, 0, 0)) |
| td = ImageDraw.Draw(tl) |
|
|
| |
| tp = min(1.0, max(0.0, (t - D*.2) / (D*.4))) |
| to = int((1 - self.ease_out(tp)) * -H * 0.3) |
| ta = int(self.ease_out(tp) * 255) |
|
|
| RX = int(W * 0.95) |
| ty = int(H * 0.15) + to |
| for i, line in enumerate(req.title.replace('\\n', '\n').split('\n')): |
| b = ft.getbbox(line) |
| tw = b[2] - b[0] |
| td.text((RX - tw, ty + i*90), line, font=ft, fill=(255, 255, 255, ta)) |
|
|
| if req.discount: |
| b = fd.getbbox(req.discount) |
| dw = b[2] - b[0] |
| td.text((RX - dw, int(H*.55) + to), req.discount, |
| font=fd, fill=(220, 160, 40, ta)) |
|
|
| |
| cp = min(1.0, max(0.0, (t - D*.5) / (D*.3))) |
| ca = int(self.ease_out(cp) * 255) |
| cy2 = int(H * 0.68) |
| for i, txt in enumerate([req.phone, req.website]): |
| if not txt: |
| continue |
| b = fc.getbbox(txt) |
| tw = b[2] - b[0] |
| td.text((RX - tw, cy2 + i*42), txt, |
| font=fc, fill=(200-i*20, 200-i*20, 200-i*20, ca)) |
|
|
| frame = Image.alpha_composite(frame, tl) |
|
|
| |
| if req.badge: |
| bp = min(1.0, max(0.0, (t - D*.1) / (D*.3))) |
| bs = self.ease_out(bp) |
| if bs > 0.05: |
| bl = Image.new('RGBA', (W, H), (0, 0, 0, 0)) |
| bd = ImageDraw.Draw(bl) |
| self._draw_badge(bd, |
| int(W*.08), int(H*.18), |
| int(70 * bs), |
| req.badge.replace('\\n', '\n').split('\n'), |
| self.load_font(int(32 * bs))) |
| frame = Image.alpha_composite(frame, bl) |
|
|
| return frame.convert('RGB') |
|
|