Initial commit
This commit is contained in:
283
make_icons.py
Normal file
283
make_icons.py
Normal file
@@ -0,0 +1,283 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
make_icons.py - Erzeugt die Symbole der Erweiterung als SVG und PNG.
|
||||
|
||||
Die Geometrie ist EINMAL beschrieben und wird von zwei Ausgaben gelesen.
|
||||
So können SVG und PNG nicht auseinanderlaufen.
|
||||
|
||||
SVG wird von LibreOffice skaliert und passt sich damit der eingestellten
|
||||
Symbolgröße an (Extras → Optionen → Ansicht → Symbolgröße).
|
||||
PNG in 16, 26 und 32 Pixeln als Rückfallebene, falls die
|
||||
SVG-Unterstützung der eingesetzten Version nicht greift.
|
||||
|
||||
Alle Symbole teilen dieselbe Grundform - ein Blatt mit umgeknickter Ecke in
|
||||
Paperless-Grün - damit die Leiste als zusammengehörige Gruppe erkennbar
|
||||
bleibt. Unterschieden wird über eine farbige Scheibe unten rechts.
|
||||
|
||||
Aufruf: python3 make_icons.py (benötigt Pillow für die PNG-Ausgabe)
|
||||
"""
|
||||
|
||||
import math
|
||||
import os
|
||||
|
||||
OUT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "icons")
|
||||
|
||||
# Zeichenfläche in Einheiten. Die Formen reichen bewusst bis dicht an den
|
||||
# Rand, damit bei kleiner Darstellung möglichst viel Fläche trägt.
|
||||
BOX = 24.0
|
||||
PNG_SIZES = (16, 26, 32)
|
||||
SCALE = 12 # Überabtastung für die PNG-Ausgabe
|
||||
|
||||
GREEN = "#175420"
|
||||
GREEN_LIGHT = "#3a8a44"
|
||||
PAPER = "#fcfcfa"
|
||||
WHITE = "#ffffff"
|
||||
BLUE = "#1e60a8" # herunterladen
|
||||
ORANGE = "#c86a14" # hinauf
|
||||
VIOLET = "#703e98" # klassifizieren
|
||||
TEAL = "#117a7a" # teilen
|
||||
GREY = "#5c6064" # einstellungen
|
||||
|
||||
|
||||
# ----------------------------------------------------------- Grundformen
|
||||
|
||||
def poly(points, fill=None, stroke=None, width=0.0):
|
||||
return ("poly", points, fill, stroke, width)
|
||||
|
||||
|
||||
def circle(cx, cy, r, fill=None, stroke=None, width=0.0):
|
||||
return ("circle", (cx, cy, r), fill, stroke, width)
|
||||
|
||||
|
||||
def line(p1, p2, stroke, width):
|
||||
return ("line", (p1, p2), None, stroke, width)
|
||||
|
||||
|
||||
def rrect(x0, y0, x1, y1, r, fill=None, stroke=None, width=0.0):
|
||||
return ("rrect", (x0, y0, x1, y1, r), fill, stroke, width)
|
||||
|
||||
|
||||
def sheet(colour=GREEN):
|
||||
"""Blatt mit umgeknickter Ecke, füllt die Fläche links oben."""
|
||||
x0, y0, x1, y1 = 0.6, 0.5, 18.4, 23.4
|
||||
fold = 5.4
|
||||
out = [
|
||||
poly([(x0, y0), (x1 - fold, y0), (x1, y0 + fold), (x1, y1), (x0, y1)],
|
||||
fill=PAPER, stroke=colour, width=1.8),
|
||||
poly([(x1 - fold, y0), (x1, y0 + fold), (x1 - fold, y0 + fold)],
|
||||
fill=colour),
|
||||
]
|
||||
for i, w in enumerate((10.4, 12.6, 8.4)):
|
||||
yy = y0 + 7.4 + i * 3.8
|
||||
out.append(line((x0 + 2.2, yy), (x0 + 2.2 + w, yy), colour, 1.4))
|
||||
return out
|
||||
|
||||
|
||||
def badge(colour):
|
||||
"""Scheibe unten rechts, mit hellem Ring zur Abgrenzung vom Blatt."""
|
||||
cx, cy, r = 16.9, 17.1, 6.7
|
||||
return cx, cy, r, [
|
||||
circle(cx, cy, r + 1.1, fill=WHITE),
|
||||
circle(cx, cy, r, fill=colour),
|
||||
]
|
||||
|
||||
|
||||
def arrow(cx, cy, up=True, colour=PAPER):
|
||||
w, h = 2.9, 3.5
|
||||
if up:
|
||||
return [poly([(cx, cy - h), (cx - w, cy - 0.3), (cx + w, cy - 0.3)],
|
||||
fill=colour),
|
||||
line((cx, cy - 0.6), (cx, cy + h * 0.85), colour, 2.0)]
|
||||
return [poly([(cx, cy + h), (cx - w, cy + 0.3), (cx + w, cy + 0.3)],
|
||||
fill=colour),
|
||||
line((cx, cy - h * 0.85), (cx, cy + 0.6), colour, 2.0)]
|
||||
|
||||
|
||||
# --------------------------------------------------------------- Symbole
|
||||
|
||||
def icon_logo():
|
||||
"""Markensymbol für den Anfang der Leiste: gefüllte Fläche, weißes Blatt."""
|
||||
out = [rrect(0.4, 0.4, 23.6, 23.6, 4.0, fill=GREEN)]
|
||||
x0, y0, x1, y1 = 5.0, 3.9, 19.0, 20.1
|
||||
fold = 4.4
|
||||
out += [
|
||||
poly([(x0, y0), (x1 - fold, y0), (x1, y0 + fold), (x1, y1), (x0, y1)],
|
||||
fill=PAPER),
|
||||
poly([(x1 - fold, y0), (x1, y0 + fold), (x1 - fold, y0 + fold)],
|
||||
fill=GREEN_LIGHT),
|
||||
]
|
||||
for i, w in enumerate((7.4, 9.2, 6.0)):
|
||||
yy = y0 + 6.0 + i * 3.4
|
||||
out.append(line((x0 + 2.0, yy), (x0 + 2.0 + w, yy), GREEN, 1.5))
|
||||
return out
|
||||
|
||||
|
||||
def icon_open():
|
||||
out = sheet()
|
||||
cx, cy, _r, b = badge(BLUE)
|
||||
return out + b + arrow(cx, cy, up=False)
|
||||
|
||||
|
||||
def icon_version():
|
||||
out = sheet()
|
||||
cx, cy, _r, b = badge(ORANGE)
|
||||
out += b + arrow(cx - 1.2, cy, up=True)
|
||||
px, py, a = cx + 3.4, cy - 2.6, 1.7
|
||||
out += [line((px - a, py), (px + a, py), PAPER, 1.4),
|
||||
line((px, py - a), (px, py + a), PAPER, 1.4)]
|
||||
return out
|
||||
|
||||
|
||||
def icon_save_as():
|
||||
# Zweites Blatt dahinter: es kann ein eigenständiges Dokument entstehen.
|
||||
out = [poly([(4.4, 0.5), (18.4, 0.5), (18.4, 4.6), (4.4, 4.6)],
|
||||
fill=PAPER, stroke=GREEN_LIGHT, width=1.5)]
|
||||
out += sheet()
|
||||
cx, cy, _r, b = badge(ORANGE)
|
||||
out += b + arrow(cx - 1.2, cy, up=True)
|
||||
sx, sy, a = cx + 3.4, cy - 2.6, 1.9
|
||||
for dx, dy in ((a, 0), (0, a), (a * .7, a * .7), (-a * .7, a * .7)):
|
||||
out.append(line((sx - dx, sy - dy), (sx + dx, sy + dy), PAPER, 1.15))
|
||||
return out
|
||||
|
||||
|
||||
def icon_classify():
|
||||
out = sheet()
|
||||
cx, cy, _r, b = badge(VIOLET)
|
||||
out += b + [
|
||||
poly([(cx - 3.6, cy - 0.5), (cx + 0.4, cy - 4.0),
|
||||
(cx + 3.7, cy - 0.6), (cx - 0.2, cy + 3.6)], fill=PAPER),
|
||||
circle(cx + 1.4, cy - 1.5, 0.9, fill=VIOLET),
|
||||
]
|
||||
return out
|
||||
|
||||
|
||||
def icon_share():
|
||||
out = sheet()
|
||||
cx, cy, _r, b = badge(TEAL)
|
||||
a, bb, c = (cx + 2.6, cy - 3.2), (cx + 2.6, cy + 3.2), (cx - 3.2, cy)
|
||||
out += b + [line(c, a, PAPER, 1.3), line(c, bb, PAPER, 1.3)]
|
||||
for p in (a, bb, c):
|
||||
out.append(circle(p[0], p[1], 1.65, fill=PAPER))
|
||||
return out
|
||||
|
||||
|
||||
def icon_settings():
|
||||
out = sheet()
|
||||
cx, cy, _r, b = badge(GREY)
|
||||
out += b
|
||||
outer, inner, hole = 5.0, 3.4, 1.6
|
||||
for i in range(8):
|
||||
ang = math.pi * i / 4
|
||||
dx, dy = math.cos(ang), math.sin(ang)
|
||||
out.append(line((cx + dx * inner * .7, cy + dy * inner * .7),
|
||||
(cx + dx * outer, cy + dy * outer), PAPER, 1.7))
|
||||
out += [circle(cx, cy, inner, stroke=PAPER, width=1.5),
|
||||
circle(cx, cy, hole, fill=GREY)]
|
||||
return out
|
||||
|
||||
|
||||
ICONS = {
|
||||
"paperless-logo": icon_logo,
|
||||
"paperless-open": icon_open,
|
||||
"paperless-version": icon_version,
|
||||
"paperless-saveas": icon_save_as,
|
||||
"paperless-classify": icon_classify,
|
||||
"paperless-share": icon_share,
|
||||
"paperless-settings": icon_settings,
|
||||
}
|
||||
|
||||
|
||||
# ----------------------------------------------------------- Ausgabe SVG
|
||||
|
||||
def to_svg(shapes):
|
||||
out = ['<?xml version="1.0" encoding="UTF-8"?>',
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" '
|
||||
'viewBox="0 0 %g %g">' % (BOX, BOX)]
|
||||
|
||||
def attrs(fill, stroke, width):
|
||||
a = ' fill="%s"' % (fill or "none")
|
||||
if stroke:
|
||||
a += (' stroke="%s" stroke-width="%g" stroke-linecap="round"'
|
||||
' stroke-linejoin="round"' % (stroke, width))
|
||||
return a
|
||||
|
||||
for kind, data, fill, stroke, width in shapes:
|
||||
if kind == "poly":
|
||||
pts = " ".join("%g,%g" % p for p in data)
|
||||
out.append(' <polygon points="%s"%s/>'
|
||||
% (pts, attrs(fill, stroke, width)))
|
||||
elif kind == "circle":
|
||||
cx, cy, r = data
|
||||
out.append(' <circle cx="%g" cy="%g" r="%g"%s/>'
|
||||
% (cx, cy, r, attrs(fill, stroke, width)))
|
||||
elif kind == "line":
|
||||
(x1, y1), (x2, y2) = data
|
||||
out.append(' <line x1="%g" y1="%g" x2="%g" y2="%g"%s/>'
|
||||
% (x1, y1, x2, y2, attrs(None, stroke, width)))
|
||||
elif kind == "rrect":
|
||||
x0, y0, x1, y1, r = data
|
||||
out.append(' <rect x="%g" y="%g" width="%g" height="%g" '
|
||||
'rx="%g"%s/>'
|
||||
% (x0, y0, x1 - x0, y1 - y0, r,
|
||||
attrs(fill, stroke, width)))
|
||||
out.append("</svg>")
|
||||
return "\n".join(out) + "\n"
|
||||
|
||||
|
||||
# ----------------------------------------------------------- Ausgabe PNG
|
||||
|
||||
def to_png(shapes, size):
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
def rgb(c):
|
||||
c = c.lstrip("#")
|
||||
return tuple(int(c[i:i + 2], 16) for i in (0, 2, 4)) + (255,)
|
||||
|
||||
s = SCALE
|
||||
img = Image.new("RGBA", (int(BOX * s), int(BOX * s)), (0, 0, 0, 0))
|
||||
d = ImageDraw.Draw(img)
|
||||
|
||||
for kind, data, fill, stroke, width in shapes:
|
||||
f = rgb(fill) if fill else None
|
||||
st = rgb(stroke) if stroke else None
|
||||
w = max(1, int(width * s))
|
||||
if kind == "poly":
|
||||
pts = [(x * s, y * s) for x, y in data]
|
||||
d.polygon(pts, fill=f, outline=st, width=w if st else 0)
|
||||
elif kind == "circle":
|
||||
cx, cy, r = data
|
||||
box = [(cx - r) * s, (cy - r) * s, (cx + r) * s, (cy + r) * s]
|
||||
if f:
|
||||
d.ellipse(box, fill=f)
|
||||
if st:
|
||||
d.ellipse(box, outline=st, width=w)
|
||||
elif kind == "line":
|
||||
(x1, y1), (x2, y2) = data
|
||||
d.line([(x1 * s, y1 * s), (x2 * s, y2 * s)], fill=st, width=w)
|
||||
elif kind == "rrect":
|
||||
x0, y0, x1, y1, r = data
|
||||
d.rounded_rectangle([x0 * s, y0 * s, x1 * s, y1 * s],
|
||||
radius=r * s, fill=f,
|
||||
outline=st, width=w if st else 0)
|
||||
|
||||
return img.resize((size, size), Image.LANCZOS)
|
||||
|
||||
|
||||
def main():
|
||||
os.makedirs(OUT, exist_ok=True)
|
||||
for name, fn in ICONS.items():
|
||||
shapes = fn()
|
||||
with open(os.path.join(OUT, name + ".svg"), "w",
|
||||
encoding="utf-8") as fh:
|
||||
fh.write(to_svg(shapes))
|
||||
for size in PNG_SIZES:
|
||||
to_png(shapes, size).save(
|
||||
os.path.join(OUT, "%s_%d.png" % (name, size)),
|
||||
"PNG", optimize=True)
|
||||
print("erzeugt: %s.svg und PNG in %s"
|
||||
% (name, ", ".join("%d" % z for z in PNG_SIZES)))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user