Så skapar du ett bredspetsigt pennstreck genom att svepa en fast form längs en bana
För att skapa ett bredspetsigt pennstreck definierar du en smal, rektangulär spets, håller dess vinkel konstant och förflyttar den längs en bana. Strecket är unionen av alla positioner som rektangeln intar. Dess tjocklek förändras med rörelseriktningen, trots att spetsen aldrig ändrar storlek.
Den här guiden visar hur du konstruerar den geometrin för en vektorritning eller en accent på en figur. Du definierar spetsen, sveper den kontinuerligt längs en polylinje och genererar SVG med ett komplett Python-program. Konstruktionen undviker de glapp som kan uppstå när en pensel simuleras med separata avtryck.
Vad innebär det att svepa en fast form?
Dela upp ritningen i två indata: en bana som beskriver hur spetsens referenspunkt rör sig och en grundform som beskriver området spetsen täcker vid en position. Placera grundformens referenspunkt i origo. Vid varje position längs banan adderar du den positionen till varje punkt i grundformen.
Matematiskt är den resulterande mängden S = {p + n | p belongs to P, n belongs to N}, där P är banan och N är spetsen. Detta är en Minkowski-summa enligt punktvis definition i CGAL:s användarhandbok för 2D-Minkowski-summor.
I den här konstruktionen betyder ”fast” att både grundformen och dess orientering förblir konstanta i ritningens koordinatsystem. Spetsen förflyttas utan att vridas för att följa banan. Om referenspunkten placeras i rektangelns mitt beskriver banan den rörliga spetsens mitt.
Hur avgör spetsens vinkel och rörelseriktningen tjockleken?
Välj tre parametrar: rektangelns långa sida w, dess korta sida h och vinkeln α för dess långa kant. Ett litet positivt h ger spetsen en synlig minimitjocklek. En ideal spets formad som ett linjesegment har däremot noll tjocklek och kan lämna ett märke med noll area om den rör sig exakt i sin egen riktning.
För ett rakt bansegment med riktningsvinkeln θ är den rektangulära spetsens bredd mätt vinkelrätt mot rörelsen:
width across the stroke = w × |sin(θ − α)| + h × |cos(θ − α)|
Formeln följer av att rektangelns två vinkelräta dimensioner projiceras på normalen till banan. Den beskriver ett rakt segment eller den lokala projektionen av grundformen längs en jämn kurva. Överlappande delar av ett böjt streck kan göra dess silhuett bredare.
För en illustrativ spets som mäter 24 enheter gånger 3 enheter ger rörelse parallellt med den långa kanten en 3 enheter bred remsa. Rörelse vinkelrätt mot kanten ger en 24 enheter bred remsa. Vid 45° skillnad är bredden ungefär (24 + 3) / √2, eller 19,09 enheter. Det här är beräknade mått, inte uppmätta penselresultat.
Använd de här tre riktningarna som ett kalibreringstest innan du ritar en komplicerad kurva. De visar om vinkeln och måtten fungerar som avsett utan att du behöver tolka flera överlappande böjar.
Hur kan du svepa kontinuerligt utan att lämna glapp mellan avtrycken?
För en konvex spets som rör sig längs ett rakt segment tar du en kopia av spetsen vid varje ändpunkt och beräknar den konvexa höljen av deras samlade hörn. Det höljet är exakt det område som sveps över vid förflyttning längs segmentet.
Anledningen är enkel: varje mellanliggande placering är en linjär interpolering mellan placeringarna vid ändpunkterna. Eftersom spetsen är konvex innehåller höljet exakt dessa interpolerade positioner. Den här segmentkonstruktionen är en direkt följd av definitionen av Minkowski-summan, inte en separat penselfunktion.
För en rektangel ger varje segment bara åtta möjliga punkter: fyra hörn i början och fyra i slutet. Beräkna deras hölje, upprepa för varje intilliggande par av banpunkter och kombinera de resulterande polygonerna. Intilliggande svep delar hela spetsplaceringen vid sin gemensamma ändpunkt, så geometrin täcker området kontinuerligt.
Beräkna inte ett enda konvext hölje runt hela banan. Det skulle fylla området över böjar och sudda ut avsedda inskärningar. Höljet hör till varje enskilt segment; hela strecket är unionen av dem.
En konkav spets kräver en annan metod. Om du tar dess hölje fylls inskärningarna igen. Dela upp den i konvexa delar, svep varje del och kombinera resultaten. CGAL beskriver den närliggande metoden med uppdelning och union för Minkowski-summor av icke-konvexa polygoner.
Hur implementerar du svepet i Python?
Följande kompletta program kräver Python 3 och dess standardbibliotek. Det definierar en exempelbana med fem punkter, konstruerar en rektangulär spets och skriver ut ett SVG-dokument med fyra fyllda sveppolygoner. Spara det som broad_edge.py och kör sedan python broad_edge.py > stroke.svg för att skapa ritningen.
Koordinaterna följer SVG-konventionen som används här: x ökar åt höger och y ökar nedåt. Därför lutar en positiv vinkel i det här programmet spetsen medurs på skärmen. Andra ritgränssnitt kan använda en annan vinkelkonvention.
from math import cos, sin, radians
def hull(points):
pts = sorted(set(points))
if len(pts) <= 1:
return pts
def cross(o, a, b):
return ((a[0] - o[0]) * (b[1] - o[1])
- (a[1] - o[1]) * (b[0] - o[0]))
def half(sequence):
out = []
for p in sequence:
while len(out) >= 2 and cross(out[-2], out[-1], p) <= 0:
out.pop()
out.append(p)
return out
return half(pts)[:-1] + half(reversed(pts))[:-1]
def sweep(path, width=24, thickness=3, angle=30):
if not path or width <= 0 or thickness <= 0:
raise ValueError(
'Provide a nonempty path and positive nib dimensions'
)
a = radians(angle)
u = (cos(a), sin(a))
v = (-sin(a), cos(a))
nib = [
(s * width * u[0] / 2 + t * thickness * v[0] / 2,
s * width * u[1] / 2 + t * thickness * v[1] / 2)
for s, t in [(-1, -1), (1, -1), (1, 1), (-1, 1)]
]
pairs = list(zip(path, path[1:])) or [(path[0], path[0])]
return [
hull([(p[0] + q[0], p[1] + q[1])
for p in (start, end) for q in nib])
for start, end in pairs
]
path = [(40, 45), (85, 125), (140, 150), (195, 110), (245, 45)]
polygons = sweep(path)
all_points = [p for polygon in polygons for p in polygon]
x0, y0 = (min(p[i] for p in all_points) - 5 for i in (0, 1))
x1, y1 = (max(p[i] for p in all_points) + 5 for i in (0, 1))
svg = [
'<svg xmlns="http://www.w3.org/2000/svg" '
f'viewBox="{x0} {y0} {x1 - x0} {y1 - y0}">',
'<g fill="#23364d" stroke="none">'
]
for polygon in polygons:
coordinates = ' '.join(f'{x:.6f},{y:.6f}' for x, y in polygon)
svg.append(f'<polygon points="{coordinates}"/>')
svg.extend(['</g>', '</svg>'])
print('\n'.join(svg))
Programmet hanterar en bana med en enda punkt genom att rita en spetsgrundform. Upprepade identiska punkter i följd ger också en grundform, utan att en riktning behöver beräknas. Tomma banor och spetsmått som inte är positiva utlöser ett fel. Ange ändliga numeriska koordinatpar; det här kompakta exemplet validerar inte importerade banor.
Konstruktionen kördes med exempelkoordinaterna: den gav fyra sveppolygoner med sex hörn vardera, och den genererade SVG-filen kunde parsas som XML. Även fall med en enda punkt och upprepade punkter kontrollerades. Kontrollerna verifierar konstruktionen och dokumentstrukturen, men utgör inget visuellt renderingstest.
Hur hanterar du kurvor, hörn och ändpunkter?
Exemplet är exakt för sin polylinje, med förbehåll för flyttalsaritmetik och koordinatavrundning. Om du vill använda en Bézierkurva approximerar du den först med raka segment och använder sedan samma svep. Lägg till fler segment där kurvan böjer skarpt i stället för att fördela alla sampel jämnt längs kurvparametern.
Välj approximationstolerans utifrån den avsedda utstorleken. En avvikelse på en kvarts pixel kan till exempel vara ett inledande mål för visning, följt av granskning i den största planerade skalan. Det är ett illustrativt tekniskt val, inte en universell kvalitetsgräns. Observera att fler delsegment längs ett redan rakt segment inte förändrar dess svepta area.
Håll separata pennstreck åtskilda. Om punkter från två frånkopplade streck skickas in i samma anrop skapas ett oönskat sammanbindande svep. Generera varje streck separat och samla polygonerna i utdata.
Vid en ändpunkt behåller spetsen hela sin grundform. I ett hörn överlappar de två intilliggande svepen. Den här modellen lägger inte till någon extra geringsformad eller rundad avslutning. SVG:s vanliga streckegenskaper definierar egna konstruktioner för avslut och hörn, så att lägga till dem skulle ge en annan geometri än det här svepet med fast spets. Se SVG 2:s definitioner av streckavslut och hörn.
När behöver du slå ihop polygonerna?
Programmet exporterar överlappande fyllda polygoner. För en enfärgad förhandsvisning motsvarar de den svepta täckningen utan att ett bibliotek för polygonunion behövs. De förblir dock separata redigerbara objekt; programmet beräknar inte en enda sammanslagen kontur.
Om du behöver en ren silhuett för fortsatt konturredigering beräknar du en boolesk union av alla segmentpolygoner. Bevara eventuella hål i resultatet. En slingrande bana kan omsluta ett omålat område, och om alla inneslutna konturer fylls förändras strecket.
Transparens kräver också eftertanke. Om partiell fill-opacity tillämpas separat på överlappande polygoner kan överlappningsområden bli mörkare. För jämn genomskinlighet tillämpar du opacitet på den omslutande gruppen eller slår ihop geometrin innan du tillämpar fyllnadsopacitet. SVG skiljer mellan objektens fyllnadsopacitet och gruppopacitet i sin målningsspecifikation.
| Avsett resultat | Lämpligt nästa steg |
|---|---|
| Enfärgad förhandsvisning | Behåll de fyllda sveppolygonerna |
| Jämnt genomskinlig förhandsvisning | Tillämpa opacitet på den omslutande gruppen |
| En redigerbar silhuett | Slå ihop sveppolygonerna med en boolesk union |
| Anpassad konkav spets | Dela upp spetsen innan den sveps |
| Jämnt böjt streck | Approximera kurvan och svep sedan segmenten |
Hur felsöker du ett oväntat streck?
Börja med kalibreringsriktningarna och granska sedan banan och konstruktionen av utdata. Ändra en indata i taget så att orsaken till en synlig skillnad förblir tydlig.
- Tjockleken är nästan konstant: Kontrollera om spetsen roterar med banan. Vinkeln måste förbli fast i ritningens koordinatsystem.
- Synliga glapp uppstår mellan märkena: Ersätt enskilda avtryck med konstruktionen med segmenthöljen.
- En bred kil fyller insidan av en böj: Kontrollera om du av misstag beräknade ett hölje över flera bansegment samtidigt.
- Ändarna ser rundade ut: Kontrollera om ett vanligt streck har lagts till eller om en annan spetsgrundform används.
- En jämn ursprungskurva ser kantig ut: Förfina polylinjeapproximationen runt böjarna.
- Oväntade förbindelser uppstår: Håll frånkopplade banor i separata svepanrop.
Om du jämför resultatet med Inkscapes kalligrafiverktyg innehåller dokumentationen användbara inställningar att kontrollera: Fixation 100 håller vinkeln fast, Thinning 0 tar bort hastighetsbaserad variation i bredden, och tryck- och lutningsreglagen kan ändra bredd och vinkel. Inaktivera dessa varierande indata när du undersöker hur en fast spets beter sig. Inställningarna beskriver verktygets reglage; de visar inte att verktygets resultat är identiskt med den här rektangelkonstruktionen. Se Inkscapes dokumentation för kalligrafiverktygsfältet.
Ett tillförlitligt bredspetsigt streck börjar med en enkel kalibreringsbana och en tydligt definierad grundform. När testerna parallellt, vinkelrätt och diagonalt fungerar som de ska använder du samma segmentsvep på den valda banan, förfinar böjda delar efter utstorleken och slår ihop polygonerna när nästa redigeringssteg kräver en enda silhuett.
