職人が教える!iOSアプリ開発で使いこなしたいとっておきのOSS

第5回グラデーション描画や色のアニメーションの実装を簡単にする色処理関連の便利OSS3選

何とも地味なテーマですが、色処理まわりは技術的にだいぶ枯れており、実装でやりたいこともある程度パターン化されています。ゆえに便利なOSSが多く出揃っている部分でもあります。

今回はそんな色処理まわりの便利なOSSを3種類紹介します。

グラデーション描画のヘルパークラスLBGradient

CocoaにはNSGradientというグラデーション描画用のクラスがあるのですが、iOSにはそれがないため、

画像

このようなグラデーションを描こうと思ったらCore Graphicsを用いて

- (void)drawRect:(CGRect)rect
{
    CGContextRef context = UIGraphicsGetCurrentContext();   
    CGContextSaveGState(context);
    
    CGContextAddRect(context, self.frame);
    
    CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
    
    CGFloat components[] = {
        0.0f, 0.0f, 1.0f, 1.0f, // Blue
        1.0f, 0.0f, 0.0f, 1.0f, // Red
        0.0f, 1.0f, 0.0f, 1.0f  // Green
    };
    CGFloat locations[] = {0.0f, 0.5f, 1.0f};
    
    size_t count = sizeof(components) / (sizeof(CGFloat) * 4);
    
    CGRect frame = self.bounds;
    CGPoint startPoint = frame.origin;
    CGPoint endPoint = CGPointMake(frame.origin.x + frame.size.width, frame.origin.y);
    
    CGGradientRef gradientRef = CGGradientCreateWithColorComponents(colorSpaceRef, components, locations, count);
    
    CGContextDrawLinearGradient(context,
                                gradientRef,
                                startPoint,
                                endPoint,
                                kCGGradientDrawsAfterEndLocation);
    
    CGGradientRelease(gradientRef);
    CGColorSpaceRelease(colorSpaceRef);
    
    CGContextRestoreGState(context);
}

こんなに長いコードを書く必要があります。

コードをよく見れば、UIGraphicsGetCurrentContext()をはじめCore Graphicsではおなじみの手続きが多く占めているものの、このコードをサラサラと書けるかというとなかなか辛いものがあります。

そこでLBGradientというクラスを使うと、この部分がこんなにシンプルになります(実行結果は上と同じ⁠⁠。

-(void)drawRect:(CGRect)rect {
    LBGradient* gradient = [[LBGradient alloc] initWithColorsAndLocations:
                            [UIColor blueColor], 0.0f,
                            [UIColor redColor], 0.5f,
                            [UIColor greenColor], 1.0f, nil];
    [gradient drawInRect:self.bounds angle:0];
    [gradient release];
}

LBGradientクラスのinitWithColorsAndLocations:というメソッドが可変長引数をとるようになっていて、ここにグラデーションの基準となる色と位置をセットで複数個指定します。グラデーションを指定するためのAPIとしては直観的でわかりやすいのではないでしょうか。

上記ではグラデーションの描画にdrawInRect:angle:を用いて「指定した矩形内を指定した角度でグラデーション描画」していますが、他にも次のようなメソッドも用意されており、

-(void)drawInBezierPath:(UIBezierPath *)path angle:(CGFloat)angle;

任意のベジェ曲線に沿ってグラデーションを描画することも可能です。

LBGradientのソースを見ると、initWithColorsAndLocations:メソッドではグラデーション基準点の色と位置の配列を生成し、描画メソッド内でCGGradientCreateWithColorsメソッドとCGContextDrawLinearGradientメソッドを用いてグラデーション描画していて、要するに最初に載せた長いコードをうまくラップしてくれている、ということがわかります。

LBGradientのリポジトリのURLは以下になります。

サクッとグラデーションをかけて色をつけたい、といった場合にいかがでしょうか。

Core Graphics のややこしい手続きをラップするクラスを自作する際の参考としてもよいかと思います。

2つのUIColorの間でクロスフェードさせるUIColorのカテゴリ

UIColor-CrossFaceを使うと、指定した比率で2つのUIColorの間でクロスフェードさせることができます。

……と文章で書いても何のことかわかりにくいと思うので、まずは下記URLからソース一式をダウンロードし、⁠UIColorCrossFadeDemo⁠というプロジェクトをビルド&実行してみてください。

画像

このような画面が現れ、画面内のスライダーを右に動かすと、以下のようにスライダーの値に従って背景が赤から青へクロスフェードします。

画像

これは、Flashで言えばシンボルの「カラー効果」プロパティに値を指定して色を変えることに相当していて、こういったことを実現するためのUIColorのカテゴリが、UIColor-CrossFaceです。

APIもとてもわかりやすく

+ (UIColor *)colorForFadeBetweenFirstColor:(UIColor *)firstColor 
                               secondColor:(UIColor *)secondColor 
                                   atRatio:(CGFloat)ratio;

最初の色、最後の色をUIColor型で指定し、あとは比率を指定すれば、⁠最初の色から最後の色までクロスフェードする際の指定比率における色」が返されます。

サンプルコードではこのratio引数に渡す値としてスライダーの値を用いていますが、たとえば本連載の第1回でご紹介したトゥイーンライブラリを用いることで、イージング付きでクロスフェードするアニメーションを実現することもできます。

ちなみに、APIには、

+ (NSArray *)colorsForFadeBetweenFirstColor:(UIColor *)firstColor 
                                  lastColor:(UIColor *)lastColor 
                          withRatioEquation:(float (^)(float))equation
                                    inSteps:(NSUInteger)steps;

といったように、比率を直接渡すのではなく、比率を求める計算式を渡すためのメソッドも用意されているので、カスタムイージングカーブを実現するための式を自作して渡すことも可能です。

カテゴリ内部で色の計算を行っている部分の実装は、下記のようになっています。

const CGFloat *firstColorComponents = CGColorGetComponents(firstColor.CGColor);
const CGFloat *secondColorComponents = CGColorGetComponents(secondColor.CGColor);

CGFloat interpolatedComponents[CGColorGetNumberOfComponents(firstColor.CGColor)] ;
for (NSUInteger i = 0; i < CGColorGetNumberOfComponents(firstColor.CGColor); i++)
{
    interpolatedComponents[i] = firstColorComponents[i] * (1 - ratio) + secondColorComponents[i] * ratio;
}

最初の色、最後の色それぞれのcolor components(RGBAの各値)を取り出し、各チャネルごとに指定比率でのブレンドを行う、というシンプルなものです。

ところで、実はこのカテゴリを使用しなくても、Core Animationを用いれば、CALayerの backgroundColorやshadowColorなどは、下記のようなコードで色のクロスフェードを実現できてしまいます。

CABasicAnimation *bgColorAnimation = [CABasicAnimation  animationWithKeyPath:@"backgroundColor"];
bgColorAnimation.toValue = (id)[UIColor redColor].CGColor;
[self.view.layer addAnimation:bgColorAnimation forKey:@"bgColorAnimation"];

ただ、この方法はあくまでCore Animationでアニメーション可能なプロパティ※をアニメーションさせたい場合にしか用いることができないので、UIColor型であればどんなプロパティに対しても適用可能なUIColor-CrossFadeのほうが汎用性は高いと言えます(たとえば、サンプルにあったようにスライダーの値に合わせて色を変化させたい場合や、UILabelのtextColorプロパティに適用したい場合など⁠⁠。

Core Animationでアニメーション可能なプロパティ一覧は、AppleのドキュメントであるCore Animation Programming Guide⁠Animatable Properties⁠の項で一覧を確認することができます。

今回紹介したUIColor-CrossFade自体は、たった百数行のシンプルなものですが、この発想を応用し、色んな自前パラメータをアニメーションさせるカテゴリを自作してみても楽しいかもしれません。

色の重ね合わせやCSSカラー名での色の指定ができるようになるUIColorのカテゴリ

最後に、UIColorの便利な拡張機能を色々と詰め合わせたカテゴリ⁠uicolor-utilities⁠を紹介します。

たとえば、UIColorのカテゴリやマクロでは定番の、16進カラーコード(Hexカラー)との相互変換機能も、UInt32型、NSString型両方に対応して備えています。

@property (nonatomic, readonly) UInt32 rgbHex;
- (NSString *)hexStringFromColor;
+ (UIColor *)colorWithRGBHex:(UInt32)hex;
+ (UIColor *)colorWithHexString:(NSString *)stringToConvert;

色の重ね合わせ機能も備えており、PhotoshopやFireworksのブレンドモードではおなじみの、乗算、加算、比較(暗⁠⁠、比較(明)といった計算方法別にメソッドが用意されています。

- (UIColor *)colorByMultiplyingByRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha;
- (UIColor *)       colorByAddingRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha;
- (UIColor *) colorByLighteningToRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha;
- (UIColor *)  colorByDarkeningToRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha;

ちょっとした機能ですが、次のようなメソッドも持っています。

- (NSString *)stringFromColor;

この内部実装は下記のようになっており、チャネルごとの値を(チャネル数を判別して)NSString型で出力してくれるので、ログ出力に便利です。

switch (self.colorSpaceModel) {
    case kCGColorSpaceModelRGB:
        result = [NSString stringWithFormat:@"{%0.3f, %0.3f, %0.3f, %0.3f}", self.red, self.green, self.blue, self.alpha];			
        break;
    case kCGColorSpaceModelMonochrome:
        result = [NSString stringWithFormat:@"{%0.3f, %0.3f}", self.white, self.alpha];
        break;
    default:
        result = nil;
}

下記メソッドでは、輝度成分を抽出しグレースケール化したUIColorを返します。

- (UIColor *)colorByLuminanceMapping

「輝度」の求め方は諸々あるのですが、ソースで確認したところ、RGB にかけている係数の値から、YPbPr の計算式に基づいているようです。

“aliceblue⁠⁠antiquewhite⁠などのCSSカラー名からUIColorを取得できるメソッドも備えています。

+ (UIColor *)colorWithName:(NSString *)cssColorName;

UIColor の標準メソッドではblackColorやredColorなどごくごく基本的な色を出力する機能しかなく、もうちょっと違う色味を出したいとなると直接RGB値から生成するしかありませんでした。CSSカラーはWeb上にたくさんの色見本があるので、Webで気に入った色を探しつつそのまま色名を指定する、といったことができるようになります。そして、RGB値を指定する場合よりソースコードから色味を想像しやすいというメリットもあります。

uicolor-utilitiesは下記URLよりダウンロードできます。

まとめ

今回は「色処理関連の便利OSS」という括りで、グラデーション描画のヘルパークラスLBGradient、2つのUIColorの間でクロスフェードさせるUIColorのカテゴリUIColor-CrossFace、色の重ね合わせやCSSカラー名での色の指定ができるようになるUIColorのカテゴリuicolor-utilitiesの3つのOSSを紹介しました。

今回ご紹介したOSSではそれほど複雑な機能を代行してくれているわけではないのですが、そのぶん内部のソースコードも把握しやすく、使い回しや応用がききやすいのではと思います。モック開発時などにサクッといい感じに色をつけたい場合に、あるいはヘルパークラスを自作する際の参考に、何かしらの形で皆様の開発のお役に立てば幸いです。

おすすめ記事

記事・ニュース一覧