diff --git a/kisscut_gui.py b/kisscut_gui.py index e1acc43..0a62173 100644 --- a/kisscut_gui.py +++ b/kisscut_gui.py @@ -244,6 +244,187 @@ def export_mask_as_bezier_svg(mask, path, rdp_epsilon_mm=0.2, spline_smooth=8.0, f.write(svg) return smooth_poly +# --- Commented reference helpers from old code/ for future experiments --- +# These stay commented so the current workflow remains unchanged, but the +# snippets can be copied back in without keeping the old files around. + +# def fit_spline_polyline(points, num_out=200, smooth=5.0): +# """SciPy spline fit used by an earlier GUI version (periodic curve).""" +# x, y = points[:, 0], points[:, 1] +# tck, _ = splprep([x, y], s=smooth, per=True) +# unew = np.linspace(0, 1.0, num_out) +# out = splev(unew, tck) +# return np.vstack([out[0], out[1]]).T + +# def catmull_rom_to_beziers(points, closed=True, max_handle_frac=0.5): +# """Catmull–Rom to cubic Bézier conversion with handle clamping.""" +# points = np.asarray(points) +# n = len(points) +# beziers = [] +# if closed: +# pts = np.vstack([points[-1], points, points[:2]]) +# rng = range(1, n + 1) +# else: +# pts = np.vstack([points[0], points, points[-1]]) +# rng = range(1, n) +# for i in rng: +# p0, p1, p2, p3 = pts[i - 1], pts[i], pts[i + 1], pts[i + 2] +# bp0 = p1 +# v1 = (p2 - p0) / 6.0 +# v2 = (p3 - p1) / 6.0 +# seg1 = np.linalg.norm(p2 - p1) +# seg0 = np.linalg.norm(p1 - p0) +# seg2 = np.linalg.norm(p3 - p2) +# max_len1 = max_handle_frac * min(seg1, seg0) +# max_len2 = max_handle_frac * min(seg2, seg1) +# v1_len = np.linalg.norm(v1) +# v2_len = np.linalg.norm(v2) +# if v1_len > max_len1 and v1_len > 0: +# v1 = v1 * (max_len1 / v1_len) +# if v2_len > max_len2 and v2_len > 0: +# v2 = v2 * (max_len2 / v2_len) +# bp1 = p1 + v1 +# bp2 = p2 - v2 +# bp3 = p2 +# beziers.append([bp0, bp1, bp2, bp3]) +# return beziers + +# def export_polyline_svg(points, path, width, height): +# """Quick SVG export of a polyline outline for debugging.""" +# d = "M " + " L ".join(f"{x:.2f},{y:.2f}" for x, y in points) + " Z" +# svg = f'\\n' +# svg += f'\\n' +# with open(path, "w") as f: +# f.write(svg) + +# def sample_polyline(points, N=1000): +# """Evenly sample points along a polyline.""" +# points = np.asarray(points) +# dists = np.sqrt(np.sum(np.diff(points, axis=0)**2, axis=1)) +# cumlen = np.concatenate([[0], np.cumsum(dists)]) +# total_len = cumlen[-1] +# interp_locs = np.linspace(0, total_len, N) +# interp_points = [] +# for loc in interp_locs: +# idx = np.searchsorted(cumlen, loc) - 1 +# idx = min(idx, len(points) - 2) +# seglen = cumlen[idx+1] - cumlen[idx] +# t = (loc - cumlen[idx]) / seglen if seglen > 0 else 0 +# interp = (1 - t) * points[idx] + t * points[idx+1] +# interp_points.append(interp) +# return np.array(interp_points) + +# def _legacy_bezier_q(ctrl_poly, t): +# """Cubic Bézier evaluation reused by sampling helpers.""" +# mt = 1 - t +# return ( +# mt**3 * ctrl_poly[0][0] + 3 * mt**2 * t * ctrl_poly[1][0] + 3 * mt * t**2 * ctrl_poly[2][0] + t**3 * ctrl_poly[3][0], +# mt**3 * ctrl_poly[0][1] + 3 * mt**2 * t * ctrl_poly[1][1] + 3 * mt * t**2 * ctrl_poly[2][1] + t**3 * ctrl_poly[3][1] +# ) + +# def sample_bezier_path(beziers, N=1000): +# """Sample a list of cubic Béziers into N evenly spaced points.""" +# samples = [] +# segs = len(beziers) +# pts_per_seg = max(2, N // segs) +# for bez in beziers: +# for i in range(pts_per_seg): +# t = i / (pts_per_seg - 1) +# p = _legacy_bezier_q(bez, t) +# samples.append(np.array(p)) +# return np.array(samples[:N]) + +# def find_deviations(poly_points, bezier_points, threshold=5.0): +# """Locate indexes where Bézier samples deviate from the polyline.""" +# dists = np.linalg.norm(poly_points - bezier_points, axis=1) +# deviations = np.where(dists > threshold)[0] +# return deviations, dists + +# def blend_bezier_with_polyline(beziers, poly_points, bezier_points, deviations, alpha=0.7): +# """Pull Bézier handles toward polyline points where error is large.""" +# N = len(bezier_points) +# segs = len(beziers) +# pts_per_seg = max(2, N // segs) +# for idx in deviations: +# seg_idx = idx // pts_per_seg +# if seg_idx >= len(beziers): +# continue +# p_poly = poly_points[idx] +# for c_idx in (1, 2): +# c = np.array(beziers[seg_idx][c_idx]) +# beziers[seg_idx][c_idx] = tuple(alpha * c + (1 - alpha) * p_poly) +# return beziers + +# Potrace + Skia offset pipeline (old kisscut_wrap.py). Requires potrace CLI, +# lxml, svg.path, and skia-python; kept for reference if a true stroke offset +# is preferred over the contour-based approach here. +# import subprocess, tempfile +# from lxml import etree +# from svg.path import parse_path, CubicBezier, Line, QuadraticBezier, Arc +# import skia +# +# def png_to_pnm(input_png, pnm_path): +# img = Image.open(input_png).convert("L") +# img.save(pnm_path, format="PPM") +# +# def pnm_to_base_svg(pnm_path, svg_path): +# subprocess.run([ +# "potrace", "-s", "-o", svg_path, "-b", "svg", +# "--alphamax", "0.1", "--turdsize", "1", pnm_path +# ], check=True) +# +# def offset_svg_paths(input_svg, output_svg, offset_pt=9.0): +# parser = etree.XMLParser(remove_blank_text=True) +# tree = etree.parse(input_svg, parser) +# root = tree.getroot() +# ns = {"svg": "http://www.w3.org/2000/svg"} +# svg_ns = "http://www.w3.org/2000/svg" +# new_root = etree.Element("{%s}svg" % svg_ns, +# nsmap={None: svg_ns}, +# width=root.get("width"), +# height=root.get("height"), +# viewBox=root.get("viewBox") or f"0 0 {root.get('width')} {root.get('height')}") +# for path_el in root.xpath("//svg:path", namespaces=ns): +# d = path_el.get("d") +# path = parse_path(d) +# sk_path = skia.Path() +# start = path[0].start +# sk_path.moveTo(start.real, -start.imag) +# for seg in path: +# if isinstance(seg, CubicBezier): +# sk_path.cubicTo( +# seg.control1.real, -seg.control1.imag, +# seg.control2.real, -seg.control2.imag, +# seg.end.real, -seg.end.imag +# ) +# elif isinstance(seg, Line): +# sk_path.lineTo(seg.end.real, -seg.end.imag) +# elif isinstance(seg, QuadraticBezier): +# c1 = seg.start + 2/3*(seg.control - seg.start) +# c2 = seg.end + 2/3*(seg.control - seg.end) +# sk_path.cubicTo( +# c1.real, -c1.imag, +# c2.real, -c2.imag, +# seg.end.real, -seg.end.imag +# ) +# elif isinstance(seg, Arc): +# sk_path.lineTo(seg.end.real, -seg.end.imag) +# stroke = skia.StrokeRec() +# stroke.setStrokeStyle(offset_pt) +# sk_offset = sk_path.strokeAndConvertToPath(stroke) +# d2 = sk_offset.toSVGString() +# etree.SubElement(new_root, "path", +# d=d2, +# fill="none", +# stroke="red", +# **{"stroke-width": "1"}) +# etree.ElementTree(new_root).write( +# output_svg, +# pretty_print=True, +# xml_declaration=True, +# encoding="utf-8" +# ) + # --- Preview Widget (just display, auto-margin alignment) --- class PreviewWidget(QLabel): def __init__(self, *args, **kwargs): @@ -514,4 +695,4 @@ if __name__ == '__main__': app = QApplication(sys.argv) window = MainWindow() window.show() - sys.exit(app.exec()) \ No newline at end of file + sys.exit(app.exec())