【iPhone SDK】スクラッチのように指でこすって画像を削る

宝くじ売り場でスクラッチ♪というような
CMでよくあるあのスクラッチをiPhoneアプリで実現するにはどうするかを調べましたのでメモ

必要なフレームワークはCoreGraphicsです。

以下のヘッダをインポートしておきましょう
#import <CoreGraphics/CoreGraphics.h>

今回UIViewがもつ「drawRect」メソッドにて描画をおこなうため、
新しくUIViewを継承するクラスを作ります。

そしてメンバ変数に以下を追加しましょう

void* data;
CGContextRef context;

では、初期化関数内にて準備を行いましょう。

必要なメモリを確保します。
data = malloc(描画する幅 * 描画する高さ * 4);
※4は1x1のデータに必要なバイト数

コンテキストイメージハンドルを作成します

CGColorSpaceRef colorRef = CGColorSpaceCreateDeviceRGB();
context = CGBitmapContextCreate(data, 描画する幅, 描画する高さ
               , 描画色ビット数, 1行あたりのデータサイズ, colorRef, kCGImageAlphaPremultipliedFirst);
CGColorSpaceRelease(colorRef);

これでイメージ描画ハンドルの準備は終わりです。

んで、以下の関数を作ります。



- (void)drawRect:(CGRect)rect {

   

    CGImageRef imageRef = CGBitmapContextCreateImage(context);

    CGContextRef currentContext = UIGraphicsGetCurrentContext();

    CGContextDrawImage(currentContext, rect, imageRef);

    CGImageRelease(imageRef);

}

これでコンテキストに登録している画像を描画処理が終わりです。

初期準備処理完了後に以下の処理を明記しておくと、プログラム起動後図1のように起動します。
CGContextSetRGBFillColor(context, 0.5, 0.5, 0.5, 1);
CGContextFillRect(context, self.bounds);

図1
Screenshot 2011.04.11 14.43.35.png

はい、のっぺりグレー一色で描画されます。
今回iPhoneの画面いっぱいにまでFillRectするように
Rectをself.bounds指定している点を参考の際注意してください。

そして、スクラッチのように画像を削るため、
UIViewのタッチイベントをオーバーライドします。

- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
       
    CGPoint p = [[touches anyObject] locationInView: self];
    CGPoint q = [[touches anyObject] previousLocationInView: self];
   
    CGContextSetLineCap(context, kCGLineCapRound);
    CGContextBeginPath(context);
   
    CGContextMoveToPoint(context, q.x, q.y);
    CGContextAddLineToPoint(context, p.x, p.y);
    CGContextStrokePath(context);
    [self setNeedsDisplay];
}

上記処理はコンテキスト内にて鉛筆のように指でなぞった部分にラインを引く処理です。
ただそれだけなのです。

では、スクラッチのような表現にはどのように+αするのか

それは以下の設定をいれておきます。

CGContextSetBlendMode(context, kCGBlendModeClear);
CGContextSetLineWidth(context, 15);
CGContextSetRGBStrokeColor(context, 1, 1, 1, 0);

BlendModeでクリアするようにしておき、
SetLineWidthにてライン描画の太さを指定します。

ラインの色は関係ないようです。
ブレンドモードにて強制透過するようです。

以上の処理をいれて、指で画面をなぞってみた結果が図2です。

図2
Screenshot 2011.04.11 14.44.00.png

机の上にあったガムのボトルが写っているのがわかりますか?
こんな感じでスクラッチの表現ができました。ちゃんちゃん


【送料無料】iOS SDK Hacks

【送料無料】iOS SDK Hacks
価格:2,520円(税込、送料別)

この記事へのコメント

  • hiros

    ホームページの通り、プログラムを作ったのですが、うまくいきません。

    すいませんが、詳しく教えていただいていいですか。

    行ったサンプルをいただければ幸いです。
    2011年12月03日 19:04
  • ヒイロ

    >hirosさん
    まずViewの登録順はどうなっていますか?
    UIWondow
     ┗UIViewController.view
      ┗UIView(サンプルプログラムのView)
       ┗UIImageView(ボトルガムの画像)

    こんな感じで行なっていますでしょうか?

    2011年12月03日 22:50
  • hiro

    ご連絡が遅くなり、すいません。

    登録順は、なっております。

    UIViewには、新たに新規クラスを作成し、その中にプログラムを埋めこんでおります。
    初期化関数内とは、viewDidLoadのことでしょうか。

    すいませんが、プログラムを埋め込む場所を教えていただけないでしょうか。
    宜しくお願いします。

    2011年12月08日 13:21
  • hiro

    忙しいところすいません。
    プログラムを添付しますので、お手数ですが、間違っている箇所を教えて頂けないでしょうか。

    ViewController.hに
    #import を追加

    クラスを作成、クラスをViewController.hのクラスに登録。

    ・・・.hの@interfaceに下記を入力。
    void* data;
    CGContextRef context;

    ・・・.mの初期設定に

    self = [super initWithFrame:frame];
    if (self) {
    // Initialization code

    data = malloc(300 * 300 * 4);

    CGColorSpaceRef colorRef = CGColorSpaceCreateDeviceRGB();
    context = CGBitmapContextCreate(data, 300, 300
    , 200, 30, colorRef, kCGImageAlphaPremultipliedFirst);
    CGColorSpaceRelease(colorRef);

    CGContextSetRGBFillColor(context, 0.5, 0.5, 0.5, 1);
    CGContextFillRect(context, self.bounds);
    }
    return self;

    を追加します。


    - (void)drawRect:(CGRect)rectに

    CGImageRef imageRef = CGBitmapContextCreateImage(context);
    CGContextRef currentContext = UIGraphicsGetCurrentContext();
    CGContextDrawImage(currentContext, rect, imageRef);
    CGImageRelease(imageRef);

    を追加。

    - (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event に

    CGPoint p = [[touches anyObject] locationInView: self];
    CGPoint q = [[touches anyObject] previousLocationInView: self];

    CGContextSetLineCap(context, kCGLineCapRound);
    CGContextBeginPath(context);

    CGContextMoveToPoint(context, q.x, q.y);
    CGContextAddLineToPoint(context, p.x, p.y);
    CGContextStrokePath(context);

    CGContextSetBlendMode(context, kCGBlendModeClear);
    CGContextSetLineWidth(context, 15);
    CGContextSetRGBStrokeColor(context, 1, 1, 1, 0);


    [self setNeedsDisplay];

    を追加実行するとエラーになりました。

    すいませんが、サンプルファイルがありましたら、
    頂けないでしょうか。

    お手数おかけいたしますが、宜しくお願いいたします。



    2011年12月08日 20:22
  • ヒイロ

    >hiroさん
    この時のサンプルプロジェクトが紛失してしまったので、もう一度作りなおさないと検証ができない状態になってしまいました。

    週末などにXCodeを使うことができれば、検証してみますね。

    みたところ、コードに問題はなさそうですが
    エラーというのは実行時にエラーで落ちるのでしょうか?
    2011年12月10日 02:50
  • hiro

    Iphoneのエミュレータを立ち上げると、
    : CGBitmapContextCreateImage: invalid context 0x0

    と表示されます。

    画面をスライドさせると
    : CGContextSetLineCap: invalid context 0x0
    : CGContextBeginPath: invalid context 0x0
    : CGContextMoveToPoint: invalid context 0x0

    とtouchesMovedで宣言したのが
    エラーになってしまいました。

    CGBitmapContextCreateImageが間違えているんでしょうか。
    他に設定する部分があるのでしょうか。
    すいませんが、宜しくお願いいたします。
    2011年12月10日 09:26
  • ヒイロ

    >hiroさん
    http://hiiro-game.seesaa.net/article/200815366.html
    こちらの記事にサンプルビューのソースをアップしています。
    とりあえずシミュレーターで動作確認をしています。
    allocしてinitWithFrameしてaddSubviewしてみてください。

    画面サイズ分でグレー色表示し、指でなぞるとなぞった部分が透明になって下のビューが表示されます。
    2011年12月16日 20:59

この記事へのトラックバック