LoginSignup
11
12

More than 5 years have passed since last update.

UIImageをプログラムで再生成する際のパフォーマンス比較

Posted at

はじめに

通常だとUIImageViewに画像を設定してUIImageView自体に角丸をつけたりすることが多いと思うのですが、NSTextAttachmentUITextView上などに画像を簡単に置きたい場合にはUIImageで置くしかありません。そのため、UIImageの画像自体に角丸をつける必要があったので、ついでに描画のパフォーマンスを調べようと思います。

描画方法

その1

  • UIImageからCGImageを取り出してリサイズ
  • リサイズされた画像をUIImageView設定
  • マスク用のUIBezierPathも設定
  • UIImageViewCALayerCGContext上に描画して、そこから新しいUIImageを生成
private struct SizeReletaion {
    private enum Element {
        case Height
        case Width
    }
    let longerSide: (value: CGFloat, element: Element)
    let shoterSide: (value: CGFloat, element: Element)
    init(size: CGSize, scale: CGFloat) {
        longerSide = size.width > size.height ? (size.width * scale, .Width) : (size.height * scale, .Height)
        shoterSide = size.width > size.height ? (size.height * scale, .Height) : (size.width * scale, .Width)
    }
    func centerTrimRect() -> CGRect {
        let delta = (longerSide.value - shoterSide.value) / 2
        let x = longerSide.element == .Height ? 0 : delta
        let y = longerSide.element == .Width ? 0 : delta
        return CGRect(x: x, y: y, width: shoterSide.value, height: shoterSide.value)
    }

    func relativeCenterTrimRect(targetSize: CGSize) -> CGRect {
        let delta = -(longerSide.value - shoterSide.value) / 2
        let x = longerSide.element == .Height ? 0 : delta
        let y = longerSide.element == .Width ? 0 : delta
        let width = longerSide.element == .Width ? longerSide.value : shoterSide.value
        let height = longerSide.element == .Height ? longerSide.value : shoterSide.value
        let scale = shoterSide.element == .Height ? targetSize.height / height : targetSize.width / width
        return CGRect(
            x: x * scale,
            y: y * scale,
            width: width * scale,
            height: height * scale
        )
    }
}

extension UIImage {
    func roundedCenterTrimImage1(cornerRadius: CGFloat, targetSize: CGSize) -> UIImage? {
        let sizeRelation = SizeReletaion(size: size, scale: scale)
        guard let newImage = trimmedImage(sizeRelation.centerTrimRect()) else {
            return nil
        }
        let scaledSize = CGSize(width: targetSize.width * scale, height: targetSize.height * scale)
        UIGraphicsBeginImageContext(scaledSize)
        let path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: scaledSize.width, height: scaledSize.height), cornerRadius: cornerRadius)
        let imageView = UIImageView(image: newImage)
        imageView.frame = CGRect(x: 0, y: 0, width: scaledSize.width, height: scaledSize.height)
        imageView.layer.masksToBounds = true
        let maskLayer = CAShapeLayer()
        maskLayer.path = path.CGPath
        imageView.layer.mask = maskLayer
        guard let context = UIGraphicsGetCurrentContext() else {
            return nil
        }
        imageView.layer.renderInContext(context)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return image
    }

    func trimmedImage(rect: CGRect) -> UIImage? {
        guard let cgImage = CGImageCreateWithImageInRect(self.CGImage, rect) else {
            return nil
        }
        return UIImage(CGImage: cgImage)
    }
}

ss1.png

その2

  • フルサイズの画像をUIImageViewに設定
  • マスク用のUIBezierPathも設定
  • UIImageViewCALayerCGContext上に描画して、そこから新しいUIImageを生成
UIImageViewとCGContext
extension UIImage {
    func roundedCenterTrimImage2(cornerRadius: CGFloat, targetSize: CGSize) -> UIImage? {
        let scaledSize = CGSize(width: targetSize.width * scale, height: targetSize.height * scale)
        UIGraphicsBeginImageContext(scaledSize)
        let path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: scaledSize.width, height: scaledSize.height), cornerRadius: cornerRadius)
        let imageView = UIImageView(image: self)
        imageView.frame = CGRect(x: 0, y: 0, width: scaledSize.width, height: scaledSize.height)
        imageView.contentMode = .ScaleAspectFill
        imageView.layer.masksToBounds = true
        let maskLayer = CAShapeLayer()
        maskLayer.path = path.CGPath
        imageView.layer.mask = maskLayer
        guard let context = UIGraphicsGetCurrentContext() else {
            return nil
        }
        imageView.layer.renderInContext(context)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return image
    }
}

ss2.png

その3

  • UIImageとマスク用のUIBezierPathCGContextに描画して、そこから新しいUIImageとして生成
CGContext
extension UIImage {
    func roundedCenterTrimImage3(cornerRadius: CGFloat, targetSize: CGSize) -> UIImage? {
        let sizeRelation = SizeReletaion(size: size, scale: scale)
        let scaledSize = CGSize(width: targetSize.width * scale, height: targetSize.height * scale)
        UIGraphicsBeginImageContext(scaledSize)
        drawInRect(sizeRelation.relativeCenterTrimRect(scaledSize))
        let context = UIGraphicsGetCurrentContext()
        let path = roundedMaskPath(cornerRadius, targetSize: scaledSize)
        CGContextAddPath(context, path.CGPath)
        CGContextClip(context)
        CGContextClearRect(context, CGRect(x: 0, y: 0, width: scaledSize.width, height: scaledSize.height))
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return image
    }

    private func roundedMaskPath(cornerRadius: CGFloat, targetSize: CGSize) -> UIBezierPath {
        struct CornerContainer {
            let corner: UIRectCorner, point: CGPoint
            func linePoint(cornerRadius: CGFloat) -> CGPoint {
                let operatorValue: CGFloat
                switch corner {
                case UIRectCorner.TopLeft, UIRectCorner.BottomLeft: operatorValue = 1
                case UIRectCorner.TopRight, UIRectCorner.BottomRight: operatorValue = -1
                default: return .zero
                }
                return CGPoint(x: point.x + (operatorValue * (cornerRadius)), y: point.y)
            }
            func curvePoint(cornerRadius: CGFloat) -> CGPoint {
                let operatorValue: CGFloat
                switch corner {
                case UIRectCorner.TopLeft, UIRectCorner.TopRight: operatorValue = 1
                case UIRectCorner.BottomRight, UIRectCorner.BottomLeft: operatorValue = -1
                default: return .zero
                }
                return CGPoint(x: point.x, y: point.y + (operatorValue * (cornerRadius )))
            }
        }

        let corners: [CornerContainer] = [
            CornerContainer(corner: .TopLeft, point: .zero),
            CornerContainer(corner: .TopRight, point: CGPoint(x: targetSize.width, y: 0)),
            CornerContainer(corner: .BottomRight, point: CGPoint(x: targetSize.width, y: targetSize.height)),
            CornerContainer(corner: .BottomLeft, point: CGPoint(x: 0, y: targetSize.height))
        ]

        let path = UIBezierPath()
        corners.forEach {
            let cornerRoundPath = UIBezierPath()
            cornerRoundPath.moveToPoint($0.point)
            cornerRoundPath.addLineToPoint($0.linePoint(cornerRadius))
            cornerRoundPath.addQuadCurveToPoint($0.curvePoint(cornerRadius), controlPoint: $0.point)
            cornerRoundPath.addLineToPoint($0.point)
            cornerRoundPath.closePath()
            path.appendPath(cornerRoundPath)
        }
        return path
    }
}

ss3.png

ちなみに計測の方法は以下のように
UIImage生成前のNSDateと生成後のNSDateの差になってます。

ViewController.swift
class ViewController: UIViewController {
    @IBOutlet var imageViews: [UIImageView]!
    @IBOutlet weak var baseImageView: UIImageView!
    @IBOutlet weak var textView: UITextView!

    @IBAction func didTapRender1(sender: UIButton) {
        let image = printRenderingTime(0) {
            return $0?.roundedCenterTrimImage1(8, targetSize: CGSize(width: 80, height: 80))
        }
        imageViews[0].image = image
    }

    @IBAction func didTapRender2(sender: UIButton) {
        let image = printRenderingTime(1) {
            return $0?.roundedCenterTrimImage2(8, targetSize: CGSize(width: 80, height: 80))
        }
        imageViews[1].image = image
    }

    @IBAction func didTapRender3(sender: UIButton) {
        let image = printRenderingTime(2) {
            return $0?.roundedCenterTrimImage3(8, targetSize: CGSize(width: 80, height: 80))
        }
        imageViews[2].image = image
    }

    private func printRenderingTime(index: Int, rendring: (UIImage?) -> UIImage?) -> UIImage? {
        let image = baseImageView.image
        let startDate = NSDate()
        let roundedImage = rendring(image)
        let finishDate = NSDate()
        let rendringTime = finishDate.timeIntervalSince1970 - startDate.timeIntervalSince1970
        let text = "Rendering\((index + 1)) Time: \(rendringTime)"
        textView.insertText(text)
        textView.insertText("\n")
        print(text)
        return roundedImage
    }
}

最後に

CGImageでリサイズしたものをUIImageViewでマスクをかけたものを描画をして、UIImageにしたものが一番速かったです。
CGContextで生成したものが一番速いのかと思ってたのですが、意外な結果でした。

11
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
12