crossmate

A collaborative crossword app for iOS
Log | Files | Refs | LICENSE

screenshots-ios-compose.swift (3688B)


      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 >= 4 else {
     10     fputs(
     11         "Usage: screenshots-ios-compose.swift <input.png> <output.png> <text> [width height]\n",
     12         stderr)
     13     exit(1)
     14 }
     15 
     16 let inputPath = CommandLine.arguments[1]
     17 let outputPath = CommandLine.arguments[2]
     18 let text = CommandLine.arguments[3]
     19 
     20 let canvasWidth: CGFloat =
     21     CommandLine.arguments.count >= 5 ? CGFloat(Int(CommandLine.arguments[4]) ?? 1320) : 1320
     22 let canvasHeight: CGFloat =
     23     CommandLine.arguments.count >= 6 ? CGFloat(Int(CommandLine.arguments[5]) ?? 2868) : 2868
     24 
     25 let backgroundColor = CGColor(
     26     red: 228.0 / 255.0,
     27     green: 242.0 / 255.0,
     28     blue: 255.0 / 255.0,
     29     alpha: 1.0
     30 )
     31 
     32 guard let dataProvider = CGDataProvider(filename: inputPath),
     33     let deviceImage = CGImage(
     34         pngDataProviderSource: dataProvider,
     35         decode: nil,
     36         shouldInterpolate: true,
     37         intent: .defaultIntent
     38     )
     39 else {
     40     fputs("Failed to load image: \(inputPath)\n", stderr)
     41     exit(1)
     42 }
     43 
     44 let deviceWidth = CGFloat(deviceImage.width)
     45 let deviceHeight = CGFloat(deviceImage.height)
     46 
     47 guard
     48     let context = CGContext(
     49         data: nil,
     50         width: Int(canvasWidth),
     51         height: Int(canvasHeight),
     52         bitsPerComponent: 8,
     53         bytesPerRow: 0,
     54         space: CGColorSpaceCreateDeviceRGB(),
     55         bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue
     56     )
     57 else {
     58     fputs("Failed to create graphics context\n", stderr)
     59     exit(1)
     60 }
     61 
     62 context.setFillColor(backgroundColor)
     63 context.fill(CGRect(x: 0, y: 0, width: canvasWidth, height: canvasHeight))
     64 
     65 let isLandscape = canvasWidth > canvasHeight
     66 let isPad = canvasWidth >= 1800
     67 let sidePadding: CGFloat = isPad ? 140 : 70
     68 let bottomPadding: CGFloat = isPad ? 150 : 80
     69 let topReserved: CGFloat = isPad ? 460 : (isLandscape ? 220 : 330)
     70 let maxDeviceWidth = canvasWidth - sidePadding * 2
     71 let maxDeviceHeight = canvasHeight - bottomPadding - topReserved
     72 let scale = min(maxDeviceWidth / deviceWidth, maxDeviceHeight / deviceHeight, 1.0)
     73 let scaledWidth = deviceWidth * scale
     74 let scaledHeight = deviceHeight * scale
     75 let deviceX = (canvasWidth - scaledWidth) / 2
     76 let deviceY = bottomPadding
     77 
     78 context.draw(
     79     deviceImage,
     80     in: CGRect(x: deviceX, y: deviceY, width: scaledWidth, height: scaledHeight)
     81 )
     82 
     83 let fontSize: CGFloat = isPad ? 118 : (isLandscape ? canvasHeight * 0.06 : canvasWidth * 0.066)
     84 let font = NSFont.systemFont(ofSize: fontSize, weight: .bold)
     85 let attributes: [NSAttributedString.Key: Any] = [
     86     .font: font,
     87     .foregroundColor: NSColor(calibratedRed: 17 / 255, green: 24 / 255, blue: 39 / 255, alpha: 1),
     88 ]
     89 let attrString = NSAttributedString(string: text, attributes: attributes)
     90 let ctLine = CTLineCreateWithAttributedString(attrString)
     91 let textBounds = CTLineGetBoundsWithOptions(ctLine, .useOpticalBounds)
     92 
     93 let textAreaTop = canvasHeight - (isPad ? 80 : 40)
     94 let textAreaBottom = deviceY + scaledHeight
     95 let textX = (canvasWidth - textBounds.width) / 2 - textBounds.origin.x
     96 let textY = (textAreaTop + textAreaBottom) / 2 - textBounds.height / 2 - textBounds.origin.y
     97 
     98 context.textPosition = CGPoint(x: textX, y: textY)
     99 CTLineDraw(ctLine, context)
    100 
    101 guard let resultImage = context.makeImage(),
    102     let destination = CGImageDestinationCreateWithURL(
    103         URL(fileURLWithPath: outputPath) as CFURL,
    104         "public.png" as CFString,
    105         1,
    106         nil
    107     )
    108 else {
    109     fputs("Failed to create output\n", stderr)
    110     exit(1)
    111 }
    112 
    113 CGImageDestinationAddImage(destination, resultImage, nil)
    114 guard CGImageDestinationFinalize(destination) else {
    115     fputs("Failed to write output\n", stderr)
    116     exit(1)
    117 }