PR TIMESデザイナー&エンジニアブログ BREAK TIMES

PR TIMES Developer Blog(デザイナー&エンジニアによる開発者ブログ)

PR TIMES Developer Blog

当ブログは下記URLに移転しました。
https://developers.prtimes.jp/

カラーを指定するとアイコン画像が指定の色に変わる仕組みをつくってみる☆*:.。.[合成処理 / マスク処理]

PR TIMES エンジニアのうさみです。
iOSアプリを開発する機会がありまして、テーマカラーの値を指定するとそのテーマカラーの画面に切り替わる仕組みを取り入れることにしました。

テキストの色や背景色は下記のようにプロパティを変更すればいいだけなので、簡単に実現できますね。

UILabel *label = [UILabel alloc] initWithFrame:CGRectMake(0, 0, 100.0f, 20.0f)];
label.textColor = [UIColor redColor];

UIView *view = [UIView alloc] initWithFrame:CGRectMake(0, 0, 100.0f, 100.0f)];
view.backgroundColor = [UIColor greenColor];

この仕組みを実装している中で、例えば下記のようなアイコンも指定したカラーにできたら、色ごとにアイコンを用意する必要も無くなるし、デザインの変更がある度にIllustratorPhotoshopを開いて画像を書き出す手間も無くなっていいなと思うように・・・

f:id:breaktimes:20160112122947p:plain
<画像1> icon.png

どのように実現できるか考えてみたところ、画像処理に強いわけでもない私が思い浮かぶ方法として下記の2つが思い浮かびました。

1. 画像データを解析して、任意のカラーの領域を塗りつぶす
2. 画像の合成処理を駆使する

今回はなるべく簡単な方法で実現してみたいと思いましたので、2の方法で実装してみました。

まず、画像は<画像1>のように下記の色で構成されることが前提となります。
・背景領域:透明部分
・色が変更されない領域:白地部分
・色が変更される領域:黒地部分

例えば、赤色を指定した場合は下記のような画像となることを期待します。

f:id:breaktimes:20160112122917p:plain
<画像2>

それでは実装してまいります!

まず、描画する領域を取得しておきます。

    // 描画領域を取得
    UIImage *image = [UIImage imageNamed:@"icon"];
    CGSize  size = image.size;
    CGRect  imageRect = CGRectMake(0, 0, size.width, size.height);

次に、合成する際に必要な画像の表示領域全面を塗りつぶした画像を生成して準備しておきます。

    // カラーを指定
    UIColor *color = [UIColor redColor];

    // 指定カラーで全面塗りつぶした画像を生成
    UIGraphicsBeginImageContext(size);
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetFillColorWithColor(context, [color CGColor]);
    CGContextFillRect(context, imageRect);
    UIImage *colorRectImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

f:id:breaktimes:20160112123301p:plain
<画像3>

    // 黒色で全面塗りつぶした画像を生成
    UIGraphicsBeginImageContext(size);
    context = UIGraphicsGetCurrentContext();
    CGContextSetFillColorWithColor(context, [[UIColor blackColor] CGColor]);
    CGContextFillRect(context, imageRect);
    UIImage *blackRectImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

f:id:breaktimes:20160112123026p:plain
<画像4>

    // 白色で全面塗りつぶした画像を生成
    UIGraphicsBeginImageContext(size);
    context = UIGraphicsGetCurrentContext();
    CGContextSetFillColorWithColor(context, [[UIColor whiteColor] CGColor]);
    CGContextFillRect(context, imageRect);
    UIImage *whiteRectImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

f:id:breaktimes:20160112123255p:plain
<画像5>

準備ができたら、これらの生成した画像とアイコンの画像を上手く合成して、期待している<画像2>の画像を生成します!

まず、直感で思いついたのは、白地はそのままで黒地部分の色を塗りつぶすということは、ブレンドモード「比較(明)」にした<画像3>をアイコン画像の上に被せたら実現できちゃうのでは?!・・・とやってみたところ。。

    // 指定カラー塗りつぶし画像をブレンドモード「比較(明)」でアイコン画像に被せる
    UIGraphicsBeginImageContext(size);
    [image drawInRect:imageRect];
    [colorRectImage drawInRect:imageRect blendMode:kCGBlendModeLighten alpha:1.0f];
    UIImage *coloredImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

f:id:breaktimes:20160112123342p:plain
<画像6>

透明部分も塗りつぶされてしまいました・・・
透明部分も塗りつぶされることなく、そのままでいてほしいのです。。
他のブレンドモードも確認して、上記コードのblendModeを変更することで実現できるかなと期待するも、さすがにこんなに簡単なコードだけでは実現は無理のようです。。

そこで、下記の手順で処理することでできるのではと考えました。

1. アイコン画像自体の領域をマスクするためのマスク画像を生成

    UIGraphicsBeginImageContext(size);
    [image drawInRect:imageRect];
    [blackRectImage drawInRect:imageRect blendMode:kCGBlendModeSourceIn alpha:1.0f];
    [whiteRectImage drawInRect:imageRect blendMode:kCGBlendModeDarken alpha:1.0f];
    UIImage *maskImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

f:id:breaktimes:20160112123320p:plain
<画像7>


2. 指定カラー塗りつぶし画像をブレンドモード「比較(明)」でアイコン画像と合成

    UIGraphicsBeginImageContext(size);
    [image drawInRect:imageRect];
    [colorRectImage drawInRect:imageRect blendMode:kCGBlendModeLighten alpha:1.0f];
    UIImage * coloredBaseImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

f:id:breaktimes:20160112123342p:plain
<画像8>


3. 2で生成した画像を1のマスク画像でマスク処理

    maskImageRef = maskImage.CGImage;
    maskRef = CGImageMaskCreate(CGImageGetWidth(maskImageRef),
                                CGImageGetHeight(maskImageRef),
                                CGImageGetBitsPerComponent(maskImageRef),
                                CGImageGetBitsPerPixel(maskImageRef),
                                CGImageGetBytesPerRow(maskImageRef),
                                CGImageGetDataProvider(maskImageRef), NULL, NO);
    maskedImageRef = CGImageCreateWithMask(coloredBaseImage.CGImage, maskRef);
    UIImage *coloredImage = [UIImage imageWithCGImage:maskedImageRef];

    CGImageRelease(maskImageRef);
    CGImageRelease(maskRef);
    CGImageRelease(maskedImageRef);

f:id:breaktimes:20160112122917p:plain
<画像9>


・・・!!
期待したとおりの画像が生成できました!

以上の処理で固定で指定したimageとcolorを下記のように外部から指定できる関数にしておけば、簡単に色の変更ができるようになりますね。

// 関数
- (UIImage*)coloredImage:(UIImage*)image color:(UIColor*)color
{
    // Check image
    if (!image) {
        return image;
    }
    
    // Check color
    if (!color) {
        return image;
    }

      :

   〜 上記の処理 〜

      :


    return coloredImage;

}

// 呼び出し
UIImage *image = [UIImage imageNamed:@"icon"];
UIImage *coloredImage = [self coloredImage:image color:[UIColor redColor]];


とりあえず、これが簡単な方法かなと思いましたが、他にもっといい方法があれば、是非教えていただきたいです (* ´ - ` *)

今回は急ぎということもあり、Objective-Cでの実装でした(汗)
まだまだ、Objective-CからSwiftへと移行できていないので、Swiftをもっと触らないとなぁと思うのでありました ☆*:.。.