diff --git a/Sources/Kapture/CaptureController.swift b/Sources/Kapture/CaptureController.swift
index 6db5f73..a39ef1c 100644
--- a/Sources/Kapture/CaptureController.swift
+++ b/Sources/Kapture/CaptureController.swift
@@ -117,19 +117,13 @@ final class ScreenCapture {
private init() {}
@available(macOS 14.0, *)
- private func crop(_ image: CGImage, to rect: NSRect, display: SCDisplay) -> CGImage? {
- // The captured image may be at any scale (retina 2x, secondary display
- // 1x, or trimmed by ScreenCaptureKit). Derive the pixel scale from the
- // image we actually got, relative to the display's point frame.
+ private func crop(_ image: CGImage, to cgRect: CGRect, display: SCDisplay) -> CGImage? {
+ // cgRect is already in SCDisplay space (Y flipped relative to AppKit).
+ // Derive the pixel scale from the image we actually got, relative to
+ // the display's point frame.
let scaleX = CGFloat(image.width) / display.frame.width
let scaleY = CGFloat(image.height) / display.frame.height
guard scaleX > 0, scaleY > 0 else { return nil }
- let cgRect = CGRect(
- x: rect.minX,
- y: rect.minY,
- width: rect.width,
- height: rect.height
- )
let pxRect = CGRect(
x: (cgRect.minX - display.frame.minX) * scaleX,
y: (cgRect.minY - display.frame.minY) * scaleY,
@@ -177,18 +171,30 @@ final class ScreenCapture {
// width/height are output PIXELS. Multiply by the backing scale
// of the screen containing the selection so retina displays are
// captured at full resolution and 1x displays stay 1x.
- let backingScale = NSScreen.screens
- .first(where: { $0.frame.contains(NSPoint(x: rect.midX, y: rect.midY)) })?
- .backingScaleFactor ?? 1
+ guard let screen = NSScreen.screens.first(where: { $0.frame.contains(NSPoint(x: rect.midX, y: rect.midY)) }) else {
+ completion(nil)
+ return
+ }
+ let backingScale = screen.backingScaleFactor
config.width = Int(CGFloat(display.width) * backingScale)
config.height = Int(CGFloat(display.height) * backingScale)
config.showsCursor = true
+ // ScreenCaptureKit's Y axis is flipped relative to AppKit
+ // NSScreen frames. Convert the selection into SCDisplay space
+ // using the matching NSScreen's top edge, then crop there.
+ let scRect = CGRect(
+ x: rect.minX,
+ y: screen.frame.maxY - rect.maxY + display.frame.minY,
+ width: rect.width,
+ height: rect.height
+ )
+
guard let image = try? await SCScreenshotManager.captureImage(contentFilter: filter, configuration: config) else {
completion(nil)
return
}
- completion(self.crop(image, to: rect, display: display))
+ completion(self.crop(image, to: scRect, display: display))
}
} else {
guard let primary = NSScreen.screens.first(where: { $0.frame.origin == .zero }) ?? NSScreen.screens.first else {
diff --git a/build-app.sh b/build-app.sh
index 3180131..984c3b2 100755
--- a/build-app.sh
+++ b/build-app.sh
@@ -108,9 +108,9 @@ cat > "$APP/Contents/Info.plist" <<'PLIST'
CFBundleIconFile
Icon
CFBundleShortVersionString
- 1.1.6
+ 1.1.7
CFBundleVersion
- 16
+ 17
LSUIElement
NSHighResolutionCapable