Metlivi-blog

Een brede penstreek maken door een vaste vorm langs een pad te bewegen

Definieer een smalle rechthoekige penpunt, houd de hoek ervan constant en verplaats deze langs een pad om een brede penstreek te maken. De streek is de vereniging van alle posities die door die rechthoek worden ingenomen. De dikte verandert met de bewegingsrichting, ook al verandert de penpunt zelf nooit van grootte.

Deze gids laat zien hoe je die geometrie opbouwt voor een vectortekening of een accent voor een personage. Je definieert de penpunt, beweegt deze continu langs een polylijn en genereert SVG met een volledig Python-programma. Bij de kernconstructie ontstaan geen tussenruimtes die kunnen optreden wanneer een penseel wordt nagebootst met afzonderlijke stempels.

29 oktober 20268 minuten leestijdDoor Metlivi Editorial Team
Sectie 1

Wat betekent het om een vaste vorm te bewegen?

Splits de tekening op in twee invoerwaarden: een pad dat beschrijft waar het referentiepunt van de penpunt naartoe beweegt, en een voetafdruk die het gebied beschrijft dat de penpunt op één positie bedekt. Plaats het referentiepunt van de voetafdruk in de oorsprong. Tel op elke positie van het pad die positie op bij elk punt in de voetafdruk.

Wiskundig is de resulterende verzameling S = {p + n | p belongs to P, n belongs to N}, waarbij P het pad is en N de penpunt. Dit is een Minkowski-som, volgens de puntsgewijze definitie in de gebruikershandleiding voor 2D-Minkowski-sommen van CGAL.

In deze constructie betekent ‘vast’ dat zowel de voetafdruk als de oriëntatie ervan gelijk blijven in de tekenruimte. De penpunt verschuift zonder mee te draaien met het pad. Als je het referentiepunt in het midden van de rechthoek houdt, beschrijft het pad het midden van de bewegende penpunt.

Sectie 2

Hoe bepalen de hoek van de penpunt en de bewegingsrichting de dikte?

Kies drie parameters: de lange afmeting van de rechthoek w, de korte afmeting h en de hoek α van de lange zijde. Een kleine positieve h geeft de penpunt een zichtbare minimumdikte. Een ideale penpunt in de vorm van een lijnstuk heeft daarentegen geen dikte en kan een markering met nul oppervlakte achterlaten wanneer deze precies in zijn eigen richting beweegt.

Voor een recht padsegment met richtingshoek θ is de breedte van de rechthoekige penpunt, gemeten loodrecht op de beweging:

width across the stroke = w × |sin(θ − α)| + h × |cos(θ − α)|

Deze formule volgt uit het projecteren van de twee loodrechte afmetingen van de rechthoek op de normaal van het pad. Ze beschrijft een recht segment of de lokale projectie van de voetafdruk langs een vloeiende curve; overlappende delen van een gekromde streek kunnen het totale silhouet breder maken.

Een penpunt van 24 bij 3 eenheden levert bijvoorbeeld een strook van 3 eenheden breed op wanneer deze parallel aan de lange zijde beweegt. Bij loodrechte beweging ontstaat een strook van 24 eenheden breed. Bij een verschil van 45° is de breedte ongeveer (24 + 3) / √2, oftewel 19,09 eenheden. Dit zijn berekende afmetingen, geen gemeten penseelresultaten.

Gebruik deze drie richtingen als kalibratietest voordat je een ingewikkelde curve tekent. Zo zie je of de hoek en afmetingen zich gedragen zoals bedoeld, zonder dat je meerdere overlappende bochten hoeft te interpreteren.

Sectie 3

Hoe beweeg je de penpunt continu zonder tussenruimtes tussen stempels?

Neem voor een convexe penpunt die langs één recht segment beweegt een kopie van de penpunt op elk eindpunt en bereken de convexe omhulling van de gecombineerde hoekpunten. Die omhulling is precies het gebied dat tijdens de verplaatsing langs het segment wordt bestreken.

De reden is eenvoudig: elke tussenliggende plaatsing is een lineaire interpolatie tussen de plaatsingen op de eindpunten. Omdat de penpunt convex is, bevat de omhulling precies die geïnterpoleerde posities. Deze segmentconstructie volgt rechtstreeks uit de definitie van de Minkowski-som en is geen afzonderlijke penseelfunctie.

Voor een rechthoek levert elk segment slechts acht kandidaatpunten op: vier hoeken aan het begin en vier aan het einde. Bereken hun omhulling, herhaal dit voor elk opeenvolgend paar padpunten en combineer de resulterende polygonen. Aangrenzende sweeps delen de volledige penpuntplaatsing op hun gemeenschappelijke eindpunt, waardoor de geometrie het hele gebied continu bedekt.

Bereken niet één convexe omhulling rond het hele pad. Daarmee zou je de ruimte tussen bochten opvullen en bedoelde inkepingen laten verdwijnen. De omhulling hoort bij elk afzonderlijk segment; de volledige streek is de vereniging daarvan.

Voor een concave penpunt is een andere aanpak nodig. De omhulling zou de inkepingen opvullen. Splits de penpunt op in convexe delen, beweeg elk deel langs het pad en combineer de resultaten. CGAL beschrijft een verwante aanpak met decompositie en vereniging voor Minkowski-sommen van niet-convexe polygonen.

Sectie 4

Hoe implementeer je de sweep in Python?

Het volgende volledige programma vereist Python 3 en de standaardbibliotheek. Het definieert een voorbeeldpad met vijf punten, maakt een rechthoekige penpunt en drukt een SVG-document af met vier gevulde sweep-polygonen. Sla het op als broad_edge.py en voer vervolgens python broad_edge.py > stroke.svg uit om de tekening te maken.

De coördinaten volgen hier de SVG-conventie: x neemt naar rechts toe en y naar beneden. Een positieve hoek kantelt de penpunt in dit programma dus met de klok mee op het scherm. Andere tekenprogramma’s kunnen een andere hoekconventie gebruiken.

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))

Bij één padpunt tekent het programma één voetafdruk van de penpunt. Ook bij herhaalde opeenvolgende punten ontstaat een voetafdruk; een richtingsberekening is daarvoor niet nodig. Lege paden en niet-positieve afmetingen van de penpunt geven een foutmelding. Geef eindige numerieke coördinatenparen op; dit compacte voorbeeld valideert geen geïmporteerde padgegevens.

De constructie is uitgevoerd met de voorbeeldcoördinaten: ze leverde vier sweep-polygonen met elk zes hoekpunten op, en de gegenereerde SVG is succesvol als XML geparseerd. Ook gevallen met één punt en herhaalde punten zijn gecontroleerd. Deze controles verifiëren de constructie en de documentstructuur; ze vormen geen visuele rendertest.

Sectie 5

Hoe ga je om met curves, hoeken en eindpunten?

Het voorbeeld is exact voor de polylijn, afgezien van drijvendekommaberekeningen en afronding van coördinaten. Als je een Béziercurve wilt gebruiken, benader deze dan eerst met rechte segmenten en pas vervolgens dezelfde sweep toe. Voeg meer segmenten toe waar de curve scherp buigt, in plaats van alle punten gelijkmatig over de curveparameter te verdelen.

Kies de benaderingstolerantie op basis van het beoogde uitvoerformaat. Een afwijking van een kwart pixel kan bijvoorbeeld een eerste doel zijn voor weergave, gevolgd door inspectie op de grootste geplande schaal. Dit is een illustratieve technische keuze, geen universele kwaliteitsdrempel. Extra onderverdelingen langs een al recht segment veranderen het bestreken gebied niet.

Houd afzonderlijke penstreken gescheiden. Als je punten van twee losstaande streken in één aanroep doorgeeft, ontstaat er een ongewenste verbindende sweep. Genereer elke streek afzonderlijk en verzamel de polygonen in de uitvoer.

Op een eindpunt behoudt de penpunt zijn volledige voetafdruk. Bij een hoek overlappen de twee aangrenzende sweeps. Dit model voegt geen extra verstekverbinding of afgeronde eindkap toe. De gebruikelijke lijneigenschappen van SVG bepalen hun eigen eindkappen en verbindingen; als je die toevoegt, introduceer je geometrie die buiten deze sweep met vaste penpunt valt. Zie de SVG 2-definities van lijneindkappen en verbindingen.

Sectie 6

Wanneer moet je de polygonen samenvoegen?

Het programma exporteert overlappende gevulde polygonen. Voor een voorbeeld met een effen kleur geven deze de bestreken dekking weer zonder dat een bibliotheek voor polygonenverenigingen nodig is. Ze blijven echter afzonderlijke bewerkbare objecten; het programma berekent geen enkele samengevoegde begrenzing.

Als je een strak silhouet nodig hebt om de omtrek daarna te bewerken, bereken dan de booleaanse vereniging van alle segmentpolygonen. Behoud eventuele gaten in het resultaat. Een lusvormig pad kan een ongeverfd gebied omsluiten; door alle omsloten contouren te vullen, zou de streek veranderen.

Ook transparantie vereist een bewuste aanpak. Als je fill-opacity afzonderlijk gedeeltelijk toepast op overlappende polygonen, kunnen de overlappende gebieden donkerder worden. Pas voor een gelijkmatige transparantie de dekking toe op de omhullende groep, of voeg de geometrie samen voordat je de vuldekking toepast. SVG maakt in zijn specificatie voor schilderbewerkingen onderscheid tussen de vuldekking van een object en de dekking van een groep.

Beoogd resultaat Geschikte vervolgstap
Voorbeeld met effen kleur Behoud de gevulde sweep-polygonen
Gelijkmatig transparant voorbeeld Pas de dekking toe op de omhullende groep
Eén bewerkbaar silhouet Voeg de sweep-polygonen booleaans samen
Aangepaste concave penpunt Splits de penpunt op voordat je deze beweegt
Vloeiende gekromde streek Benader de curve en beweeg de penpunt vervolgens langs de segmenten
Sectie 7

Hoe spoor je een onverwachte penstreek op?

Begin met de kalibratierichtingen en inspecteer daarna het pad en de uitvoerconstructie. Verander telkens één invoerwaarde, zodat duidelijk blijft waar een zichtbaar verschil vandaan komt.

  • De dikte blijft vrijwel constant: Controleer of de penpunt met het pad meedraait. De hoek moet vast blijven in de tekenruimte.
  • Er verschijnen zichtbare tussenruimtes tussen markeringen: Vervang afzonderlijke stempels door de segmentconstructie met omhulling.
  • Een brede wig vult de binnenkant van een bocht: Controleer of je per ongeluk één omhulling over meerdere padsegmenten tegelijk hebt berekend.
  • De uiteinden lijken afgerond: Controleer of er een gewone lijn of een andere voetafdruk van de penpunt is toegevoegd.
  • Een vloeiende broncurve ziet er hoekig uit: Verfijn de polylijnbenadering rond de bochten.
  • Er verschijnen onverwachte verbindingsstukken: Houd losstaande paden in afzonderlijke sweep-aanroepen.

Als je het resultaat vergelijkt met het kalligrafiegereedschap van Inkscape, bevat de documentatie daarvan nuttige parametercontroles: Fixation 100 houdt de hoek vast, Thinning 0 verwijdert breedtevariatie op basis van snelheid, en de druk- en kantelregelaars kunnen de breedte en hoek veranderen. Schakel deze variabele invoerwaarden uit wanneer je het gedrag van een penpunt met vaste hoek onderzoekt. Deze instellingen beschrijven de regelaars van het gereedschap; ze bewijzen niet dat de uitvoer identiek is aan deze rechthoekige constructie. Zie de documentatie van de kalligrafiewerkbalk van Inkscape.

Een betrouwbare brede penstreek begint met een eenvoudig kalibratiepad en een duidelijk gedefinieerde voetafdruk. Zodra de tests met parallelle, loodrechte en diagonale bewegingen het juiste gedrag laten zien, pas je dezelfde segmentsweep toe op het gekozen pad. Verfijn gekromde delen op basis van de uitvoerschaal en voeg de polygonen samen wanneer de volgende bewerkingsstap één silhouet vereist.

Sectie 8

Bronnen

Gerelateerde artikelen

Blijf dit onderwerp verkennen