iOS
UIImage
Swift

UIImageのリサイズ方法と注意点

はじめに

UIImageをリサイズしたいと思い、とりあえず「UIImage resize」とググってみたのですが、サンプルコードを動作させると思わぬ落とし穴にハマったので、備忘録として書き残しておきます。

画像が粗くなる(画像が劣化する)!?

だいたい検索すると出てくるサンプルコードはこんな感じです。(Swift3.0に書き直してます)

extension UIImage {
    func resize(size _size: CGSize) -> UIImage? {
        let widthRatio = _size.width / size.width
        let heightRatio = _size.height / size.height
        let ratio = widthRatio < heightRatio ? widthRatio : heightRatio

        let resizedSize = CGSize(width: size.width * ratio, height: size.height * ratio)

        UIGraphicsBeginImageContext(resizedSize)
        draw(in: CGRect(origin: .zero, size: resizedSize))
        let resizedImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        return resizedImage
    }
}

これを実行すると、画像自体は希望のサイズにリサイズされたように見えますが、実際表示してみるとなんだか画像が粗い!?

原因はUIGraphicsBeginImageContext()

原因はUIGraphicsBeginImageContext(resizedSize)でした。
これを用いて実行すると、Retinaディスプレイのscaleの設定が無視され、事実上のpixelサイズで扱われてリサイズ処理がされてしまいます。
つまり1xの画像として書き出されてしまうということでした。

これで解決

ということでUIGraphicsBeginImageContext(_ size: CGSize)のかわりに、
UIGraphicsBeginImageContextWithOptions(_ size: CGSize, _ opaque: Bool, _ scale: CGFloat)を使いましょう。

こちらを用いるとscaleが引数として設定されているので、Retinaディスプレイの場合も対象となるscaleを設定することで2x, 3xのような画像に対応することができます。
先ほどのコードを書き直してみます。

extension UIImage {
    func resize(size _size: CGSize) -> UIImage? {
        let widthRatio = _size.width / size.width
        let heightRatio = _size.height / size.height
        let ratio = widthRatio < heightRatio ? widthRatio : heightRatio

        let resizedSize = CGSize(width: size.width * ratio, height: size.height * ratio)

        UIGraphicsBeginImageContextWithOptions(resizedSize, false, 0.0) // 変更
        draw(in: CGRect(origin: .zero, size: resizedSize))
        let resizedImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        return resizedImage
    }
}

UIGraphicsBeginImageContextWithOptions()の第二引数のopaqueは不透明かどうかなので、画像の不透明が保証されてる場合はtrueに指定した方がパフォーマンスがよくなるようです。
第三引数のscaleには0.0を入れておりますが、0.0を指定するとmainScreenのscaleを動的に設定してくれるので、4.7inchや5.5inchにてUIScreen.main.scaleをみて出し分ける必要はありません!

まとめ

UIImageのリサイズ処理を検索すると結構古い記事がヒットしてしまい、UIGraphicsBeginImageContext()を使っている記事を参照しやすいので、現在の環境ではUIGraphicsBeginImageContextWithOptions()を使っていきましょう。

参考