NSImage をリサイズする。

Swift プログラミング

OS X アプリで NSImage に読み込んだ画像をリサイズしてみました。

いくつかの方法があるようですが期待どおりに動いてくれないものもあったので、それについても備忘録として記しておきました。


OS X アプリで、NSImage型 で表現された画像データをリサイズ した画像を作成してみることにしました。リサイズ後の画像は新しいNSImage データとして作成します。

作成方法はいくつかのアプローチがあるようだったのですが、なかなか期待どおりに動いてくれず、最終的には次のようにして画像をリサイズできるようになりました。

最終的に落ち着いたリサイズ方法

画像データがNSImage型変数sourceImage に読み込まれているときに、新しいサイズの画像コンテキストをCGBitmapContextCreate関数 で作成して、そこにCGContextDrawImage関数 を使って元画像を書き出す方法を採ってみました。

// sourceImage から NSBitmapImageRep を取得して、そこから CGImage を取り出します。
let image = NSBitmapImageRep(data: sourceImage.TIFFRepresentation!)?.CGImage!

// 新しいサイズのビットマップを作成します。
let width = UInt(newSize.width)
let height = UInt(newSize.height)
let bitsPerComponent = UInt(8)
let bytesPerRow = UInt(4) * width
let colorSpace = CGColorSpaceCreateDeviceRGB()
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.PremultipliedLast.rawValue)

let bitmapContext = CGBitmapContextCreate(nil, width, height, bitsPerComponent, bytesPerRow, colorSpace, bitmapInfo)!

// 元画像 (image) をビットマップに書き出します。
let bitmapSize = NSMakeSize(CGFloat(width), CGFloat(height))
let bitmapRect = NSMakeRect(0.0, 0.0, bitmapSize.width, bitmapSize.height)

CGContextDrawImage(bitmapContext, bitmapRect, image)

// ビットマップを NSImage に変換します。
let newImageRef = CGBitmapContextCreateImage(bitmapContext)!
let newImage = NSImage(CGImage: newImageRef, size: bitmapSize)

このようにすることで、画像をリサイズできました。

ソースコード内の ! が現れる場所は、場合によっては変換できずに nil が得られる場合があります。実際に使う際には nil だった場合のエラー処理を実装するのが良いでしょう。

期待どおりに使えなかったリサイズ方法

上記のコードに至る中で、いまいち期待どおりに動かなかったり、Playground でなら動くのにアプリに組み込むと動かなくなるといったことがありました。

その直接的な原因が理解できていないのと、上記の方法が確実かも判断できないところなので、備忘録として以下に記しておくことにします。上手くいかなかったときの助けになるかもしれません。

NSImagelockFocusメソッド を使う方法

NSImagelockFocusメソッド メソッドを使うと、コード的に簡単にリサイズ画像を作れるのですが、実行環境がRetina Mac 環境の場合、リサイズ後の画像サイズが倍(DPI が倍)になってしまう様子です。

let newImage = NSImage(size: newSize)
let imageRect = NSRect(x: 0.0, y: 0.0, width: newSize.width, height: newSize.height)

// lockFocus は通常スクリーンで 72dpi, Retina スクリーンで 144dpi になる様子
if let imageRep = image.bestRepresentationForRect(imageRect, context: nil, hints: nil) {

	newImage.lockFocus()
	imageRep.drawInRect(imageRect)
	newImage.unlockFocus()
}

NSBitmapImageRep でリサイズする方法

NSImageCGImage に変換して、それをNSBitmapImageRep で変換する方法もあるようでしたが、これだとなぜか指定したサイズと微妙にずれた画像が生成される様子でした。

let imageRef = sourceImage.CGImageForProposedRect(nil, context: nil, hints: nil)!

let image = imageRef.takeRetainedValue()
let imageRep = NSBitmapImageRep(CGImage: image)

// 指定通りのサイズにならない様子です。
imageRep.size = newSize

let newImage = NSImage(CGImage: imageRep.CGImage!, size: newSize)

指定するサイズが小さすぎるとなのかどうなのか、リサイズされないこともありました。

NSImage からCGImage を取得する方法として…

最終的にはNSImageTIFFRepresentationプロパティNSBitmapImageRep を使ってNSImage からCGImage を取得する方法をとりましたが、次のようにCGImageForProposedRectメソッド を使って取得することもできるようです。

let image = sourceImage.CGImageForProposedRect(nil, context: nil, hints: nil)!.takeRetainedValue()

ただ、この方法はXcode 6.1 のPlayground 上では期待どおりに動いてくれたのですが、いざOS X アプリで使ってみたところ、原因はわからないのですがCGImageForProposedRectメソッド のところでnil が得られてしまいました。