スタッフブログ

STAFF BLOG

アプリ開発日誌

2016.07.22

【iOS】スワイプで画像にお絵かきできるプログラムをSwiftで書いてみた

こんにちは。鶴本です。

今回はスワイプで画像にお絵かきできるプログラムをSwiftで書いてみました。画像に線を描画する手順としては。。。

1:ビットマップ画像のコンテキストを作成
2:お絵かきしたい画像を描画する
3:線の各設定
4:線を引く始点と終点の設定
5:線を引く
6:線が引かれた画像を手順2の画像に反映させる
7:コンテキストの解放

以上をスワイプし続けている限り行います。このお絵かき機能を持ったカスタムクラスPaintImageViewのコードを紹介します。

1:ビットマップ画像のコンテキストを作成

        // ビットマップ画像のコンテキストを作成
        UIGraphicsBeginImageContext(self.frame.size);

コンテキストのサイズには PaintImageViewのsizeを指定します。

2:お絵かきしたい画像を描画する

        // 現在の画像を描画
        self.image?.drawInRect(CGRect.init(x: 0, y: 0, width: self.frame.size.width, height: self.frame.size.height))

PaintImageViewの画像を描画します。

3:線の設定

        // 線の太さと色の指定
        CGContextSetLineWidth(UIGraphicsGetCurrentContext(), strokeWidth);
        CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), 1.0, 0, 0, 1.0);
        
        // 線の角を丸くする
        CGContextSetLineCap(UIGraphicsGetCurrentContext(), .Round);

ここでは線の太さと色と線の角を丸くする設定をしています。この部分はお好みで。

4:線を引く始点と終点の設定

        // 線の始点と終点を設定する
        CGContextMoveToPoint(UIGraphicsGetCurrentContext(), tapedPoint.x, tapedPoint.y);
        CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), endPoint.x, endPoint.y);

線の始点(CGContextMoveToPoint)にはスワイプ開始地点を、終点(CGContextAddLineToPoint)にはスワイプの終了地点を設定しています。

5:線を引く

        // 線を引く
        CGContextStrokePath(UIGraphicsGetCurrentContext());

手順4で設定した始点から終点にかけて線を描画します。

6:線が引かれた画像を手順2の画像に反映させる

        // 線が引かれた画像を自分に設定する
        self.image = UIGraphicsGetImageFromCurrentImageContext();

PaintImageViewのimageプロパティに線が引かれた画像を設定しています。

7:コンテキストの解放

        // 描画領域を解放
        UIGraphicsEndImageContext();

これを忘れるとメモリが枯渇するので忘れずに。

PaintImageView

1から7までがメインの描画処理となります。PaintImageViewの全体を載せておきます。

//
//  PaintImageView.swift
//  PracticeSwift
//
//  Created by 鶴本賢太朗 on 2016/07/11.
//  Copyright © 2016年 鶴本賢太朗. All rights reserved.
//

import UIKit

// スワイプで塗りつぶしができるImageView
class PaintImageView: UIImageView {
    
    // 線の太さ
    var strokeWidth: CGFloat = 10.0
    
    // タップした位置
    private var tapedPoint: CGPoint = CGPoint()
    
    // 塗りつぶす前の元の画像
    private var originalImage: UIImage = UIImage()
    
    // MARK: 初期化メソッド
    func initialize() {
        self.userInteractionEnabled = true
        
        guard let image = self.image else {
            // imageの監視スタート
            self.addObserver(self, forKeyPath: "image", options: .New, context: nil)
            return
        }
        
        self.originalImage = image
    }
    
    override init(image: UIImage?) {
        super.init(image: image)
        initialize()
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        initialize()
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        initialize()
    }
    
    // MARK: 画面タップイベント
    override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
        if let touch = touches.first {
            tapedPoint = touch.locationInView(self)
        }
    }
    override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
        if let touch = touches.first {
            let endPoint = touch.locationInView(self)
            draw(endPoint)
        }
    }
    
    // MARK: imageが設定された際に呼ばれる
    override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
        if keyPath == "image" {
            
            // 画像を保持しておく
            originalImage = change![NSKeyValueChangeNewKey] as! UIImage
            
            // もう監視しない
            self.removeObserver(self, forKeyPath: "image")
        }
    }
    
    // MARK: 線を描画する
    func draw(endPoint: CGPoint) {
        
        // ビットマップ画像のコンテキストを作成
        UIGraphicsBeginImageContext(self.frame.size);
        
        // 現在の画像を描画
        self.image?.drawInRect(CGRect.init(x: 0, y: 0, width: self.frame.size.width, height: self.frame.size.height))
        
        // 線の太さと色の指定
        CGContextSetLineWidth(UIGraphicsGetCurrentContext(), strokeWidth);
        CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), 1.0, 0, 0, 1.0);
        
        // 線の角を丸くする
        CGContextSetLineCap(UIGraphicsGetCurrentContext(), .Round);
        
        // 線の始点と終点を設定する
        CGContextMoveToPoint(UIGraphicsGetCurrentContext(), tapedPoint.x, tapedPoint.y);
        CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), endPoint.x, endPoint.y);
        
        // 線を引く
        CGContextStrokePath(UIGraphicsGetCurrentContext());
        
        // 線が引かれた画像を自分に設定する
        self.image = UIGraphicsGetImageFromCurrentImageContext();
        
        // 描画領域を解放
        UIGraphicsEndImageContext();
        
        // 現在のタッチ座標を次に線を引く開始座標にする
        tapedPoint = endPoint;
    }
    
    // MARK: 塗りつぶした線を全部消して元の画像に戻す
    func clear() {
        self.image = nil
        self.image = originalImage
    }
}

スワイプの始点と終点はtouchesBeganとtouchesMovedで取得しています。また元の画像に戻すためにClearメソッドを実装しています。このメソッドの実現のために元々の画像をオブザーバとイニシャライザを使って保持するようにしています。

クライアントコード

PaintImageViewを表示するためにクライアントコードでは例えば以下のように書きます。

    override func viewDidLoad() {
        
        super.viewDidLoad()

        paintImageView = PaintImageView.init(image: UIImage.init(named: "03-logo"))
        paintImageView?.frame = self.view.frame
        
        self.view.addSubview(paintImageView!)
    }

プレビュー

実際にお絵かきした画像を載せます。

IMG_0333

終わりに

画像を加工するプログラムはあまり書いたことがなかったのでいい経験になりました。今度は画像のトリミングなどにチャレンジしてみたいと思います。

BACK

お問合せ

イーディーエーに興味をお持ちいただいて
ありがとうございます!
スマホアプリに関するご相談、
お見積りや弊社へのご質問など、
お気軽にお問い合わせください。
担当者より折り返しご連絡させていただきます。

    お名前必須
    会社名
    メールアドレス必須
    電話番号必須
    お問合わせ種別必須
    お問合わせ内容必須

    アンケートにご協力ください。
    弊社サイトへはどのようにしてアクセスされましたか?

    個人情報のお取扱いに関する同意事項

    1.事業者の氏名又は名称

    株式会社イーディーエー

    2.個人情報保護管理者の氏名又は職名、所属及び連絡先

    個人情報保護管理者 小宮 保人
    Mail:[email protected]

    3.取得した個人情報の利用目的

    当フォームで取得した個人情報は、お問い合わせに関する回答のために利用し、目的外利用はいたしません。

    4.弊社が取得した個人情報の第三者への委託、提供について

    弊社は、ご本人に関する情報をご本人の同意なしに第三者に委託または提供することはありません。

    5.個人情報保護のための安全管理

    弊社は、ご本人の個人情報を保護するための規程類を定め、従業者全員に周知・徹底と啓発・教育を図るとともに、その遵守状況の監査を定期的に実施いたします。
    また、ご本人の個人情報を保護するために必要な安全管理措置の維持・向上に努めてまいります。

    6.個人情報の開示・訂正・利用停止等の手続

    ご本人が、弊社が保有するご自身の個人情報の、利用目的の通知、開示、内容の訂正、追加又は削除、利用の停止、消去及び第三者への提供の停止を求める場合には、下記に連絡を頂くことで、対応致します。

    株式会社イーディーエー 個人情報お問合せ窓口
    〒106-0032 東京都港区六本木7丁目14番23 ラウンドクロス六本木4F
    TEL:03-5422-7524 FAX:03-5422-7534
    Mail:[email protected]

    7.ご提供いただく情報の任意性

    個人情報のご提供は任意ですが、同意を頂けない場合には、第3項にあります利用目的が達成できない事をご了承いただくこととなります。

    8.弊社Webサイトの運営について

    弊社サイトでは、ご本人が弊社Webサイトを再度訪問されたときなどに、より便利に閲覧して頂けるよう「クッキー(Cookie)」という技術を使用することがあります。これは、ご本人のコンピュータが弊社Webサイトのどのページに訪れたかを記録しますが、ご本人が弊社Webサイトにおいてご自身の個人情報を入力されない限りご本人ご自身を特定、識別することはできません。
    クッキーの使用を希望されない場合は、ご本人のブラウザの設定を変更することにより、クッキーの使用を拒否することができます。その場合、一部または全部のサービスがご利用できなくなることがあります。