MILLEN BOX

音楽好きの組み込みソフトエンジニアによるプログラミング(主にiOSアプリ開発)の勉強の記録

UIImageを塗りつぶしてみよう [UIImage]

ふとUIImageを塗りつぶしたいと思ったのですがちょっと詰まった為、以下にメモを残しておきます。

以下の記事をがっつり参考にしました。

qiita.com

ソースを以下に貼っておきます。

func fillColorWithUIImage(image: UIImage) -> UIImage {
        let imageSize = CGSizeMake(image.size.width, image.size.height)               //サイズの決定
        let imageRect = CGRectMake(0, 0, imageSize.width, imageSize.height)         //キャンバスのRectの決定
        UIGraphicsBeginImageContextWithOptions(imageSize, false, 0.0)               //コンテキスト作成(キャンバスのUIImageを作成する為)
        UIColor.whiteColor().setFill()                                              //白色塗りつぶし作業1
        UIRectFill(imageRect)                                                       //白色塗りつぶし作業2
        image.drawInRect(imageRect)                                                 //内容を描く(真っ白)
        let retImage = UIGraphicsGetImageFromCurrentImageContext()                  //何も描かれてないUIImageを取得
        UIGraphicsEndImageContext()                                                 //コンテキストを閉じる
        return retImage                                                             //塗りつぶしUIImageを返す
}

以上です。

意外と知らない!?UIImageViewを削除する方法 [UIImageView]

UIImageViewを削除する方法...ってどうやるんだっけ?ということがあったのでメモしておきます。

どこかで以下のようにインスタンス化しているUIImageView。

let imageView = UIImageView(image: uiImage)

removeFromSuperviewを使用することで削除できます。

imageView.removeFromSuperview()

本日は以上。

extensionを使ってみる [swift2.2]

前回、タップした瞬間に円を描くサンプルを作成しました。
▶︎ swiftで円を描く [UIGestureRecognizer] [drawInRect] [Context] [swift 2.2] - MILLEN BOX

それを見た方から「extensionを使うといいよ〜」とアドバイス頂きましたのでやってみました。
今回はその方法のメモ。

Githubは以下です。

▶︎GitHub - anthrgrnwrld/oekakiGestureRecognizer at branch

あくまでextensionの使い方のメモですので、「変数をこうした方がいいぜ!」という部分はたくさん含まれていると思われます。

ソースをベタっと貼っときます

extensionUIImage.swift

import UIKit

extension UIImage {
    
    func drawCircle(size: CGSize, center: CGPoint) -> UIImage {
        
        let radius: CGFloat = 20.0                                          //Drawする円の半径
        let red: CGFloat = 1.0                                              //Drawする色 R
        let blue: CGFloat = 0.0                                             //Drawする色 B
        let green: CGFloat = 0.0                                            //Drawする色 G
        let alpha: CGFloat = 1.0                                            //Drawする色 Alpha
        
        UIGraphicsBeginImageContextWithOptions(size, false, 0.0)            //コンテキストを取得
        
        //タップした時に描く円のRect
        let circleRect = CGRectMake(center.x - radius, center.y - radius, radius * 2, radius * 2)
        
        
        self.drawInRect(CGRectMake(0, 0, size.width, size.height))          //コンテキストにimageViewの内容を写す
        let circlePath = UIBezierPath(ovalInRect: circleRect)               //円を描く
        let color = UIColor(red:red, green:green, blue:blue, alpha:alpha)   //透明色を格納
        color.setFill()
        circlePath.fill()
        let retImage = UIGraphicsGetImageFromCurrentImageContext()          //UIImageを取得する
        UIGraphicsEndImageContext()                                         //コンテキストを閉じる
        
        return retImage                                                     //UIImageをReturnする
        
    }
    
}

ViewController.swift

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var imageView: UIImageView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        
        let myOekaki = OekakiGestureRecognizer(target: self, action: #selector(ViewController.drawGesture(_:)))
        self.view.addGestureRecognizer(myOekaki)
        
        
        let size = self.imageView.frame.size                                //コンテキストのサイズ
        
        //(extension対策)self.imageView.imageに所期画像を追加しておく
        UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
        self.imageView.image?.drawInRect(CGRectMake(0, 0, size.width, size.height))
        self.imageView.image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    
    
    internal func drawGesture(sender: AnyObject) {
        
        guard let oekakiGesture = sender as? OekakiGestureRecognizer else {
            print("Oekaki Gesture Error happened.")
            return
        }
        
        switch oekakiGesture.state {
            
        case .Began:
            
            let size = self.imageView.frame.size                                //コンテキストのサイズ
            let touchPoint = oekakiGesture.locationInView(self.imageView)       //タッチ座標を取得
            
            //Drawを実行 -> 結果をUIImageにて取得
            //let imageAfterDraw = drawCircle(self.imageView.image, size: size, center: touchPoint)
            let imageAfterDraw = self.imageView.image?.drawCircle(size, center: touchPoint)
            self.imageView.image = imageAfterDraw
            
        default:
            break
            
        }
        
    }



}

次はprotocolのサンプルを作ってみようと思う今日この頃です。

swiftで円を描く [UIGestureRecognizer] [drawInRect] [Context] [swift 2.2]

画像イメージを貼り付け表示とかではなく、swiftで円を描く方法のメモです。
今回は前回作成した OekakiGestureRecognizer を使ってタップした瞬間に円を描くサンプルを作成しました。

参考URL: OekakiGestureRecognizer (touchDownを認識するGestureRecognizer) [UIGestureRecognizer] - MILLEN BOX

Githubは以下です。

▶︎GitHub - anthrgrnwrld/oekakiGestureRecognizer

ソースをベタっと貼り付けときます

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var imageView: UIImageView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        
        let myOekaki = OekakiGestureRecognizer(target: self, action: #selector(ViewController.drawGesture(_:)))
        self.view.addGestureRecognizer(myOekaki)
        
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    
    
    internal func drawGesture(sender: AnyObject) {
        
        guard let oekakiGesture = sender as? OekakiGestureRecognizer else {
            print("Oekaki Gesture Error happened.")
            return
        }
        
        switch oekakiGesture.state {
            
        case .Began:
            
            let size = self.imageView.frame.size                                //コンテキストのサイズ
            let touchPoint = oekakiGesture.locationInView(self.imageView)       //タッチ座標を取得
            
            //Drawを実行 -> 結果をUIImageにて取得
            let imageAfterDraw = drawCircle(self.imageView.image, size: size, center: touchPoint)
            self.imageView.image = imageAfterDraw
            
        default:
            break
            
        }
    
    }
    
    func drawCircle(canvas: UIImage?, size: CGSize, center: CGPoint) -> UIImage {
        
        let radius: CGFloat = 20.0                                          //Drawする円の半径
        let red: CGFloat = 1.0                                              //Drawする色 R
        let blue: CGFloat = 0.0                                             //Drawする色 B
        let green: CGFloat = 0.0                                            //Drawする色 G
        let alpha: CGFloat = 1.0                                            //Drawする色 Alpha
        
        UIGraphicsBeginImageContextWithOptions(size, false, 0.0)            //コンテキストを取得
        
        //タップした時に描く円のRect
        let circleRect = CGRectMake(center.x - radius, center.y - radius, radius * 2, radius * 2)
        

        canvas?.drawInRect(CGRectMake(0, 0, size.width, size.height))        //コンテキストにimageViewの内容を写す
        let circlePath = UIBezierPath(ovalInRect: circleRect)               //円を描く
        let color = UIColor(red:red, green:green, blue:blue, alpha:alpha)   //透明色を格納
        color.setFill()
        circlePath.fill()
        let retImage = UIGraphicsGetImageFromCurrentImageContext()          //UIImageを取得する
        UIGraphicsEndImageContext()                                         //コンテキストを閉じる
        
        return retImage                                                     //UIImageをReturnする
        
    }


}

こんな風に北斗七星が書けるよ!
f:id:anthrgrnwrld:20160701192840p:plain

ふとした時に役に立つかもです。

OekakiGestureRecognizer (touchDownを認識するGestureRecognizer) [UIGestureRecognizer]

現在簡単なお絵描き機能を持ったアプリを作成中ですが、その実現方法として当初UIPanGestureRecognizerを使用してタップ・タッチを検出していました。
しかしUIPanGestureRecognizerでお絵描き機能を実現した場合、画面に指を引っ付けて指を動かしたり離したりした時にタップが検出されます。
画面にタップした瞬間は検出されません。よって画面をタップして指を動かさない場合には、お絵描きの実行はされないのです。

しかし現実世界では紙に筆を落とした瞬間に墨の色が付くわけで。
今回はその実現のため、 UIGestureRecognizer を拡張した OekakiGestureRecognizer を作ってみましたのでメモしておきます。

Githubは以下です。

▶︎oekakiGestureRecognizer/OekakiGestureRecognizer.swift at master · anthrgrnwrld/oekakiGestureRecognizer · GitHub

OekakiGestureRecognizer

import UIKit
import UIKit.UIGestureRecognizerSubclass

class OekakiGestureRecognizer: UIGestureRecognizer
{
    
    var touchPoint: CGPoint?                                //タッチ座標を格納
    
    override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent)
    {
        
        let numberOfTouches = event.allTouches()!.count     //タッチした指の本数を取得
        
        if numberOfTouches != 1 {
            self.state = .Cancelled                         //タッチ本数が1本以外の場合はstateをCancelに
        }
        
        if self.state == .Possible {
            let touch = touches.first! as UITouch
            touchPoint = touch.locationInView(self.view)    //タッチ座標を取得・格納
            self.state = .Began                             //stateをBeganに
        }
    }
    
    override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent) {
        let touch = touches.first! as UITouch
        touchPoint = touch.locationInView(self.view)        //タッチ座標を取得・格納
        self.state = .Changed                               //stateをChangedに
    }
    
    override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent) {
        let touch = touches.first! as UITouch
        touchPoint = touch.locationInView(self.view)        //タッチ座標を取得・格納
        self.state = .Ended                                 //stateをEndedに
    }
    
    override func touchesCancelled(touches: Set<UITouch>, withEvent event: UIEvent) {
        let touch = touches.first! as UITouch
        touchPoint = touch.locationInView(self.view)        //タッチ座標を取得・格納
        self.state  = .Cancelled                            //stateをCancelledに
    }
    
    override func locationInView(view: UIView?) -> CGPoint {

        guard var tchpnt = touchPoint else {
            fatalError("Can't get touchPoint")
        }
        
        //parameterのviewがUIScrollViewか否かで処理を分岐(拡大時対応の為)
        if let parameterView = self.view! as? UIScrollView {
            
            let contentSize = parameterView.contentSize
            let rate = self.view!.bounds.width / contentSize.width
            
            tchpnt.x = tchpnt.x * rate
            tchpnt.y = tchpnt.y * rate
            
            return tchpnt                                   //parameterのviewがUIScrollViewだった時

        } else {
            return tchpnt                                       //parameterのviewがUIScrollView以外だった時
        }


    }

    
}

正直検証しきれてないので想定していない動作で何が起こるか分かってませんが、
恐らく想定していない動作については「何も起こらない」ようになっている為、大きな問題にはならないんじゃないかと楽観的に考えています。

ScrollViewのdelegate [UIScrollView]

UIScrollViewにて画像の拡大縮小に対応した時、特定の動作(拡大縮小やスクロールなど)時のみ消したいViewがある!といったことがあるかもしれません。
今回はその実現方法のメモです。

    /**
     写真の拡大縮小に対応
     */
    func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
        return self.pictureView
    }
    
    /**
     写真の拡大縮小中に「とあるview」を隠す
     */
    func scrollViewWillBeginZooming(scrollView: UIScrollView, withView view: UIView?) {
        toaruView.hidden = true
    }
    
    /**
     写真の拡大縮小完了で「とあるview」を隠していたのを解除する
     */
    func scrollViewDidEndZooming(scrollView: UIScrollView, withView view: UIView?, atScale scale: CGFloat) {
        toaruView.hidden = false
    }

    /**
     写真のスクロール中に「とあるview」を隠す
     */
    func scrollViewWillBeginDragging(scrollView: UIScrollView) {
        toaruView.hidden = true
    }
    /**
     写真のスクロール完了で「とあるview」を隠していたのを解除する
     */
    func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
        toaruView.hidden = false
    }

以上です。

Core ImageでBlur画像を作成する(Ver.1 & 2) [CIFilter]

CoreImageにてBlur画像を作成する為の関数についてメモを残しときます。

1. ガウジアンFilter関数 Ver.1

以下は元画像(CIImage)からBlur画像を作成する関数です。
UIImageからCIImageのゲット方法は let imageCIImage = CIImage(CGImage: imageUIImage.CGImage!) です。

    /**
     ガウジアンFilterを適応する
     
     - parameter inputCIImage : 元CIICmage
     - parameter value : フィルターのかけ度合い
     - returns : ぼかし画像のサイズ
     */
    func applyGaussianBlurFilter(inputCIImage: CIImage, value: Float) -> CIImage {

        //ガウシンアン(ぼかし)フィルターの適応
        let filter = CIFilter(name: "CIGaussianBlur")
        filter?.setValue(inputCIImage, forKey: kCIInputImageKey)
        filter?.setValue((value), forKey: kCIInputRadiusKey)
        let outputCIImage = filter?.outputImage
        
        guard let _outputCIImage = outputCIImage else {
            fatalError("applyGaussianBlurFilter does not work.")
        }
        
        return _outputCIImage

    }

CIImageからUIImageへの復元は以下を参考にして下さい。
(変数が何を示しているかは関数名から想像お願いいたします...汗)

let rect = CGRect(origin: CGPointZero, size: imageUIImage.size)
let context = CIContext(options: nil)
let cgImage = context.createCGImage(outputCIImage, fromRect: rect)
let blurImage = UIImage(CGImage: cgImage)

2. ガウジアンFilter関数 Ver.2

Ver.1 のままだとガウジアンFilter適応後の画像端が透明色のグラデーションがかかってしまう問題があります。
対策としてClampフィルターを前処理として適応します。
この辺の詳細については以下を参照の事。
▶︎ objective c - Correct crop of CIGaussianBlur - Stack Overflow

以下、関数です。

    /**
     ガウジアンFilterを適応する
     
     - parameter inputCIImage : 元CIICmage
     - parameter value : フィルターのかけ度合い
     - returns : ぼかし画像のサイズ
     */
    func applyGaussianBlurFilter(inputCIImage: CIImage, value: Float) -> CIImage {
        
        //inputCIImageを何も考えずにCIGaussianBlurをかけると画面端に透過色が混じる
        //その対策の為、CIAffineClampを使い画面外側に拡大した元画像を配置し現象の対策を行う
        //URL: http://stackoverflow.com/questions/12839729/correct-crop-of-cigaussianblur
        let affineClampFilter = CIFilter(name: "CIAffineClamp")
        let xform = CGAffineTransformMakeScale(1.0, 1.0)
        affineClampFilter?.setValue(inputCIImage, forKey: kCIInputImageKey)
        affineClampFilter?.setValue(NSValue(CGAffineTransform: xform), forKey: "inputTransform")
        let outputAfterCalmp = affineClampFilter?.outputImage

        //ガウシンアン(ぼかし)フィルターの適応
        let filter = CIFilter(name: "CIGaussianBlur")
        filter?.setValue(outputAfterCalmp, forKey: kCIInputImageKey)
        filter?.setValue((value), forKey: kCIInputRadiusKey)
        let outputCIImage = filter?.outputImage
        
        guard let _outputCIImage = outputCIImage else {
            fatalError("applyGaussianBlurFilter does not work.")
        }
        
        return _outputCIImage

    }

CIImageからUIImageへの復元はrectの取得方法に変更があります。

let rect = inputCIImage.extent
let context = CIContext(options: nil)
let cgImage = context.createCGImage(outputCIImage, fromRect: rect)
let blurImage = UIImage(CGImage: cgImage)

所感

Ver.1で発生した「画面端透明問題」で結構ハマってしましました。
情報提供頂いた @akio0911 さんには感謝です!