From 26700c59f49abdcf6e0b6177fb3eb06eaef7d28a Mon Sep 17 00:00:00 2001 From: TZGyn Date: Wed, 23 Sep 2026 17:28:38 +0800 Subject: [PATCH] Make exported stroke thickness match the preview MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Strokes were sized in view points but exported in image pixels, so on retina captures the saved/copied annotation looked several times thinner. Stroke widths are now converted into image pixels when drawn and scaled back for the preview, keeping preview and export identical. 💘 Generated with Crush Assisted-by: Crush:deepseek-v4-flash-0731 --- Sources/Kapture/ResultWindow.swift | 16 +++++++++------- build-app.sh | 4 ++-- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/Sources/Kapture/ResultWindow.swift b/Sources/Kapture/ResultWindow.swift index b28702c..42cd8b2 100644 --- a/Sources/Kapture/ResultWindow.swift +++ b/Sources/Kapture/ResultWindow.swift @@ -195,14 +195,15 @@ final class CaptureModel: ObservableObject { return rep.cgImage ?? image } - func addStroke(_ points: [CGPoint]) { + /// `widthPx` is the stroke thickness in IMAGE PIXELS (the preview + /// converts the slider value so exports match what was drawn). + func addStroke(_ points: [CGPoint], widthPx: CGFloat) { guard points.count > 1 else { return } if tool == .redact { - // Redaction: a filled black bar from start to end, as thick as the - // current stroke width (drawn as a rect for clean edges). - strokes.append(Stroke(points: points, width: max(strokeWidth, 12), color: .black, isRedaction: true)) + // Redaction: a filled black bar from start to end. + strokes.append(Stroke(points: points, width: max(widthPx, 12), color: .black, isRedaction: true)) } else { - strokes.append(Stroke(points: points, width: strokeWidth, color: drawColor)) + strokes.append(Stroke(points: points, width: widthPx, color: drawColor)) } redoStack = [] } @@ -575,7 +576,8 @@ struct ScreenshotPane: View { for point in stroke.points.dropFirst() { path.addLine(to: CGPoint(x: origin.x + point.x * scale, y: origin.y + point.y * scale)) } - context.stroke(path, with: .color(Color(nsColor: stroke.color)), lineWidth: stroke.width) + // stroke.width is in image pixels; scale to view points + context.stroke(path, with: .color(Color(nsColor: stroke.color)), lineWidth: stroke.width * scale) } } } @@ -636,7 +638,7 @@ struct ScreenshotPane: View { .onEnded { _ in if model.tool != .select { if currentStroke.count > 1 { - model.addStroke(currentStroke) + model.addStroke(currentStroke, widthPx: model.strokeWidth / max(scale, 0.01)) } currentStroke = [] } else { diff --git a/build-app.sh b/build-app.sh index 523f98d..2b7dd9f 100755 --- a/build-app.sh +++ b/build-app.sh @@ -108,9 +108,9 @@ cat > "$APP/Contents/Info.plist" <<'PLIST' CFBundleIconFile Icon CFBundleShortVersionString - 1.9.1 + 1.9.2 CFBundleVersion - 91 + 92 LSUIElement NSHighResolutionCapable