102 lines
3.0 KiB
Python
102 lines
3.0 KiB
Python
"""
|
|
Cairo-basiertes Rendering der Vektorpfade.
|
|
Erzeugt aus den manipulierten Pfaden ein neues Bild (für Preview und Export).
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import cairo
|
|
import numpy as np
|
|
from PySide6.QtGui import QImage, QPixmap
|
|
from svgpathtools import Path as SvgPath, Line, CubicBezier, QuadraticBezier
|
|
|
|
from core.logo_model import LogoProject, GlyphGroup
|
|
|
|
|
|
def _walk_svg_path(ctx: cairo.Context, svg_path: SvgPath):
|
|
"""Zeichnet einen svgpathtools-Pfad auf einen Cairo-Context."""
|
|
for seg in svg_path:
|
|
start = seg.start
|
|
if isinstance(seg, Line):
|
|
ctx.line_to(seg.end.real, seg.end.imag)
|
|
elif isinstance(seg, CubicBezier):
|
|
ctx.curve_to(
|
|
seg.control1.real, seg.control1.imag,
|
|
seg.control2.real, seg.control2.imag,
|
|
seg.end.real, seg.end.imag,
|
|
)
|
|
elif isinstance(seg, QuadraticBezier):
|
|
# Quadratische Bezier → kubische Approximation
|
|
p0 = seg.start
|
|
p1 = seg.control
|
|
p2 = seg.end
|
|
cp1 = p0 + (2 / 3) * (p1 - p0)
|
|
cp2 = p2 + (2 / 3) * (p1 - p2)
|
|
ctx.curve_to(
|
|
cp1.real, cp1.imag,
|
|
cp2.real, cp2.imag,
|
|
p2.real, p2.imag,
|
|
)
|
|
|
|
|
|
def render_project(project: LogoProject) -> np.ndarray:
|
|
"""
|
|
Rendert das gesamte Projekt mit Cairo und gibt RGBA-Pixels zurück.
|
|
Berücksichtigt globale Strichdicke und Buchstabenabstand.
|
|
"""
|
|
w = max(project.width, 1)
|
|
h = max(project.height, 1)
|
|
|
|
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h)
|
|
ctx = cairo.Context(surface)
|
|
|
|
ctx.set_source_rgba(0, 0, 0, 0)
|
|
ctx.paint()
|
|
|
|
ctx.set_source_rgba(0, 0, 0, 1)
|
|
|
|
cumulative_offset = 0.0
|
|
for glyph in project.glyphs:
|
|
ctx.save()
|
|
|
|
spacing = project.global_letter_spacing + glyph.offset_x
|
|
ctx.translate(cumulative_offset + spacing, glyph.offset_y)
|
|
cumulative_offset += spacing
|
|
|
|
stroke_w = project.global_stroke_width * glyph.stroke_width_factor
|
|
ctx.set_line_width(stroke_w)
|
|
|
|
for svg_path in glyph.paths:
|
|
if not svg_path:
|
|
continue
|
|
first_pt = svg_path[0].start
|
|
ctx.move_to(first_pt.real, first_pt.imag)
|
|
_walk_svg_path(ctx, svg_path)
|
|
ctx.close_path()
|
|
|
|
ctx.fill_preserve()
|
|
if stroke_w > 0:
|
|
ctx.stroke()
|
|
else:
|
|
ctx.new_path()
|
|
|
|
ctx.restore()
|
|
|
|
buf = surface.get_data()
|
|
arr = np.frombuffer(buf, dtype=np.uint8).reshape((h, w, 4)).copy()
|
|
# Cairo liefert BGRA, wir brauchen RGBA
|
|
arr[:, :, [0, 2]] = arr[:, :, [2, 0]]
|
|
return arr
|
|
|
|
|
|
def render_to_qpixmap(project: LogoProject) -> QPixmap:
|
|
"""Rendert das Projekt und gibt ein QPixmap zurück."""
|
|
pixels = render_project(project)
|
|
h, w, _ = pixels.shape
|
|
qimg = QImage(
|
|
pixels.data.tobytes(),
|
|
w, h,
|
|
4 * w,
|
|
QImage.Format.Format_RGBA8888,
|
|
)
|
|
return QPixmap.fromImage(qimg)
|