screenshots-mac-desktop.swift (6526B)
1 #!/usr/bin/env swift 2 3 import AppKit 4 import CoreGraphics 5 import CoreText 6 import Foundation 7 import ImageIO 8 9 guard CommandLine.arguments.count >= 3 else { 10 fputs("Usage: screenshots-mac-desktop.swift <window.png> <output.png> [wallpaper]\n", stderr) 11 exit(1) 12 } 13 14 let windowPath = CommandLine.arguments[1] 15 let outputPath = CommandLine.arguments[2] 16 let wallpaperPath = 17 CommandLine.arguments.count >= 4 18 ? CommandLine.arguments[3] 19 : "Marketing/sequoia-wallpaper.jpg" 20 21 // MacBook Air 13" 4th Gen native resolution 22 let canvasWidth: CGFloat = 2560 23 let canvasHeight: CGFloat = 1664 24 25 // Load wallpaper (supports HEIC, JPEG, PNG via ImageIO) 26 guard let wpData = try? Data(contentsOf: URL(fileURLWithPath: wallpaperPath)), 27 let wpSource = CGImageSourceCreateWithData(wpData as CFData, nil), 28 let wallpaper = CGImageSourceCreateImageAtIndex(wpSource, 0, nil) 29 else { 30 fputs("Failed to load wallpaper: \(wallpaperPath)\n", stderr) 31 exit(1) 32 } 33 34 // Load window screenshot 35 guard let winProvider = CGDataProvider(filename: windowPath), 36 let windowImage = CGImage( 37 pngDataProviderSource: winProvider, decode: nil, shouldInterpolate: true, 38 intent: .defaultIntent) 39 else { 40 fputs("Failed to load window image: \(windowPath)\n", stderr) 41 exit(1) 42 } 43 44 // Create canvas 45 guard 46 let context = CGContext( 47 data: nil, 48 width: Int(canvasWidth), 49 height: Int(canvasHeight), 50 bitsPerComponent: 8, 51 bytesPerRow: 0, 52 space: CGColorSpaceCreateDeviceRGB(), 53 bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue 54 ) 55 else { 56 fputs("Failed to create graphics context\n", stderr) 57 exit(1) 58 } 59 60 // Draw wallpaper: scale to fit width, align to top, crop from bottom 61 let wpWidth = CGFloat(wallpaper.width) 62 let wpHeight = CGFloat(wallpaper.height) 63 let wpScale = canvasWidth / wpWidth 64 let scaledWpHeight = wpHeight * wpScale 65 // CG origin is bottom-left; align top edge of wallpaper to top of canvas 66 let wpY = canvasHeight - scaledWpHeight 67 context.draw( 68 wallpaper, in: CGRect(x: 0, y: wpY, width: canvasWidth, height: scaledWpHeight)) 69 70 // Draw menu bar: translucent strip at the top (@2x: 60px ≈ 30pt) 71 let menuBarHeight: CGFloat = 60 72 let menuBarY = canvasHeight - menuBarHeight 73 context.setFillColor(CGColor(red: 1, green: 1, blue: 1, alpha: 0.25)) 74 context.fill(CGRect(x: 0, y: menuBarY, width: canvasWidth, height: menuBarHeight)) 75 76 // Helper to draw a menu bar SF Symbol via CTFont 77 func drawMenuSymbol(_ name: String, at x: CGFloat, size: CGFloat) -> CGFloat { 78 guard let image = NSImage(systemSymbolName: name, accessibilityDescription: nil) else { 79 return x 80 } 81 let config = NSImage.SymbolConfiguration(pointSize: size, weight: .regular) 82 let configured = image.withSymbolConfiguration(config) ?? image 83 // Tint the symbol 84 let tinted = NSImage(size: configured.size, flipped: false) { rect in 85 menuTextColor.set() 86 rect.fill(using: .sourceOver) 87 configured.draw(in: rect, from: .zero, operation: .destinationIn, fraction: 1.0) 88 return true 89 } 90 let imgWidth = tinted.size.width 91 let imgHeight = tinted.size.height 92 let imgY = menuBarY + (menuBarHeight - imgHeight) / 2 93 guard let cgImage = tinted.cgImage(forProposedRect: nil, context: nil, hints: nil) else { 94 return x 95 } 96 context.draw(cgImage, in: CGRect(x: x, y: imgY, width: imgWidth, height: imgHeight)) 97 return x + imgWidth 98 } 99 100 // Helper to draw menu bar text 101 let menuTextColor = NSColor(white: 0.0, alpha: 0.85) 102 func drawMenuText(_ text: String, at x: CGFloat, size: CGFloat, weight: NSFont.Weight = .regular) 103 -> CGFloat 104 { 105 let font = NSFont.systemFont(ofSize: size, weight: weight) 106 let attrs: [NSAttributedString.Key: Any] = [ 107 .font: font, 108 .foregroundColor: menuTextColor, 109 ] 110 let attrStr = NSAttributedString(string: text, attributes: attrs) 111 let line = CTLineCreateWithAttributedString(attrStr) 112 let bounds = CTLineGetBoundsWithOptions(line, .useOpticalBounds) 113 let y = menuBarY + (menuBarHeight - bounds.height) / 2 - bounds.origin.y 114 context.textPosition = CGPoint(x: x, y: y) 115 CTLineDraw(line, context) 116 return x + bounds.width 117 } 118 119 // Left side: Apple logo, app name, menus 120 let appleFont = NSFont(name: "SF Pro Display", size: 42) ?? NSFont.systemFont(ofSize: 42) 121 let appleAttrs: [NSAttributedString.Key: Any] = [ 122 .font: appleFont, 123 .foregroundColor: menuTextColor, 124 ] 125 let appleString = NSAttributedString(string: "\u{F8FF}", attributes: appleAttrs) 126 let appleLine = CTLineCreateWithAttributedString(appleString) 127 let appleBounds = CTLineGetBoundsWithOptions(appleLine, .useOpticalBounds) 128 let appleX: CGFloat = 32 129 let appleTextY = menuBarY + (menuBarHeight - appleBounds.height) / 2 - appleBounds.origin.y 130 context.textPosition = CGPoint(x: appleX, y: appleTextY) 131 CTLineDraw(appleLine, context) 132 133 var curX = appleX + appleBounds.width + 40 134 curX = drawMenuText("Listless", at: curX, size: 28, weight: .bold) + 36 135 curX = drawMenuText("File", at: curX, size: 28) + 36 136 curX = drawMenuText("Edit", at: curX, size: 28) + 36 137 curX = drawMenuText("View", at: curX, size: 28) + 36 138 curX = drawMenuText("Window", at: curX, size: 28) + 36 139 _ = drawMenuText("Help", at: curX, size: 28) 140 141 // Right side: icons, date and time 142 var rightX = canvasWidth - 260 143 _ = drawMenuText("Wed 9 Apr 9:41 AM", at: rightX, size: 28) 144 rightX -= 20 145 _ = drawMenuSymbol("switch.2", at: rightX - 44, size: 32) 146 _ = drawMenuSymbol("magnifyingglass", at: rightX - 100, size: 28) 147 148 // Scale and draw window screenshot offset slightly right of centre 149 let winWidth = CGFloat(windowImage.width) 150 let winHeight = CGFloat(windowImage.height) 151 let winScale = (canvasWidth * 0.4) / winWidth 152 let scaledWinWidth = winWidth * winScale 153 let scaledWinHeight = winHeight * winScale 154 let winX = (canvasWidth - scaledWinWidth) / 2 + 400 155 let availableHeight = canvasHeight - menuBarHeight 156 let winY = (availableHeight - scaledWinHeight) / 2 + 150 157 context.draw( 158 windowImage, 159 in: CGRect(x: winX, y: winY, width: scaledWinWidth, height: scaledWinHeight)) 160 161 // Save 162 guard let resultImage = context.makeImage(), 163 let destination = CGImageDestinationCreateWithURL( 164 URL(fileURLWithPath: outputPath) as CFURL, 165 "public.png" as CFString, 166 1, 167 nil 168 ) 169 else { 170 fputs("Failed to create output\n", stderr) 171 exit(1) 172 } 173 174 CGImageDestinationAddImage(destination, resultImage, nil) 175 guard CGImageDestinationFinalize(destination) else { 176 fputs("Failed to write output\n", stderr) 177 exit(1) 178 }