UIImage, CGImageの面倒なとこ
色々面倒なので拡張を作っています。
以下のサンプル、相互に参照しているのでメソッド足りなければ記事から探してください。
CGImage, CIImage変換
UIImage.ciImageは、UIImageがCIImageから作られていたときのみ値があり、その他のときはnilです。nilのときでも、 CIImage(image: self)
だと成功することもあります。
cgImageも同様に、ciImageから作られているとnilなので、以下のようにして取得すると成功率が高いです。
extension UIImage {
var safeCiImage: CIImage? {
return self.ciImage ?? CIImage(image: self)
}
var safeCgImage: CGImage? {
if let cgImge = self.cgImage {
return cgImge
}
if let ciImage = safeCiImage {
let context = CIContext(options: nil)
return context.createCGImage(ciImage, from: ciImage.extent)
}
return nil
}
}
クロップ処理
UIImageから特定の領域のみを切り取った画像を作りたい時、便利です
extension UIImage {
/// Create UIImage by cropping current image with the specified rectangle.
///
/// - parameter rect: The rectangle to crop
///
/// - returns: The cropped image. Nil on error.
func cropped(to rect: CGRect) -> UIImage? {
guard let unwrappedCGImage = safeCgImage else {
return nil
}
guard let imgRef = unwrappedCGImage.cropping(to: rect) else {
return nil
}
return UIImage(cgImage: imgRef, scale: scale, orientation: imageOrientation)
}
}
回転処理
UIImageを回転させた画像を作りたい時、便利です
extension UIImage {
/// Create UIImage by rotating current image and crop center to keep image-size.
/// The image size may not be changed
/// https://stackoverflow.com/questions/40389215/swift-rotate-an-image-then-zoom-in-and-crop-to-keep-width-height-aspect-ratio
///
/// - parameter angle: Angle to rotate
///
/// - returns: The rotated image. Nil on error.
func rotatedAndCropped(angle: CGFloat) -> UIImage? {
guard let ciImage = safeCiImage else {
return nil
}
let rotated = ciImage.applyingFilter("CIStraightenFilter", parameters: [kCIInputAngleKey: -angle])
return UIImage(ciImage: rotated)
}
/// Create UIImage by rotating current image.
/// The image size may changed to covert as rectangle
/// http://blogs.innovationm.com/image-croprotateresize-handling-in-ios/
///
/// - parameter angle: Angle to rotate
/// - parameter flipVertical: Whether the image will be flipped vertically or not. Defaults to false
/// - parameter flipHorizontal: Whether the image will be flipped horizontally or not. Defaults to false
///
/// - returns: The rotated image. Nil on error.
func rotated(angle: CGFloat, flipVertical: Bool = false, flipHorizontal: Bool = false) -> UIImage? {
guard let ciImage = safeCiImage else {
return nil
}
guard let filter = CIFilter(name: "CIAffineTransform") else {
return nil
}
filter.setValue(ciImage, forKey: kCIInputImageKey)
filter.setDefaults()
let newAngle = angle * CGFloat(-1)
var transform = CATransform3DIdentity
transform = CATransform3DRotate(transform, CGFloat(newAngle), 0, 0, 1)
transform = CATransform3DRotate(transform, (flipVertical ? 1.0 : 0) * CGFloat.pi, 0, 1, 0)
transform = CATransform3DRotate(transform, (flipHorizontal ? 1.0 : 0) * CGFloat.pi, 1, 0, 0)
let affineTransform = CATransform3DGetAffineTransform(transform)
filter.setValue(NSValue(cgAffineTransform: affineTransform), forKey: kCIInputTransformKey)
guard let outputImage = filter.outputImage else {
return nil
}
let context = CIContext(options: [kCIContextUseSoftwareRenderer: true])
guard let cgImage = context.createCGImage(outputImage, from: outputImage.extent) else {
return nil
}
return UIImage(cgImage: cgImage)
}
}
背景色の設定
透過画像から、背景を塗った画像を作りたい時に便利です
extension UIImage {
/// Create UIImage by drawing current image on colored context.
///
/// - parameter color: Color of the background context
///
/// - returns: The created image. Nil on error.
func withSettingBackground(color: UIColor) -> UIImage? {
UIGraphicsBeginImageContext(size)
defer {
UIGraphicsEndImageContext()
}
guard let context = UIGraphicsGetCurrentContext() else {
return nil
}
guard let cgImage = safeCgImage else {
return nil
}
let frame = CGRect(origin: .zero, size: size)
context.clear(frame)
context.setFillColor(color.cgColor)
context.fill(frame)
context.draw(cgImage, in: frame)
return UIGraphicsGetImageFromCurrentImageContext()
}
}
白を透過する
CALayerのmaskは透過色を透過し、ベタ塗りは透過しませんが、
CGImageのmaskは白を透過し、黒を透過しません。
よって、この変換のため、真っ黒なCGImageに自分自身でCGImageのmaskをかけることで、白を透過色にした画像を生成することで対応しました。
extension UIImage {
/// Create UIImage of black or transparent.
/// CGImage-masking treats white as transparent, while CALayer-masking
/// treats transparent as transparent.
/// For this reason, we should make white of UIImage transparent to use
/// the CGImage-masking image for CALayer-masking image.
///
/// - parameter inverse: Invert transparent area or not
///
/// - returns: The created image. Nil on error.
func blacked(inverse: Bool = false) -> UIImage? {
if inverse {
guard let mask = UIImage.empty(size: size, color: .white)?.masked(with: self)?.withSettingBackground(color: .black) else {
return nil
}
return UIImage.empty(size: size, color: .black)?.masked(with: mask)
} else {
return UIImage.empty(size: size, color: .black)?.masked(with: self)
}
}
}
マスク処理
白黒のUIImageから、黒部分のみをマスクした画像を生成します。
透過色を用いている場合は、上の blacked
を通してから設定してください。
extension UIImage {
/// Create UIImage by masking current image with another image.
/// Treat white as transparent.
///
/// - parameter image: Image for masking
///
/// - returns: The created image. Nil on error.
func masked(with image: UIImage) -> UIImage? {
guard let maskRef = image.safeCgImage,
let ref = safeCgImage,
let dataProvider = maskRef.dataProvider else {
return nil
}
let mask = CGImage(maskWidth: maskRef.width,
height: maskRef.height,
bitsPerComponent: maskRef.bitsPerComponent,
bitsPerPixel: maskRef.bitsPerPixel,
bytesPerRow: maskRef.bytesPerRow,
provider: dataProvider,
decode: nil,
shouldInterpolate: false)
return mask
.flatMap { ref.masking($0) }
.map { UIImage(cgImage: $0) }
}
}
画像の生成
ベタ塗り画像、円の画像、文字の画像を生成します
extension UIImage {
/// Create UIImage filled with a color.
///
/// - parameter size: Size of output image
/// - parameter color: Color to fill
///
/// - returns: The created image. Nil on error.
static func empty(size: CGSize, color: UIColor = .clear) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(size, true, UIScreen.main.scale)
defer {
UIGraphicsEndImageContext()
}
guard let context = UIGraphicsGetCurrentContext() else {
return nil
}
let frame = CGRect(origin: .zero, size: size)
context.clear(frame)
context.setFillColor(color.cgColor)
context.fill(frame)
return UIGraphicsGetImageFromCurrentImageContext()
}
/// Create UIImage of circle.
///
/// - parameter size: Size of output image
/// - parameter color: Color of the circle
/// - parameter backgroundColor: Background color of the image
///
/// - returns: The created image. Nil on error.
static func circle(size: CGSize, color: UIColor, backgroundColor: UIColor = .clear) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(size, true, UIScreen.main.scale)
defer {
UIGraphicsEndImageContext()
}
guard let context = UIGraphicsGetCurrentContext() else {
return nil
}
let frame = CGRect(origin: .zero, size: size)
context.clear(frame)
// background
context.setFillColor(backgroundColor.cgColor)
context.fill(frame)
// circle
context.setFillColor(color.cgColor)
context.setLineWidth(0)
context.addEllipse(in: frame)
context.fillPath()
return UIGraphicsGetImageFromCurrentImageContext()
}
/// Create UIImage by drawing text.
///
/// - parameter text: string to draw
/// - parameter fontSize: size of text
///
/// - returns: The created image. Nil on error.
static func fromText(text: String, fontSize: CGFloat) -> UIImage? {
let attributes = [
NSAttributedStringKey.font: UIFont.systemFont(ofSize: fontSize),
NSAttributedStringKey.foregroundColor: UIColor.white,
NSAttributedStringKey.paragraphStyle: NSMutableParagraphStyle.default
]
let imageSize = (text as NSString).size(withAttributes: attributes)
UIGraphicsBeginImageContextWithOptions(imageSize, false, UIScreen.main.scale)
guard let context = UIGraphicsGetCurrentContext() else {
return nil
}
context.setTextDrawingMode(CGTextDrawingMode.fill)
let textRect = CGRect(origin: .zero, size: imageSize)
text.draw(in: textRect, withAttributes: attributes)
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage
}
}