|
| 1 | +from pathlib import Path |
| 2 | + |
| 3 | +from PIL import Image, ImageDraw, ImageFont |
| 4 | + |
| 5 | +SIZE = 1024 |
| 6 | +SCALE = 3 |
| 7 | +canvas = Image.new("RGBA", (SIZE * SCALE, SIZE * SCALE), (0, 0, 0, 0)) |
| 8 | +draw = ImageDraw.Draw(canvas) |
| 9 | + |
| 10 | +font_dir = Path("C:/Windows/Fonts") |
| 11 | +serif_regular = font_dir / "georgia.ttf" |
| 12 | +serif_bold = font_dir / "georgiab.ttf" |
| 13 | +if not serif_regular.exists(): |
| 14 | + serif_regular = Path("/usr/share/fonts/truetype/dejavu/DejaVuSerif.ttf") |
| 15 | + serif_bold = Path("/usr/share/fonts/truetype/dejavu/DejaVuSerif-Bold.ttf") |
| 16 | + |
| 17 | + |
| 18 | +def cubic_bezier(start, control_a, control_b, end, steps=36): |
| 19 | + points = [] |
| 20 | + for index in range(steps): |
| 21 | + t = index / steps |
| 22 | + inverse = 1 - t |
| 23 | + x = ( |
| 24 | + inverse**3 * start[0] |
| 25 | + + 3 * inverse**2 * t * control_a[0] |
| 26 | + + 3 * inverse * t**2 * control_b[0] |
| 27 | + + t**3 * end[0] |
| 28 | + ) |
| 29 | + y = ( |
| 30 | + inverse**3 * start[1] |
| 31 | + + 3 * inverse**2 * t * control_a[1] |
| 32 | + + 3 * inverse * t**2 * control_b[1] |
| 33 | + + t**3 * end[1] |
| 34 | + ) |
| 35 | + points.append((round(x * SCALE), round(y * SCALE))) |
| 36 | + return points |
| 37 | + |
| 38 | + |
| 39 | +# Asymmetric Bézier field follows the soft, slightly flattened original shape. |
| 40 | +segments = [ |
| 41 | + ((440, 72), (625, 60), (780, 165), (842, 345)), |
| 42 | + ((842, 345), (895, 500), (838, 660), (744, 752)), |
| 43 | + ((744, 752), (635, 836), (445, 842), (286, 811)), |
| 44 | + ((286, 811), (142, 782), (88, 650), (98, 464)), |
| 45 | + ((98, 464), (86, 286), (162, 151), (294, 96)), |
| 46 | + ((294, 96), (342, 77), (392, 70), (440, 72)), |
| 47 | +] |
| 48 | +blob_points = [] |
| 49 | +for segment in segments: |
| 50 | + blob_points.extend(cubic_bezier(*segment)) |
| 51 | +draw.polygon(blob_points, fill=(218, 218, 218, 255)) |
| 52 | + |
| 53 | +font_k = ImageFont.truetype(str(serif_regular), 405 * SCALE) |
| 54 | +font_b = ImageFont.truetype(str(serif_bold), 390 * SCALE) |
| 55 | +draw.text( |
| 56 | + (165 * SCALE, 115 * SCALE), |
| 57 | + "K", |
| 58 | + font=font_k, |
| 59 | + fill=(15, 15, 15, 255), |
| 60 | + stroke_width=1 * SCALE, |
| 61 | +) |
| 62 | +draw.text((405 * SCALE, 300 * SCALE), "B", font=font_b, fill=(15, 15, 15, 255)) |
| 63 | + |
| 64 | +# Definition card is drawn on a separate layer for a subtle rotation and shadow. |
| 65 | +card = Image.new("RGBA", (535 * SCALE, 205 * SCALE), (0, 0, 0, 0)) |
| 66 | +card_draw = ImageDraw.Draw(card) |
| 67 | +card_draw.rounded_rectangle( |
| 68 | + (15 * SCALE, 18 * SCALE, 515 * SCALE, 185 * SCALE), |
| 69 | + radius=36 * SCALE, |
| 70 | + fill=(0, 0, 0, 28), |
| 71 | +) |
| 72 | +card_draw.rounded_rectangle( |
| 73 | + (0, 0, 500 * SCALE, 167 * SCALE), |
| 74 | + radius=34 * SCALE, |
| 75 | + fill=(255, 255, 255, 255), |
| 76 | + outline=(205, 205, 205, 255), |
| 77 | + width=2 * SCALE, |
| 78 | +) |
| 79 | +card_font_bold = ImageFont.truetype(str(serif_bold), 34 * SCALE) |
| 80 | +card_font = ImageFont.truetype(str(serif_regular), 30 * SCALE) |
| 81 | +card_draw.text( |
| 82 | + (38 * SCALE, 32 * SCALE), "ka·ta", font=card_font_bold, fill=(20, 20, 20, 255) |
| 83 | +) |
| 84 | +card_draw.text((145 * SCALE, 35 * SCALE), "n", font=card_font, fill=(75, 75, 75, 255)) |
| 85 | +card_draw.text( |
| 86 | + (38 * SCALE, 83 * SCALE), "unsur bahasa", font=card_font, fill=(30, 30, 30, 255) |
| 87 | +) |
| 88 | +card_draw.text( |
| 89 | + (38 * SCALE, 120 * SCALE), |
| 90 | + "yang diucapkan atau dituliskan", |
| 91 | + font=ImageFont.truetype(str(serif_regular), 22 * SCALE), |
| 92 | + fill=(70, 70, 70, 255), |
| 93 | +) |
| 94 | +card = card.rotate(2.5, resample=Image.Resampling.BICUBIC, expand=True) |
| 95 | +canvas.alpha_composite(card, (450 * SCALE, 700 * SCALE)) |
| 96 | + |
| 97 | +canvas = canvas.resize((SIZE, SIZE), Image.Resampling.LANCZOS) |
| 98 | +output = Path(__file__).resolve().parents[1] / "public" / "kbbi-logo.png" |
| 99 | +output.parent.mkdir(parents=True, exist_ok=True) |
| 100 | +canvas.save(output, "PNG", optimize=True) |
| 101 | +print(f"Generated {output}") |
0 commit comments