Skip to content

Instantly share code, notes, and snippets.

@motebaya
Created May 7, 2026 17:31
Show Gist options
  • Select an option

  • Save motebaya/5f81ea6b8585baaa3dcf47b7658644d4 to your computer and use it in GitHub Desktop.

Select an option

Save motebaya/5f81ea6b8585baaa3dcf47b7658644d4 to your computer and use it in GitHub Desktop.
Generates a YouTube-style subscribe button graphic with a transparent background
"""
Generates a YouTube-style subscribe button graphic with a transparent background,
decorative dashed accent lines, and an animated pointing-hand cursor overlay.
@github.com/motebaya - 2026-03-29 08:33 PM
"""
from __future__ import annotations
import os
from PIL import Image, ImageDraw, ImageFont, ImageFilter
FONT_PATH = os.path.join(
os.path.expanduser("~"),
"AppData", "Local", "Microsoft", "Windows", "Fonts", "Oswald-Bold.ttf",
)
# Perimeter of the hand cursor in normalised (unscaled) coordinate space.
# Points are listed clockwise starting at the index-finger left edge.
_CURSOR_PERIMETER: list[tuple[float, float]] = [
# --- Index Finger (Extended & Rounded) ---
(4, 1),
(4.2, 0.2),
(5, -0.4), # tip peak
(5.8, 0.2),
(6, 1),
(6, 2), # inner corner Index/Middle
# --- Middle Knuckle (Folded & Rounded) ---
(6.2, 1.7),
(7, 1.4), # peak
(7.8, 1.7),
(8, 2.2),
(8, 3), # inner corner Middle/Ring
# --- Ring Knuckle (Folded & Rounded) ---
(8.2, 2.7),
(9, 2.4), # peak
(9.8, 2.7),
(10, 3.2),
(10, 4), # inner corner Ring/Pinky
# --- Pinky Knuckle (Folded & Rounded) ---
(10.2, 3.7),
(11, 3.4), # peak
(11.8, 3.7),
(12, 4.5),
# --- Palm and Wrist ---
(12, 10),
(9, 14),
(9, 18),
(4, 18),
(4, 15),
# --- REVISED: Classic Compact & Rounded Thumb ---
(2.5, 12.5),
(1.0, 10.5),
(0.2, 8.5),
(0.2, 7.2), # tip peak
(0.8, 6.5),
(2.0, 6.5),
(4, 8), # thumb crotch
(4, 1), # close back to index left side
]
# Unscaled separator lines between folded fingers (start, end).
_FINGER_SEPARATORS: list[tuple[tuple[float, float], tuple[float, float]]] = [
((6, 2), (6, 8)), # Index / Middle
((8, 3), (8, 9)), # Middle / Ring
((10, 4), (10, 9)), # Ring / Pinky
]
def draw_broken_line(
draw: ImageDraw.ImageDraw,
x_start: float,
x_end: float,
y: float,
pattern: list[float],
fill: str = "#FF2222",
width: int = 2,
) -> None:
"""Draw a proportional dashed line along a horizontal axis.
:param draw: Active :class:`~PIL.ImageDraw.ImageDraw` context.
:param x_start: Left boundary of the line.
:param x_end: Right boundary of the line.
:param y: Vertical position.
:param pattern: Alternating proportions ``[draw, gap, draw, gap, …]``
that sum to 1.0. Even-indexed values are drawn segments.
:param fill: Stroke colour (default red).
:param width: Stroke width in pixels.
"""
total = x_end - x_start
current_x = x_start
for i, pct in enumerate(pattern):
segment = total * pct
if i % 2 == 0: # even index → draw segment
draw.line([(current_x, y), (current_x + segment, y)], fill=fill, width=width)
current_x += segment
def draw_dynamic_cursor(
base_img: Image.Image,
offset_x: float,
offset_y: float,
scale: float = 4.5,
) -> None:
"""Composite a vector-style pointing-hand cursor onto *base_img*.
The cursor shape is defined in normalised space via :data:`_CURSOR_PERIMETER`
and scaled/translated at draw-time. A soft drop-shadow is rendered first.
:param base_img: RGBA image to composite onto (modified in-place).
:param offset_x: Horizontal translation applied after scaling.
:param offset_y: Vertical translation applied after scaling.
:param scale: Uniform scale factor (pixels per normalised unit).
"""
scaled = [(x * scale + offset_x, y * scale + offset_y) for x, y in _CURSOR_PERIMETER]
line_width = max(2, int(scale * 0.6))
# 1. Drop shadow
shadow_layer = Image.new("RGBA", base_img.size, (0, 0, 0, 0))
shadow_draw = ImageDraw.Draw(shadow_layer)
shadow_offset = 8
shadow_draw.polygon(
[(x + shadow_offset, y + shadow_offset) for x, y in scaled],
fill=(0, 0, 0, 50),
)
base_img.alpha_composite(shadow_layer.filter(ImageFilter.GaussianBlur(4)))
# 2. Hand shape
cursor_layer = Image.new("RGBA", base_img.size, (0, 0, 0, 0))
cursor_draw = ImageDraw.Draw(cursor_layer)
cursor_draw.polygon(scaled, fill="#FFFFFF")
cursor_draw.line(scaled + [scaled[0]], fill="#0F172A", width=line_width, joint="curve")
# 3. Inner finger-separator lines
for (sx_n, sy_n), (ex_n, ey_n) in _FINGER_SEPARATORS:
cursor_draw.line(
[
(sx_n * scale + offset_x, sy_n * scale + offset_y),
(ex_n * scale + offset_x, ey_n * scale + offset_y),
],
fill="#0F172A",
width=line_width,
)
base_img.alpha_composite(cursor_layer)
def create_youtube_button(
text: str = "SUBSCRIBE",
output_filename: str = "custom_button.png",
) -> None:
"""Render a YouTube-style subscribe button and save it as a PNG.
The output uses an RGBA transparent background so it can be placed
directly over any video frame or graphic.
:param text: Label displayed on the button (e.g. ``"@itsmochino"``).
:param output_filename: Destination file path for the rendered PNG.
"""
width, height = 1000, 600
base_img = Image.new("RGBA", (width, height), (0, 0, 0, 0))
draw = ImageDraw.Draw(base_img)
# 1. Font
try:
font = ImageFont.truetype(FONT_PATH, 85)
except IOError:
print(f"Error: Could not find {FONT_PATH}. Using default.")
font = ImageFont.load_default()
# 2. Dynamic sizing
bbox = draw.textbbox((0, 0), text, font=font)
text_w, text_h = bbox[2] - bbox[0], bbox[3] - bbox[1]
icon_w, icon_h = 130, 90
gap = 40
pad_x, pad_y = 60, 40
button_w = icon_w + gap + text_w + pad_x * 2
button_h = max(icon_h, text_h) + pad_y * 2
btn_x1 = (width - button_w) // 2
btn_y1 = (height - button_h) // 2
btn_x2 = btn_x1 + button_w
btn_y2 = btn_y1 + button_h
# 3. Button drop shadow
shadow_layer = Image.new("RGBA", (width, height), (0, 0, 0, 0))
ImageDraw.Draw(shadow_layer).rounded_rectangle(
[btn_x1, btn_y1 + 15, btn_x2, btn_y2 + 15],
radius=20,
fill=(0, 0, 0, 40),
)
base_img.alpha_composite(shadow_layer.filter(ImageFilter.GaussianBlur(15)))
# 4. Broken red accent lines
line_start, line_end = btn_x1 + 30, btn_x2 - 30
offset = 30
# Proportions: "---- ------------------ - ---------"
draw_broken_line(
draw,
line_start,
line_end,
btn_y1 - offset,
[0.10, 0.10, 0.40, 0.05, 0.05, 0.15, 0.15]
)
# Proportions: "--------- ------------------- ----"
draw_broken_line(
draw,
line_start,
line_end,
btn_y2 + offset,
[0.20, 0.15, 0.45, 0.15, 0.05]
)
# 5. White button body
draw.rounded_rectangle([btn_x1, btn_y1, btn_x2, btn_y2], radius=20, fill="#FFFFFF")
# 6. YouTube icon (red pill + white play triangle)
icon_x = btn_x1 + pad_x
icon_y = btn_y1 + (button_h - icon_h) // 2
draw.rounded_rectangle([
icon_x,
icon_y,
icon_x + icon_w,
icon_y + icon_h
], radius=22, fill="#FF0033")
tri_w, tri_h = 35, 40
tri_x = icon_x + (icon_w - tri_w) // 2 + 5
tri_y = icon_y + (icon_h - tri_h) // 2
draw.polygon(
[
(tri_x, tri_y),
(tri_x, tri_y + tri_h),
(tri_x + tri_w, tri_y + tri_h // 2)
],
fill="#FFFFFF",
)
# 7. Label text
text_x = icon_x + icon_w + gap
text_y = btn_y1 + (button_h - text_h) // 2 - 25
draw.text((text_x, text_y), text, fill="#0F172A", font=font)
# 8. Hand cursor overlaid on the button corner
draw_dynamic_cursor(base_img, btn_x2 - 50, btn_y2 - 30, scale=4.2)
# 9. Save – PNG preserves transparency
base_img.save(output_filename)
print(f"Saved: {output_filename}")
if __name__ == "__main__":
create_youtube_button("@itsmochino", "transparent_subscribe.png")
@motebaya

motebaya commented May 7, 2026

Copy link
Copy Markdown
Author

result:

transparent_subscribe

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment