App icon and a lavender Stretching palette
A lotus inside a dry-brush ensō, with a small plate in the gap and a small barbell as the lifting hint. Drawn by tools/icon/make_icon.py and wired up with flutter_launcher_icons; the launch screen uses the icon's lavender. Stretching Mode moves from sage/teal to the icon's lavender and violet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No files matched your search
@@ -11,7 +11,7 @@ app: design, Exercise Library, Plans and History. The last-used Mode is remember
|
||||
_Avoid_: Tab, section, category
|
||||
|
||||
**Stretching Mode**:
|
||||
The calm Mode: light design, sage/teal, rounded shapes, slow transitions.
|
||||
The calm Mode: light design, lavender and violet, rounded shapes, slow transitions.
|
||||
|
||||
**Workout Mode**:
|
||||
The energetic Mode: dark design, orange/red on near-black, bold type, snappy transitions.
|
||||
|
||||
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
@@ -1,12 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<!-- The launch screen: the app icon's lavender mist, until Flutter draws its first frame. -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="?android:colorBackground" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
<item android:drawable="@color/launch_background" />
|
||||
</layer-list>
|
||||
|
After Width: | Height: | Size: 9.2 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 23 KiB |
@@ -1,12 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<!-- The launch screen: the app icon's lavender mist, until Flutter draws its first frame. -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@android:color/white" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
<item android:drawable="@color/launch_background" />
|
||||
</layer-list>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground>
|
||||
<inset
|
||||
android:drawable="@drawable/ic_launcher_foreground"
|
||||
android:inset="16%" />
|
||||
</foreground>
|
||||
</adaptive-icon>
|
||||
|
Before Width: | Height: | Size: 544 B After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 442 B After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 721 B After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 8.4 KiB |
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="launch_background">#F1EEF6</color>
|
||||
<color name="ic_launcher_background">#F1EEF6</color>
|
||||
</resources>
|
||||
|
After Width: | Height: | Size: 93 KiB |
|
After Width: | Height: | Size: 68 KiB |
@@ -13,7 +13,7 @@ Status: Accepted (2026-10-28)
|
||||
Stretching = hold, both-sides flag, rounds. Workout Steps can be grouped into Supersets.
|
||||
5. **Library browsing**: search + muscle/equipment/level filters, reused as the Plan exercise picker.
|
||||
6. **Structure & design**: Mode switch at top; per-mode tabs Plans · Library · History.
|
||||
Stretching = calm/light (sage/teal, rounded, slow). Workout = dark/energetic (orange/red, bold, snappy).
|
||||
Stretching = calm/light (lavender/violet since ADR 0007, first sage/teal; rounded, slow). Workout = dark/energetic (orange/red, bold, snappy).
|
||||
Last-used mode is remembered.
|
||||
7. **Player cues**: Visual cues (countdown ring, accent pulse, next cards, auto-advance, screen kept on)
|
||||
plus a gentle, non-annoying sound. No vibration.
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# ADR 0007 — App icon: a lotus in a Zen circle, with a hint of lifting
|
||||
|
||||
Status: Accepted (2026-10-29)
|
||||
|
||||
1. **Motif**: a lotus inside an ensō (Zen brush circle). Yoga and calm lead; lifting is only a hint:
|
||||
a small weight plate floating in the circle's open gap, and a small barbell under the lotus.
|
||||
2. **Brush**: the ensō is painted counter-clockwise from the top, with its gap at the upper right.
|
||||
It starts round and solid with calm, nearly smooth edges, then runs dry: pale streaks open inside
|
||||
the stroke and the bristles give out at different points, so the tail frays out close to the plate.
|
||||
The circle is never closed.
|
||||
3. **Colours**: a calm purple palette: lavender mist ground `#F1EEF6`, muted violet `#6B5B95` for the
|
||||
ensō and the lotus's centre petal, light violet `#A496C8` for the side petals, and a soft apricot
|
||||
`#E98A6F` only for the plate and the barbell.
|
||||
4. **Stretching follows the icon**: Stretching Mode's palette moved from sage/teal to the same lavender
|
||||
and violet, so the app and its icon match. Workout keeps its dark ember design.
|
||||
5. **Style**: flat shapes, no text, no gradients or outlines.
|
||||
6. **Format**: Android adaptive icon (foreground on a solid lavender background, masked by the launcher)
|
||||
plus a legacy square PNG, wired up with `flutter_launcher_icons` as in CookIT.
|
||||
7. **Launch screen**: the Android launch background uses the same lavender mist as the icon.
|
||||
|
||||
The icon is generated by `tools/icon/make_icon.py` (seeded, so it redraws identically): run it, then
|
||||
`dart run flutter_launcher_icons`. Rejected along the way: a dumbbell or barbell as the main element,
|
||||
a plate closing the circle's gap, a seated figure, and heavier, more textured brush strokes.
|
||||
@@ -74,16 +74,17 @@ class ZenStyle extends ThemeExtension<ZenStyle> {
|
||||
static const stretching = ZenStyle(
|
||||
mode: Mode.stretching,
|
||||
brightness: Brightness.light,
|
||||
background: Color(0xFFEDF3EF),
|
||||
// Lavender mist and a muted violet, as in the app icon.
|
||||
background: Color(0xFFF1EEF6),
|
||||
surface: Color(0xFFFFFFFF),
|
||||
surfaceSoft: Color(0xFFDCEAE2),
|
||||
ink: Color(0xFF1E3832),
|
||||
inkMuted: Color(0xFF587069),
|
||||
accent: Color(0xFF2E8B80),
|
||||
accentEnd: Color(0xFF2E8B80),
|
||||
surfaceSoft: Color(0xFFE4DEEF),
|
||||
ink: Color(0xFF2A2338),
|
||||
inkMuted: Color(0xFF6A6279),
|
||||
accent: Color(0xFF6B5B95),
|
||||
accentEnd: Color(0xFF6B5B95),
|
||||
onAccent: Color(0xFFFFFFFF),
|
||||
line: Color(0xFFA8C9B4),
|
||||
navBar: Color(0xFFD3E5DA),
|
||||
line: Color(0xFFC3B8DA),
|
||||
navBar: Color(0xFFE0D9EC),
|
||||
radius: 28,
|
||||
controlRadius: 999,
|
||||
motion: Duration(milliseconds: 700),
|
||||
|
||||
@@ -17,6 +17,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.1.1"
|
||||
archive:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: archive
|
||||
sha256: "0d58d1fa1fddf6588251ccf2819f3fb3b6d0452c1b2813ac7413a05592bfbefe"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.5.2"
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -294,6 +302,14 @@ packages:
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_launcher_icons:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: flutter_launcher_icons
|
||||
sha256: "10f13781741a2e3972126fae08393d3c4e01fa4cd7473326b94b72cf594195e7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.14.4"
|
||||
flutter_lints:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
@@ -397,6 +413,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.2"
|
||||
image:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image
|
||||
sha256: "89ed75b2f8231fe676808c5564f9590d728024ded7ac782a7ed8474f1c95b72a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.12.0"
|
||||
intl:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -613,6 +637,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.3"
|
||||
posix:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: posix
|
||||
sha256: "613646d076944f3acc125830af935b48d4b8a57a388691547bbfd25b434f4d63"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.5.4"
|
||||
provider:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
||||
@@ -31,6 +31,7 @@ dev_dependencies:
|
||||
drift_dev: ^2.31.0
|
||||
build_runner: ^2.15.1
|
||||
sqlite3: ^2.9.4
|
||||
flutter_launcher_icons: ^0.14.4
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
@@ -84,3 +85,12 @@ flutter:
|
||||
weight: 600
|
||||
- asset: assets/fonts/Barlow-Bold.ttf
|
||||
weight: 700
|
||||
|
||||
# Drawn by tools/icon/make_icon.py; regenerate with `dart run flutter_launcher_icons`.
|
||||
flutter_launcher_icons:
|
||||
android: true
|
||||
ios: false
|
||||
image_path: assets/icon/icon.png
|
||||
adaptive_icon_background: "#F1EEF6"
|
||||
adaptive_icon_foreground: assets/icon/icon_foreground.png
|
||||
min_sdk_android: 21
|
||||
@@ -0,0 +1,236 @@
|
||||
"""Draws the ZenXercise app icon: a lotus inside a dry-brush ensō, with a hint of lifting.
|
||||
|
||||
Usage: python make_icon.py [output folder] (default: assets/icon)
|
||||
|
||||
Writes icon.png (legacy square icon on the lavender ground) and icon_foreground.png
|
||||
(transparent adaptive-icon foreground, kept inside the launcher's safe zone).
|
||||
Everything is drawn at 4x and scaled down, which smooths the edges. All randomness is
|
||||
seeded, so the icon is identical on every run.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
import random
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
SIZE = 1024
|
||||
SCALE = 4
|
||||
# Stretching's palette.
|
||||
GROUND = (241, 238, 246, 255) # Lavender mist: the icon's background and the launch screen
|
||||
VIOLET = (107, 91, 149, 255) # The ensō and the lotus's centre petal
|
||||
VIOLET_LIGHT = (164, 150, 200, 255) # Side petals: a tonal step of the violet
|
||||
APRICOT = (233, 138, 111, 255) # The lifting hint only: the plate and the small barbell
|
||||
|
||||
|
||||
def _s(v: float) -> float:
|
||||
return v * SCALE
|
||||
|
||||
|
||||
class _Noise:
|
||||
"""Smooth 1-D value noise in -1..1."""
|
||||
|
||||
def __init__(self, seed: int, size: int = 512):
|
||||
rng = random.Random(seed)
|
||||
self.values = [rng.uniform(-1, 1) for _ in range(size)]
|
||||
|
||||
def __call__(self, x: float) -> float:
|
||||
i = math.floor(x)
|
||||
f = x - i
|
||||
a = self.values[i % len(self.values)]
|
||||
b = self.values[(i + 1) % len(self.values)]
|
||||
return a + (b - a) * (1 - math.cos(math.pi * f)) / 2
|
||||
|
||||
|
||||
def dry_brush_enso(
|
||||
draw: ImageDraw.ImageDraw, cx: float, cy: float, r: float, width: float, gap_deg: float, gap_at: float
|
||||
):
|
||||
"""An ensō painted counter-clockwise from beside the gap.
|
||||
|
||||
The opening of [gap_deg] degrees is centred on [gap_at] (degrees, 0 = right, counter-clockwise).
|
||||
The brush lands with a round start and calm, nearly smooth edges; from halfway it runs dry:
|
||||
its bristles stop overlapping, so pale streaks open inside the stroke, and the bristles give
|
||||
out at different points, fraying the tail.
|
||||
"""
|
||||
serration, nicks, swell, wander = _Noise(3), _Noise(29), _Noise(11), _Noise(19)
|
||||
rng = random.Random(13)
|
||||
start = math.radians(gap_at + gap_deg / 2)
|
||||
sweep = math.radians(360 - gap_deg)
|
||||
dry = 0.5 # Where the brush starts to run out of ink.
|
||||
|
||||
def angle(t: float) -> float:
|
||||
return start + sweep * t
|
||||
|
||||
def centre(t: float) -> float:
|
||||
return r * (1 + 0.01 * wander(t * 3))
|
||||
|
||||
def stroke_width(t: float) -> float:
|
||||
return width * (0.93 + 0.1 * swell(t * 3.5)) * (1 - 0.28 * (max(0.0, t - dry) / (1 - dry)) ** 1.6)
|
||||
|
||||
def point(t: float, d: float) -> tuple[float, float]:
|
||||
a = angle(t)
|
||||
return (_s(cx + d * math.cos(a)), _s(cy - d * math.sin(a)))
|
||||
|
||||
def ragged(t: float, side: int) -> float:
|
||||
"""A faint irregularity on both edges, and the rare small nick on the outside."""
|
||||
fine = 0.006 * serration(t * 40 + side * 31) + 0.002 * serration(t * 120 + side * 77)
|
||||
nick = max(0.0, nicks(t * 9 + side * 5) - 0.8) * (0.03 if side > 0 else 0.0)
|
||||
return (fine - nick) * min(1.0, t / 0.08)
|
||||
|
||||
steps = 1400
|
||||
# Solid body, overlapping the bristles where they take over, with a round start.
|
||||
ts = [i * (dry + 0.06) / steps for i in range(steps + 1)]
|
||||
outer = [point(t, centre(t) + stroke_width(t) * (0.5 + ragged(t, 1))) for t in ts]
|
||||
inner = [point(t, centre(t) - stroke_width(t) * (0.5 + ragged(t, -1))) for t in ts]
|
||||
draw.polygon(outer + inner[::-1], fill=VIOLET)
|
||||
a0, w0, d0 = angle(0.0), stroke_width(0.0), centre(0.0)
|
||||
x0, y0 = cx + d0 * math.cos(a0), cy - d0 * math.sin(a0)
|
||||
draw.ellipse([_s(x0 - w0 / 2), _s(y0 - w0 / 2), _s(x0 + w0 / 2), _s(y0 + w0 / 2)], fill=VIOLET)
|
||||
|
||||
# Bristles from the dry point on.
|
||||
count = 15
|
||||
for k in range(count):
|
||||
frac = -0.5 + (k + 0.5) / count
|
||||
edge = abs(frac) * 2
|
||||
ink = rng.random()
|
||||
end = 0.95 - 0.05 * rng.random() - 0.16 * edge**1.6 # Edge bristles give out sooner.
|
||||
skips = []
|
||||
for _ in range(rng.randint(0, 2)):
|
||||
s0 = dry + 0.22 + rng.random() * max(0.0, end - dry - 0.3)
|
||||
skips.append((s0, s0 + 0.008 + rng.random() * 0.025 * (1.2 - ink)))
|
||||
phase = rng.random() * math.tau
|
||||
|
||||
def edges(t: float) -> tuple[float, float]:
|
||||
w = stroke_width(t)
|
||||
dried = max(0.0, (t - dry) / (end - dry))
|
||||
# Full overlap while inked; separating as the bristle dries out.
|
||||
half = 0.5 / count * (1.8 - 1.25 * dried ** (0.7 + ink)) * max(0.0, min(1.0, (end - t) / 0.12)) ** 0.8
|
||||
# Lifting off the paper and touching down again: taper into and out of each skip.
|
||||
for a, b in skips:
|
||||
near = max(a - t, t - b, 0.0)
|
||||
half *= min(1.0, near / 0.018) ** 0.7
|
||||
splay = w * 0.005 * dried * math.sin(2 * math.pi * 6 * t + phase)
|
||||
mid = centre(t) + w * frac + splay
|
||||
# Never past the stroke's own edges, so the body hands over without a step.
|
||||
top = min(mid + w * half, centre(t) + w * (0.5 + ragged(t, 1)))
|
||||
bottom = max(mid - w * half, centre(t) - w * (0.5 + ragged(t, -1)))
|
||||
return top, min(bottom, top)
|
||||
|
||||
run: list[float] = []
|
||||
|
||||
def flush():
|
||||
if len(run) > 1:
|
||||
draw.polygon([point(t, edges(t)[0]) for t in run] + [point(t, edges(t)[1]) for t in reversed(run)], fill=VIOLET)
|
||||
run.clear()
|
||||
|
||||
for i in range(steps + 1):
|
||||
t = dry + (end - dry) * i / steps
|
||||
if any(a <= t <= b for a, b in skips):
|
||||
flush()
|
||||
else:
|
||||
run.append(t)
|
||||
flush()
|
||||
|
||||
|
||||
def plate(draw: ImageDraw.ImageDraw, cx: float, cy: float, r: float, ground):
|
||||
"""A weight plate seen from the front: a disc with a centre hole."""
|
||||
draw.ellipse([_s(cx - r), _s(cy - r), _s(cx + r), _s(cy + r)], fill=APRICOT)
|
||||
hole = r * 0.3
|
||||
draw.ellipse([_s(cx - hole), _s(cy - hole), _s(cx + hole), _s(cy + hole)], fill=ground)
|
||||
|
||||
|
||||
class Pen:
|
||||
"""Draws in 1024-unit coordinates around the centre, shrunk by [scale]."""
|
||||
|
||||
def __init__(self, draw: ImageDraw.ImageDraw, scale: float):
|
||||
self.draw = draw
|
||||
self.scale = scale
|
||||
|
||||
def p(self, x: float, y: float) -> tuple[float, float]:
|
||||
c = SIZE / 2
|
||||
return (_s(c + (x - c) * self.scale), _s(c + (y - c) * self.scale))
|
||||
|
||||
def w(self, v: float) -> float:
|
||||
return _s(v * self.scale)
|
||||
|
||||
def circle(self, x, y, r, fill):
|
||||
cx, cy = self.p(x, y)
|
||||
rr = self.w(r)
|
||||
self.draw.ellipse([cx - rr, cy - rr, cx + rr, cy + rr], fill=fill)
|
||||
|
||||
def limb(self, points, width, fill):
|
||||
"""A thick stroke through [points] with round ends and joints."""
|
||||
pts = [self.p(x, y) for x, y in points]
|
||||
self.draw.line(pts, fill=fill, width=int(self.w(width)), joint="curve")
|
||||
for x, y in points:
|
||||
self.circle(x, y, width / 2, fill)
|
||||
|
||||
def bar(self, x0, x1, y, thickness, fill, plates=1.0):
|
||||
"""A horizontal barbell: the bar, a big and a small plate at each end, sized by [plates]."""
|
||||
self.limb([(x0, y), (x1, y)], thickness, fill)
|
||||
for side, end in ((-1, x0), (1, x1)):
|
||||
for offset, w, h in ((-28 * plates, 34 * plates, 120 * plates), (4 * plates, 24 * plates, 84 * plates)):
|
||||
px = end - side * offset
|
||||
a, b = self.p(px - w / 2, y - h / 2)
|
||||
c, d = self.p(px + w / 2, y + h / 2)
|
||||
self.draw.rounded_rectangle([a, b, c, d], radius=self.w(w / 2.4), fill=fill)
|
||||
|
||||
def petal(self, bx, by, length, width, angle_deg, fill):
|
||||
"""An almond-shaped petal growing from (bx, by) towards [angle_deg] (90 = up)."""
|
||||
a = math.radians(angle_deg)
|
||||
ux, uy = math.cos(a), -math.sin(a)
|
||||
nx, ny = -uy, ux
|
||||
left, right = [], []
|
||||
steps = 80
|
||||
for i in range(steps + 1):
|
||||
t = i / steps
|
||||
half = width / 2 * math.sin(math.pi * t) ** 0.75 * (1 - 0.25 * t)
|
||||
x, y = bx + ux * length * t, by + uy * length * t
|
||||
left.append(self.p(x + nx * half, y + ny * half))
|
||||
right.append(self.p(x - nx * half, y - ny * half))
|
||||
self.draw.polygon(left + right[::-1], fill=fill)
|
||||
|
||||
|
||||
def lotus(pen: Pen, base: float) -> None:
|
||||
"""Five petals growing from (512, [base]): a deep centre, lighter sides."""
|
||||
for angle, length, width, fill in (
|
||||
(162, 250, 150, VIOLET_LIGHT),
|
||||
(18, 250, 150, VIOLET_LIGHT),
|
||||
(128, 300, 160, VIOLET_LIGHT),
|
||||
(52, 300, 160, VIOLET_LIGHT),
|
||||
(90, 360, 190, VIOLET),
|
||||
):
|
||||
pen.petal(512, base, length, width, angle, fill)
|
||||
|
||||
|
||||
def draw_icon(ground, scale: float) -> Image.Image:
|
||||
"""The motif on [ground]; [scale] shrinks it to fit the adaptive safe zone."""
|
||||
image = Image.new("RGBA", (SIZE * SCALE, SIZE * SCALE), ground)
|
||||
draw = ImageDraw.Draw(image)
|
||||
c = SIZE / 2
|
||||
r = 300 * scale
|
||||
plate_at = 58 # Upper right, in the ensō's open gap.
|
||||
# The stroke starts 29° past the plate and runs on until its dry tail gives out close to it.
|
||||
dry_brush_enso(draw, c, c, r, 74 * scale, gap_deg=44, gap_at=plate_at + 7)
|
||||
lotus(Pen(draw, scale * 0.7), base=660)
|
||||
Pen(draw, scale * 0.7).bar(382, 642, 730, 16, APRICOT, plates=0.5)
|
||||
a = math.radians(plate_at)
|
||||
plate(draw, c + r * math.cos(a), c - r * math.sin(a), 46 * scale, ground)
|
||||
return image.resize((SIZE, SIZE), Image.LANCZOS)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
out = Path(sys.argv[1]) if len(sys.argv) > 1 else Path(__file__).resolve().parents[2] / "assets" / "icon"
|
||||
out.mkdir(parents=True, exist_ok=True)
|
||||
# Legacy icon: full bleed on the lavender ground.
|
||||
draw_icon(GROUND, 1.0).save(out / "icon.png")
|
||||
# Adaptive foreground: transparent, shrunk into the central safe zone (about 61% of the canvas).
|
||||
draw_icon((0, 0, 0, 0), 0.68).save(out / "icon_foreground.png")
|
||||
print(f"icon -> {out}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||