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

clane160201

こんにちは。鶴本です。

今回はスワイプで画像にお絵かきできるプログラムを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

 

終わりに

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

鶴本 賢太朗
システム開発事業部のiOSの開発担当。
Swift,Objective-Cを用いての開発がメイン。

Egg Device Application

東京品川のスマホアプリ開発会社です。
一般アプリ、業務用アプリからVRまで開発可能。

ライター一覧

求人情報

スマホアプリ開発の
相談を受け付けています

メールでのご相談

お電話でのご相談
TEL 03-5422-7524
平日10:00~18:00