Compare commits

..
3 Commits
Author SHA1 Message Date
TZGyn c9a807e8a0 Fix secondary display captures after retina scaling change
The 1.1.2 retina fix overrode the stream width from the display's pixel
dimensions, which made captures on secondary monitors come out at the
wrong scale or offset. Restore requesting the display's native pixel
resolution and keep the crop scale derived from the actual returned image,
plus guard against out-of-bounds crop rects.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-flash-0731
2026-08-12 16:34:18 +08:00
TZGyn 7bc93c2014 Anti-alias downscaled screenshots in the result window
Full-res retina captures are downscaled to fit the window; nearest-neighbor
made them look chunky. Use high-quality interpolation when shrinking, keep
pixel-crisp rendering only when upscaling small selections.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-flash-0731
2026-08-12 16:25:23 +08:00
TZGyn 374af50acc Capture at full retina resolution
The ScreenCaptureKit stream was sized in points at 1x scale, so retina
displays produced half-resolution captures that appeared pixelated.
Request backingScale x pixels and derive the crop scale from the actual
captured image dimensions.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-flash-0731
2026-08-12 16:14:39 +08:00
3 changed files with 26 additions and 12 deletions
+14 -4
View File
@@ -86,8 +86,12 @@ final class ScreenCapture {
@available(macOS 14.0, *)
private func crop(_ image: CGImage, to rect: NSRect, display: SCDisplay) -> CGImage? {
let scaleX = CGFloat(display.width) / display.frame.width
let scaleY = CGFloat(display.height) / display.frame.height
// 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.
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,
@@ -100,14 +104,18 @@ final class ScreenCapture {
width: cgRect.width * scaleX,
height: cgRect.height * scaleY
)
// Guard against negative offsets when the selection reaches beyond the
// display's frame (e.g. secondary monitor coordinate quirks).
let safeRect = pxRect.intersection(CGRect(x: 0, y: 0, width: CGFloat(image.width), height: CGFloat(image.height)))
guard safeRect.width >= 1, safeRect.height >= 1 else { return nil }
guard let cropCtx = CGContext(
data: nil, width: Int(pxRect.width), height: Int(pxRect.height),
data: nil, width: Int(safeRect.width), height: Int(safeRect.height),
bitsPerComponent: 8, bytesPerRow: 0,
space: CGColorSpaceCreateDeviceRGB(),
bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue
) else { return nil }
cropCtx.interpolationQuality = .high
cropCtx.draw(image, in: CGRect(x: -pxRect.minX, y: -pxRect.minY, width: CGFloat(image.width), height: CGFloat(image.height)))
cropCtx.draw(image, in: CGRect(x: -safeRect.minX, y: -safeRect.minY, width: CGFloat(image.width), height: CGFloat(image.height)))
return cropCtx.makeImage()
}
@@ -133,6 +141,8 @@ final class ScreenCapture {
let excluded = content.windows.filter { excludedSet.contains($0.windowID) }
let filter = SCContentFilter(display: display, excludingWindows: excluded)
let config = SCStreamConfiguration()
// display.width/height are pixels; request the display's full
// native resolution so ScreenCaptureKit gives us every pixel.
config.width = Int(display.width)
config.height = Int(display.height)
config.showsCursor = true
+5 -1
View File
@@ -193,10 +193,14 @@ struct ResultView: View {
.padding(.top, 10)
if model.mode == .screenshot {
GeometryReader { geo in
let imageWidth = CGFloat(model.image.width)
Image(nsImage: NSImage(cgImage: model.image, size: .zero))
.resizable()
.interpolation(.none)
// Downscale with smoothing (AA); upscale keeps pixels crisp.
.interpolation(imageWidth >= geo.size.width ? .high : .none)
.aspectRatio(contentMode: .fit)
}
.border(Color.secondary.opacity(0.4))
.padding(.horizontal, 12)
} else {
+2 -2
View File
@@ -108,9 +108,9 @@ cat > "$APP/Contents/Info.plist" <<'PLIST'
<key>CFBundleIconFile</key>
<string>Icon</string>
<key>CFBundleShortVersionString</key>
<string>1.1.1</string>
<string>1.1.4</string>
<key>CFBundleVersion</key>
<string>11</string>
<string>14</string>
<key>LSUIElement</key>
<true/>
<key>NSHighResolutionCapable</key>