🍁

Swift: 画像をプログラムで描画するときのTips

2021/01/28に公開1

Retinaディスプレイ環境時、画像をプログラムで生成するとき気をつけないと、なぜか指定したSizeの2倍のピクセルサイズの画像が生成されます。リサイズとかトリミングをする時にこれはかなり厄介なので、知見を書き残しておきます。

大丈夫なパターン

// 1 コンテンツのURL/パスから直接生成
let image = NSImage(contentsOf: URL)

// 2 NSBitmapImageRepを使う
guard let data = try? Data(contentsOf: URL),
      let rep = NSBitmapImageRep(data: data),
      let pngData = rep.representation(using: .png, properties: [:])
      let image = NSImage(data: pngData) else {
   return
}

// 3 CIImageをいったん経由する
let cgImage = CGImage() // 何かしらCGImageを生成
let ciImage = CIImage(cgImage: cgImage),
let rep = NSCIImageRep(ciImage: ciImage)
let image = NSimage(size: rep.size)
image.addRepresentation(rep)

// 4 CGContextを使う
let size = CGSize() // 目的のピクセルサイズ
guard let cgContext = CGContext(data: nil,
                                width: Int(size.width),
                                height: Int(size.height),
                                bitsPerComponent: 8,
                                bytesPerRow: 4 * Int(size.width),
                                space: CGColorSpaceCreateDeviceRGB(),
                                bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) else {
    return
}
// 塗りつぶしたり図形描いたりする場合(色々ある)
cgContext.setFillColor(CGColor.white)
cgContext.fill(CGRect(origin: .zero, size: size))
cgContext.setLineColor(CGColor.black)
cgContext.stroke(CGRect(origin: .zero, size: size))

// 画像からやる場合
let cgImage = CGImage() 何かしらCGImageを生成
cgContext.draw(cgImage, in: CGRect(origin: .zero, size: size))

guard let image = cgContext.makeImage() else { return }

ダメなパターン

// 1 NSImage(cgImage:size:)を使う
let cgImage = CGImage() // 何かしらCGImageを生成
let image = NSImage(cgImage: cgImage, size: CGSize(width: cgImage.width, height: cgImage.height))

// 2 NSImage(size:), NSImage.lockFocus()/unlockFocus(), NSGraphicsContextを使う
let originalImage = NSImage() // ピクセルサイズがちゃんとしている画像
let rect = CGRect() // 目的の画像サイズの矩形
let image = NSImage(size: rect.size)
image.lockFocus()
if let ctx = NSGraphicsContext.current {
    NSColor.white.setFill()
    rect.fill()
    
    originalImage.draw(in: rect) // ここでサイズがバグる
}
image.unlockFocus()

順次大丈夫なパターンとダメなパターンを見つけたら追加していく。

Discussion

KyomeKyome

よく見たら、ダメなパターンのctx全く使ってないじゃん。