ScreenCapture
the Mac's own screen as material: a whole display, one app, or a single window arrives as a live image you can draw and filter, and the tunnel parameter keeps the sketch inside its own capture, so the picture recedes into itself (ScreenCapture)
- Run
swift run --package-path Examples Example-Integration-ScreenCapture- Imports
OllinScreen- Source
- Examples/Integration/ScreenCapture/Sketch.swift
import Ollin
import OllinScreen
/// Takes the Mac's own screen as material. The whole display, one app, or a
/// single window arrives as a live image, so anything running on the machine can
/// be drawn, filtered, and composited: a browser, a map, a video call, a
/// terminal, another sketch.
///
/// The parameter worth changing first is `tunnel`. By default the sketch's own window
/// is cut out of the capture, so pointing it at the screen it is drawn on shows
/// everything except itself. Turn `tunnel` on and the sketch is left in the
/// picture, so it draws a screen containing a window drawing a screen containing
/// a window, and the image recedes into itself. That is the oldest trick in
/// video art, and it costs one boolean here.
///
/// `look` proves the frames are ordinary images: they go through the same filter
/// catalog as anything else drawn. `detail` trades captured pixels for speed, and
/// is the parameter to reach for on a large display.
///
/// Press `L` to list what this Mac can currently capture, with the line of code
/// that names each one. Capturing needs the screen-recording permission; if it is
/// off, the sketch says so on the canvas and tells you where to turn it on.
@main
final class ScreenCaptureExample: Sketch {
enum Look: String, CaseIterable, ParamOption {
case plain, bloom, posterize, edges
}
@Param(icon: "arrow.triangle.2.circlepath") var tunnel = false
@Param(icon: "camera.filters") var look = Look.plain
@Param(0.25 ... 1, icon: "square.resize") var detail = 1.0
private let screen = ScreenCapture(.mainDisplay)
private var listing: [String] = []
private var showsListing = false
override func setup() {
screen.start()
}
override func draw() {
background(Color(white: 0.04))
screen.excludesOwnWindows = !tunnel
screen.scale = detail
guard let frame = screen.frame,
let rect = screen.fittedRectangle(in: bounds) else {
drawStatus(screen.waitingMessage, style: ScreenCapture.isAvailable ? .info : .warning)
return
}
if look == .plain {
drawImage(frame, in: rect)
} else {
// The capture is an ordinary image, so it goes through the effect
// graph like anything else the sketch draws.
let layer = makeRenderTarget()
withTarget(layer) {
background(.black)
drawImage(frame, in: bounds)
}
drawImage(layer.filtered(filter).image, in: bounds)
}
drawLabel()
if showsListing { drawListing() }
}
private var filter: Filter {
switch look {
case .plain: return .exposure(stops: 0)
case .bloom: return .bloom(threshold: 0.55, amount: 0.7, radius: 12)
case .posterize: return .posterize(levels: 5)
case .edges: return .edges(amount: 2.2)
}
}
override func keyPressed() {
guard key == "l" || key == "L" else { return }
showsListing.toggle()
guard showsListing else { return }
// Enumerating is asynchronous, so the listing lands a moment later and
// the sketch keeps drawing in the meantime.
Task { @MainActor in
var lines: [String] = ["Displays"]
for display in await ScreenCapture.availableDisplays() {
lines.append(" .display(\(display.id)) \(display.label)")
}
lines.append("Apps")
for app in await ScreenCapture.availableApps().prefix(12) {
lines.append(" .app(\"\(app.name)\")")
}
lines.append("Windows")
for window in await ScreenCapture.availableWindows().prefix(12) {
guard let title = window.title, !title.isEmpty else { continue }
lines.append(" .window(matching: \"\(title)\")")
}
listing = lines
}
}
private func drawLabel() {
let name = tunnel ? "the screen, this window included" : "the screen, this window left out"
fill(Color(white: 0.85))
textSize(20 * scale)
drawText(name, 26 * scale, 40 * scale)
fill(Color(white: 0.5))
textSize(16 * scale)
drawText("L lists what can be captured", 26 * scale, 66 * scale)
}
private func drawListing() {
let lines = listing.isEmpty ? ["Looking…"] : listing
let box = Rectangle(x: 26 * scale, y: 90 * scale,
width: width - 52 * scale,
height: Double(lines.count) * 20 * scale + 24 * scale)
fill(Color(white: 0, alpha: 0.72))
noStroke()
drawRect(box)
textSize(15 * scale)
for (index, line) in lines.enumerated() {
fill(line.hasPrefix(" ") ? Color(white: 0.72) : Color(hex: 0x7FD4FF))
drawText(line, box.x + 14 * scale, box.y + (Double(index) + 1) * 20 * scale)
}
}
}