Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions scripts/beautify.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#!/usr/bin/env swift
// Composites a window screenshot (with alpha shadow) onto a gradient backdrop —
// the "nice screenshot" treatment for site images. Capture the input with
// scripts/screenshot.swift, which preserves the shadow alpha this expects.
// Usage: swift scripts/beautify.swift <in.png> <out.png>

import AppKit

guard CommandLine.arguments.count == 3 else {
print("usage: beautify.swift <in.png> <out.png>")
exit(1)
}
let inputURL = URL(fileURLWithPath: CommandLine.arguments[1])
let outputURL = URL(fileURLWithPath: CommandLine.arguments[2])

guard let image = NSImage(contentsOf: inputURL),
let rep = image.representations.first as? NSBitmapImageRep
else {
print("could not read \(inputURL.path)")
Comment thread
alexkroman marked this conversation as resolved.
exit(1)
}
let shotW = rep.pixelsWide
let shotH = rep.pixelsHigh

// Padding proportional to the shot, capped for consistency across sizes.
let pad = min(max(Int(Double(min(shotW, shotH)) * 0.07), 72), 140)
let outW = shotW + pad * 2
let outH = shotH + pad * 2

let out = NSBitmapImageRep(
bitmapDataPlanes: nil, pixelsWide: outW, pixelsHigh: outH,
bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false,
colorSpaceName: .deviceRGB, bytesPerRow: 0, bitsPerPixel: 0)!
NSGraphicsContext.saveGraphicsState()
NSGraphicsContext.current = NSGraphicsContext(bitmapImageRep: out)

// Brand gradient (matches the site's accent colors), angled.
let gradient = NSGradient(colors: [
NSColor(calibratedRed: 0.24, green: 0.36, blue: 0.86, alpha: 1),
NSColor(calibratedRed: 0.42, green: 0.23, blue: 0.86, alpha: 1),
NSColor(calibratedRed: 0.58, green: 0.25, blue: 0.78, alpha: 1),
])!
gradient.draw(in: NSRect(x: 0, y: 0, width: outW, height: outH), angle: -35)

// The captured window already carries macOS corner radius + shadow in its alpha.
let dest = NSRect(x: pad, y: pad, width: shotW, height: shotH)
rep.draw(
in: dest, from: .zero, operation: .sourceOver, fraction: 1.0,
respectFlipped: true, hints: [.interpolation: NSImageInterpolation.high.rawValue])

NSGraphicsContext.restoreGraphicsState()
guard let data = out.representation(using: .png, properties: [:]) else {
print("encode failed")
exit(1)
}
try! data.write(to: outputURL)
print("wrote \(outputURL.path) (\(outW)x\(outH))")
92 changes: 92 additions & 0 deletions scripts/screenshot.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#!/usr/bin/env swift
// Captures a single app window to PNG with the macOS corner radius + drop
// shadow preserved in the alpha channel — exactly the input
// scripts/beautify.swift composites onto the gradient backdrop.
//
// Needs Screen Recording permission (System Settings → Privacy & Security);
// screencapture prompts for it on first use.
//
// Usage: swift scripts/screenshot.swift <app name> <out.png> [window title substring]
// swift scripts/screenshot.swift Blurt /tmp/blurt.png
// swift scripts/screenshot.swift Blurt /tmp/settings.png Settings

import AppKit
import CoreGraphics

let args = CommandLine.arguments
guard args.count == 3 || args.count == 4 else {
print("usage: screenshot.swift <app name> <out.png> [window title substring]")
exit(1)
}
let appName = args[1]
let outputPath = args[2]
let titleFilter = args.count == 4 ? args[3] : nil

// On-screen windows, front to back. Layer 0 keeps ordinary document/settings
// windows and drops menu bar extras, overlays, and the Dock; the size floor
// drops helper popovers.
guard
let windowList = CGWindowListCopyWindowInfo(
[.optionOnScreenOnly, .excludeDesktopElements], kCGNullWindowID) as? [[String: Any]]
else {
print("could not read the window list")
exit(1)
}

struct Candidate {
let id: Int
let title: String
let width: Int
let height: Int
}

let candidates: [Candidate] = windowList.compactMap { info in
guard let owner = info[kCGWindowOwnerName as String] as? String, owner == appName,
let layer = info[kCGWindowLayer as String] as? Int, layer == 0,
let id = info[kCGWindowNumber as String] as? Int,
let bounds = info[kCGWindowBounds as String] as? [String: Any],
let width = bounds["Width"] as? Double, let height = bounds["Height"] as? Double,
width >= 64, height >= 64
else { return nil }
// Window titles are only visible here once Screen Recording is granted —
// the same permission screencapture itself needs below.
let title = info[kCGWindowName as String] as? String ?? ""
return Candidate(id: id, title: title, width: Int(width), height: Int(height))
}

let matches: [Candidate]
if let titleFilter {
matches = candidates.filter { $0.title.localizedCaseInsensitiveContains(titleFilter) }
} else {
matches = candidates
}

guard let window = matches.first else {
let filterNote = titleFilter.map { " with title containing \"\($0)\"" } ?? ""
print("no on-screen window matched app \"\(appName)\"\(filterNote)")
for c in candidates {
let title = c.title.isEmpty ? "(untitled — grant Screen Recording to see titles)" : c.title
print(" [\(c.id)] \(title) \(c.width)x\(c.height)")
}
exit(1)
}

// screencapture keeps the window's rounded corners and drop shadow in the
// PNG's alpha channel by default (-o would strip the shadow, which beautify
// relies on); -x mutes the shutter sound.
let task = Process()
task.executableURL = URL(fileURLWithPath: "/usr/sbin/screencapture")
task.arguments = ["-x", "-t", "png", "-l", String(window.id), outputPath]
do {
try task.run()
} catch {
print("could not launch screencapture: \(error.localizedDescription)")
exit(1)
}
task.waitUntilExit()
guard task.terminationStatus == 0, FileManager.default.fileExists(atPath: outputPath) else {
print("screencapture failed — is Screen Recording granted in System Settings?")
exit(1)
}
let titleNote = window.title.isEmpty ? "" : " — \(window.title)"
print("captured \"\(appName)\"\(titleNote) (\(window.width)x\(window.height)) to \(outputPath)")