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:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user