build-desktop-icon.swift 2.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455
  1. import AppKit
  2. // Keep the shared brand artwork intact; the white tile belongs to the macOS app icon.
  3. let arguments = CommandLine.arguments
  4. guard arguments.count == 3,
  5. let logo = NSImage(contentsOfFile: arguments[1]) else {
  6. fatalError("Usage: swift build-desktop-icon.swift <brand.png> <output-directory>")
  7. }
  8. let output = URL(fileURLWithPath: arguments[2], isDirectory: true)
  9. let iconset = output.appendingPathComponent("icon.iconset", isDirectory: true)
  10. try FileManager.default.createDirectory(at: iconset, withIntermediateDirectories: true)
  11. func render(size: Int) throws -> Data {
  12. let bitmap = NSBitmapImageRep(bitmapDataPlanes: nil, pixelsWide: size, pixelsHigh: size,
  13. bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false,
  14. colorSpaceName: .deviceRGB, bytesPerRow: 0, bitsPerPixel: 0)!
  15. let context = NSGraphicsContext(bitmapImageRep: bitmap)!
  16. NSGraphicsContext.saveGraphicsState()
  17. NSGraphicsContext.current = context
  18. context.imageInterpolation = .high
  19. context.shouldAntialias = true
  20. let scale = CGFloat(size) / 1024
  21. context.cgContext.clear(CGRect(x: 0, y: 0, width: size, height: size))
  22. context.cgContext.scaleBy(x: scale, y: scale)
  23. NSColor.white.setFill()
  24. NSBezierPath(roundedRect: NSRect(x: 100, y: 100, width: 824, height: 824),
  25. xRadius: 184, yRadius: 184).fill()
  26. logo.draw(in: NSRect(x: 140, y: 140, width: 744, height: 744),
  27. from: .zero, operation: .sourceOver, fraction: 1)
  28. context.flushGraphics()
  29. NSGraphicsContext.restoreGraphicsState()
  30. // These points catch an absent tile or an accidentally flattened outer canvas.
  31. for (x, y) in [(0, 0), (size - 1, 0), (0, size - 1), (size - 1, size - 1)] {
  32. precondition(bitmap.colorAt(x: x, y: y)!.alphaComponent == 0,
  33. "The icon's outer corners must be transparent")
  34. }
  35. let center = bitmap.colorAt(x: size / 2, y: size / 2)!.usingColorSpace(.deviceRGB)!
  36. precondition(center.alphaComponent == 1 && center.redComponent == 1 &&
  37. center.greenComponent == 1 && center.blueComponent == 1,
  38. "The icon's interior must have an opaque, pure-white background")
  39. return bitmap.representation(using: .png, properties: [:])!
  40. }
  41. for points in [16, 32, 128, 256, 512] {
  42. for scale in [1, 2] {
  43. let suffix = scale == 2 ? "@2x" : ""
  44. let data = try render(size: points * scale)
  45. try data.write(to: iconset.appendingPathComponent("icon_\(points)x\(points)\(suffix).png"))
  46. if points == 512 && scale == 2 {
  47. try data.write(to: output.appendingPathComponent("icon.png"))
  48. }
  49. }
  50. }
  51. print("桌面图标已生成:纯白底板,10 个尺寸,透明度校验通过")