Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

[iOS] 画像の平均色を抽出する

More than 1 year has passed since last update.

画像(UIImage)の全ピクセルの平均の色(UIColor)を抽出するメソッドが、Chameleonという有名OSSにあります。

swift
public func AverageColorFromImage(_ image: UIImage) -> UIColor
objc
+ (UIColor *)colorWithAverageColorFromImage:(UIImage *)image

この平均色を抽出するメソッドの実装が「なるほどそうやるのか」と参考になったのでここに書いておきます。

https://github.com/viccalexander/Chameleon/blob/master/Pod/Classes/Objective-C/UIColor%2BChameleon.m

+ (UIColor *)colorWithAverageColorFromImage:(UIImage *)image withAlpha:(CGFloat)alpha {

    //Work within the RGB colorspoace
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    unsigned char rgba[4];
    CGContextRef context = CGBitmapContextCreate(rgba, 1, 1, 8, 4, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);

    //Draw our image down to 1x1 pixels
    CGContextDrawImage(context, CGRectMake(0, 0, 1, 1), image.CGImage);
    CGColorSpaceRelease(colorSpace);
    CGContextRelease(context);

    //Check if image alpha is 0
    if (rgba[3] == 0) {        
        CGFloat imageAlpha = ((CGFloat)rgba[3])/255.0;
        CGFloat multiplier = imageAlpha/255.0;

        UIColor *averageColor = [UIColor colorWithRed:((CGFloat)rgba[0])*multiplier green:((CGFloat)rgba[1])*multiplier blue:((CGFloat)rgba[2])*multiplier alpha:imageAlpha];

        //Improve color
        averageColor = [averageColor colorWithMinimumSaturation:0.15];

        //Return average color
        return averageColor;
    }    
    else {        
        //Get average
        UIColor *averageColor = [UIColor colorWithRed:((CGFloat)rgba[0])/255.0 green:((CGFloat)rgba[1])/255.0 blue:((CGFloat)rgba[2])/255.0 alpha:alpha];

        //Improve color
        averageColor = [averageColor colorWithMinimumSaturation:0.15];

        //Return average color
        return averageColor;
    }
}

実装のポイント

ポイントは、画像の全ピクセルを走査して各ピクセル値を合算して平均色を出す、みたいなことをやるのではなく、CGContextDrawImageでいったん1x1ピクセルの画像に書き出すという処理。

CGContextDrawImage(context, CGRectMake(0, 0, 1, 1), image.CGImage);

CGContextDrawImageでダウンサンプルする際に平均化されることを利用しています。Core Graphicsが中でどれだけ効率よくやってくれてるのかはわかりませんが、画像の全ピクセルをメモリに展開して走査するよりは良さそうです。

もうひとつのポイントは、"Improve color"というコメントつきで書かれている次の1行。

averageColor = [averageColor colorWithMinimumSaturation:0.15];

colorWithMinimumSaturation:は、UIColor+ChameleonPrivate.mに実装されている、HSB色空間で彩度の最低値を補正するメソッドです。

- (UIColor *)colorWithMinimumSaturation:(CGFloat)saturation {
    if (!self)
        return nil;

    CGFloat h, s, b, a;
    [self getHue:&h saturation:&s brightness:&b alpha:&a];

    if (s < saturation)
        return [UIColor colorWithHue:h saturation:saturation brightness:b alpha:a];

    return self;
}

ここで指定している0.15が、色処理の世界でこういう処理をやる際によく使われる値なのか、Chameleon独自の調整値なのかはわかりません。ガンマを考慮してないのでえいやで彩度を上げてるのかもしれないし、平均取った結果の彩度が高いほうがライブラリとしての顧客(ユーザー)満足度が上がるという判断なのかもしれません。

その他

  • Swiftで実装しなおす機会があれば追記します
  • GPUImage2にもAverageColorExtractorというクラスがあるようです
    • が、UIColor(CPU側で取り扱う)ものを抽出するためにGPU側で処理をするのは直感的には筋が良くないかなと思いました
shu223
フリーランスiOSエンジニア 著書:『iOS×BLE Core Bluetooth プログラミング』『Metal入門』『実践ARKit』『Depth in Depth』『iOSアプリ開発 達人のレシピ100』他 GitHubの累計スター数24,000超
http://shu223.hatenablog.com/
engineerlife
技術力をベースに人生を謳歌する人たちのコミュニティです。
https://community.camp-fire.jp/projects/view/280040
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away