Edited at

[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側で処理をするのは直感的には筋が良くないかなと思いました