Blog de Metlivi

Cómo crear un trazo de plumilla de borde ancho desplazando una forma fija a lo largo de un recorrido

Para crear un trazo de plumilla de borde ancho, define una plumilla rectangular estrecha, mantén constante su ángulo y desplázala a lo largo de un recorrido. El trazo es la unión de todas las posiciones que ocupa ese rectángulo. Su grosor cambia según la dirección del movimiento, aunque la plumilla nunca cambie de tamaño.

Esta guía muestra cómo construir esa geometría para un dibujo vectorial o un acento de personaje. Definirás la plumilla, la desplazarás continuamente a lo largo de una polilínea y generarás SVG con un programa completo de Python. La construcción central evita los huecos que pueden aparecer al simular un pincel con sellos separados.

29 de octubre de 20268 min de lecturaPor Metlivi Editorial Team
Sección 1

¿Qué significa desplazar una forma fija?

Separa el dibujo en dos entradas: un recorrido que describe por dónde se desplaza el punto de referencia de la plumilla y una huella que describe el área cubierta por la plumilla en una posición. Coloca el punto de referencia de la huella en el origen. En cada posición del recorrido, suma esa posición a todos los puntos de la huella.

Matemáticamente, el conjunto resultante es S = {p + n | p belongs to P, n belongs to N}, donde P es el recorrido y N es la plumilla. Esto es una suma de Minkowski, según la definición punto a punto del manual de usuario de sumas de Minkowski 2D de CGAL.

En esta construcción, «fija» significa que tanto la huella como su orientación permanecen constantes en las coordenadas del dibujo. La plumilla se traslada sin girar para seguir el recorrido. Si mantienes el punto de referencia en el centro del rectángulo, el recorrido describe el centro de la plumilla en movimiento.

Sección 2

¿Cómo determinan el ángulo de la plumilla y la dirección de movimiento el grosor?

Elige tres parámetros: la dimensión larga del rectángulo w, su dimensión corta h y el ángulo α de su lado largo. Un h positivo pequeño da a la plumilla un grosor mínimo visible. En cambio, una plumilla ideal con forma de segmento de línea tiene grosor cero y puede dejar una marca de área nula si se desplaza exactamente en su propia dirección.

Para un segmento de recorrido recto con ángulo de dirección θ, el ancho de la plumilla rectangular medido perpendicularmente al movimiento es:

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

Esta fórmula se obtiene al proyectar las dos dimensiones perpendiculares del rectángulo sobre la normal al recorrido. Describe un segmento recto o la proyección local de la huella a lo largo de una curva suave; las partes superpuestas de un trazo curvo pueden hacer que su silueta total sea más ancha.

Con una plumilla ilustrativa de 24 unidades por 3, el movimiento paralelo al lado largo produce una franja de 3 unidades de ancho. El movimiento perpendicular produce una franja de 24 unidades de ancho. Con una diferencia de 45°, el ancho es aproximadamente (24 + 3) / √2, es decir, 19,09 unidades. Son dimensiones calculadas, no resultados medidos de un pincel.

Usa esas tres direcciones como prueba de calibración antes de dibujar una curva complicada. Te permiten comprobar si el ángulo y las dimensiones se comportan como esperas sin tener que interpretar varios giros superpuestos.

Sección 3

¿Cómo puedes desplazar la plumilla de forma continua sin dejar huecos entre sellos?

Para mover una plumilla convexa a lo largo de un único segmento recto, toma una copia de la plumilla en cada extremo y calcula la envolvente convexa de los vértices combinados. Esa envolvente corresponde exactamente al área barrida durante la traslación a lo largo del segmento.

La razón es sencilla: cada posición intermedia es una interpolación lineal entre las posiciones de los extremos. Como la plumilla es convexa, la envolvente contiene exactamente esas posiciones interpoladas. Esta construcción para cada segmento es una consecuencia directa de la definición de suma de Minkowski, no una función independiente del pincel.

En el caso de un rectángulo, cada segmento aporta solo ocho puntos candidatos: cuatro esquinas al inicio y cuatro al final. Calcula su envolvente, repite el proceso para cada par de puntos consecutivos del recorrido y combina los polígonos resultantes. Los desplazamientos adyacentes comparten la posición completa de la plumilla en el extremo común, por lo que la geometría tiene una cobertura continua.

No calcules una única envolvente convexa alrededor de todo el recorrido. Eso rellenaría las curvas y eliminaría los entrantes previstos. La envolvente corresponde a cada segmento individual; el trazo completo es la unión de todas ellas.

Una plumilla cóncava requiere un tratamiento distinto. Calcular su envolvente rellenaría sus hendiduras. Descompónla en piezas convexas, desplaza cada pieza y combina los resultados. CGAL documenta un método relacionado de descomposición y unión para las sumas de Minkowski de polígonos no convexos.

Sección 4

¿Cómo se implementa el desplazamiento en Python?

El siguiente programa completo requiere Python 3 y su biblioteca estándar. Define un recorrido de muestra de cinco puntos, construye una plumilla rectangular e imprime un documento SVG que contiene cuatro polígonos de desplazamiento rellenos. Guárdalo como broad_edge.py y luego ejecuta python broad_edge.py > stroke.svg para crear el dibujo.

Aquí se sigue la convención de coordenadas de SVG: x aumenta hacia la derecha e y hacia abajo. Por consiguiente, un ángulo positivo en este programa inclina la plumilla en sentido horario en pantalla. En otras interfaces de dibujo, los ángulos pueden seguir una convención distinta.

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

El programa maneja un recorrido de un solo punto dibujando una única huella de la plumilla. Los puntos consecutivos repetidos también producen una huella, sin necesidad de calcular una dirección. Los recorridos vacíos y las dimensiones de plumilla no positivas generan un error. Proporciona pares de coordenadas numéricas finitas; este ejemplo compacto no valida los datos de recorrido importados.

La construcción se ejecutó con las coordenadas de muestra: produjo cuatro polígonos de desplazamiento de seis vértices, y el SVG generado se analizó correctamente como XML. También se comprobaron los casos de un solo punto y de puntos repetidos. Estas comprobaciones verifican la construcción y la estructura del documento; no constituyen una prueba de renderizado visual.

Sección 5

¿Cómo se manejan las curvas, las esquinas y los extremos?

El ejemplo es exacto para su polilínea, salvo por la aritmética de coma flotante y el redondeo de coordenadas. Para usar una curva de Bézier, primero aproxímala con segmentos rectos y luego aplica el mismo desplazamiento. Añade más segmentos donde la curva se doble bruscamente, en lugar de distribuir todas las muestras de manera uniforme en el parámetro de la curva.

Elige la tolerancia de aproximación según el tamaño de salida previsto. Por ejemplo, una desviación de un cuarto de píxel puede servir como objetivo inicial de visualización, seguida de una inspección a la escala máxima prevista. Es una decisión de ingeniería ilustrativa, no un umbral de calidad universal. Es importante señalar que añadir subdivisiones a un segmento que ya es recto no cambia el área barrida.

Mantén separados los trazos independientes levantando el lápiz entre ellos. Si pasas los puntos de dos trazos desconectados en una sola llamada, se crearía un desplazamiento de conexión no deseado. Genera cada trazo por separado y añade sus polígonos a la salida.

En un extremo, la plumilla conserva toda su huella. En una esquina, los dos desplazamientos contiguos se superponen. Este modelo no añade una unión en inglete ni un extremo redondeado. Las propiedades de trazo habituales de SVG definen sus propias construcciones de extremos y uniones, así que añadirlas introduciría una geometría ajena a este desplazamiento con plumilla fija. Consulta las definiciones de extremos y uniones de trazo de SVG 2.

Sección 6

¿Cuándo es necesario unir los polígonos?

El programa exporta polígonos rellenos superpuestos. Para una vista previa de color sólido, representan la cobertura barrida sin necesidad de una biblioteca de unión de polígonos. Sin embargo, siguen siendo objetos editables por separado: el programa no calcula un único contorno consolidado.

Si necesitas una silueta limpia para editar su contorno después, calcula la unión booleana de todos los polígonos de los segmentos. Conserva los huecos que queden en el resultado. Un recorrido con bucles puede rodear una región sin pintar, y rellenar todos los contornos cerrados cambiaría el trazo.

La transparencia también requiere un manejo deliberado. Aplicar fill-opacity parcial por separado a polígonos superpuestos puede oscurecer las zonas de solapamiento. Para obtener una translucidez uniforme, aplica la opacidad al grupo contenedor o une la geometría antes de aplicar la opacidad del relleno. SVG distingue entre la opacidad del relleno de un objeto y la opacidad de un grupo en su especificación de pintura.

Resultado deseado Siguiente paso adecuado
Vista previa de color sólido Mantener los polígonos de desplazamiento rellenos
Vista previa translúcida uniforme Aplicar la opacidad al grupo contenedor
Una única silueta editable Unir los polígonos de desplazamiento mediante una operación booleana
Plumilla personalizada cóncava Descomponer la plumilla antes de desplazarla
Trazo curvo suave Aproximar la curva y luego desplazar sus segmentos
Sección 7

¿Cómo se diagnostica un trazo inesperado?

Empieza con las direcciones de calibración y luego inspecciona el recorrido y la construcción de salida. Cambia una entrada a la vez para que quede claro el origen de cualquier diferencia visible.

  • El grosor se mantiene casi constante: Comprueba si la plumilla gira con el recorrido. Su ángulo debe permanecer fijo en las coordenadas del dibujo.
  • Aparecen huecos visibles entre las marcas: Sustituye los sellos aislados por la construcción de envolvente por segmentos.
  • Una cuña ancha rellena el interior de una curva: Comprueba si calculaste por accidente una envolvente conjunta para varios segmentos del recorrido.
  • Los extremos parecen redondeados: Comprueba si añadiste un trazo habitual o una huella distinta para la plumilla.
  • Una curva de origen suave se ve angulosa: Refina la aproximación poligonal alrededor de las curvas.
  • Aparecen conectores inesperados: Mantén los recorridos desconectados en llamadas de desplazamiento separadas.

Si comparas el resultado con la herramienta Caligrafía de Inkscape, su documentación ofrece comprobaciones útiles de los parámetros: Fijación 100 mantiene fijo el ángulo, Adelgazamiento 0 elimina la variación del ancho según la velocidad, y los controles de presión e inclinación pueden alterar el ancho y el ángulo. Desactiva esas entradas variables al examinar el comportamiento de una plumilla fija. Estos ajustes describen los controles de la herramienta; no demuestran que su resultado sea idéntico a esta construcción rectangular. Consulta la documentación de la barra de herramientas de Caligrafía de Inkscape.

Un trazo de plumilla de borde ancho fiable empieza con un recorrido de calibración sencillo y una huella claramente definida. Cuando las pruebas paralela, perpendicular y diagonal den el resultado correcto, aplica el mismo desplazamiento por segmentos al recorrido elegido, refina las secciones curvas según la escala de salida y une los polígonos cuando el siguiente paso de edición requiera una única silueta.

Sección 8

Fuentes

Lecturas relacionadas

Sigue explorando este tema