Add window detection capture with rounded corners

Hovering during selection now highlights the window under the cursor;
clicking captures it with a rounded-corner alpha mask matching macOS
window chrome. Dragging still switches to manual marquee selection.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-flash-0731
This commit is contained in:
2026-09-09 10:29:24 +08:00
parent de642ad692
commit 969cda6f14
3 changed files with 176 additions and 15 deletions
+44 -7
View File
@@ -59,13 +59,25 @@ final class CaptureController: NSObject {
OverlayWindow(
screen: screen,
onComplete: { [weak self] rect in self?.finishSelection(rect) },
onCancel: { [weak self] in self?.cancelSelection() }
onCancel: { [weak self] in self?.cancelSelection() },
onWindowPick: { [weak self] rect in self?.finishWindowPick(rect) }
)
}
overlays.forEach { $0.makeKeyAndOrderFront(nil) }
overlayIDs = overlays.map { $0.windowNumber }
}
/// A window was picked from hover — capture it with rounded corners.
private func finishWindowPick(_ rect: NSRect) {
let ids = overlayIDs
hideOverlays()
isSelecting = false
restorePreviousApp()
DispatchQueue.main.async { [self] in
capture(rect, excludingWindowIDs: ids, roundedCorners: true)
}
}
private func finishSelection(_ rect: NSRect) {
let ids = overlayIDs
hideOverlays()
@@ -94,8 +106,8 @@ final class CaptureController: NSObject {
previousApp = nil
}
private func capture(_ rect: NSRect, excludingWindowIDs ids: [Int]) {
ScreenCapture.shared.capture(rect, excludingWindowIDs: ids) { [weak self] image in
private func capture(_ rect: NSRect, excludingWindowIDs ids: [Int], roundedCorners: Bool = false) {
ScreenCapture.shared.capture(rect, excludingWindowIDs: ids, roundedCorners: roundedCorners) { [weak self] image in
DispatchQueue.main.async {
self?.isSelecting = false
self?.restorePreviousApp()
@@ -117,7 +129,7 @@ final class ScreenCapture {
private init() {}
@available(macOS 14.0, *)
private func crop(_ image: CGImage, to cgRect: CGRect, display: SCDisplay) -> CGImage? {
private func crop(_ image: CGImage, to cgRect: CGRect, display: SCDisplay, roundedCorners: Bool = false) -> CGImage? {
// cgRect is in SCDisplay space (bottom-left origin, Y flipped vs AppKit).
// The captured image has a TOP-LEFT origin. So within the display,
// we must flip Y: pixelY = displayHeight - (cgY - displayMinY) - cgHeight.
@@ -141,7 +153,32 @@ final class ScreenCapture {
// CGContext, which resamples and loses quality on retina captures.
let rounded = safeRect.integral
guard rounded.width >= 1, rounded.height >= 1 else { return nil }
return image.cropping(to: rounded)
guard var result = image.cropping(to: rounded) else { return nil }
// Apply a rounded-corner alpha mask for window captures, matching
// macOS window chrome (≈10pt radius, scaled to image pixels).
if roundedCorners {
let radius = 10 * scaleX
let maskRect = CGRect(x: 0, y: 0, width: result.width, height: result.height)
guard let maskCtx = CGContext(
data: nil, width: result.width, height: result.height,
bitsPerComponent: 8, bytesPerRow: 0,
space: CGColorSpaceCreateDeviceRGB(),
bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue
) else { return result }
maskCtx.setFillColor(CGColor(gray: 1, alpha: 1))
maskCtx.fill(maskRect)
maskCtx.setBlendMode(.clear)
maskCtx.setFillColor(CGColor(gray: 0, alpha: 1))
maskCtx.addPath(CGPath(roundedRect: maskRect, cornerWidth: radius, cornerHeight: radius, transform: nil))
maskCtx.fillPath()
maskCtx.setBlendMode(.normal)
if let mask = maskCtx.makeImage(),
let masked = result.masking(mask) {
result = masked
}
}
return result
}
@available(macOS 14.0, *)
@@ -154,7 +191,7 @@ final class ScreenCapture {
return content.displays.first(where: { $0.displayID == cgID }) ?? content.displays.first
}
func capture(_ rect: NSRect, excludingWindowIDs excludedIDs: [Int], completion: @escaping (CGImage?) -> Void) {
func capture(_ rect: NSRect, excludingWindowIDs excludedIDs: [Int], roundedCorners: Bool = false, completion: @escaping (CGImage?) -> Void) {
if #available(macOS 14.0, *) {
Task {
guard let content = try? await SCShareableContent.current,
@@ -193,7 +230,7 @@ final class ScreenCapture {
completion(nil)
return
}
completion(self.crop(image, to: scRect, display: display))
completion(self.crop(image, to: scRect, display: display, roundedCorners: roundedCorners))
}
} else {
guard let primary = NSScreen.screens.first(where: { $0.frame.origin == .zero }) ?? NSScreen.screens.first else {