需求背景
-
有时候我们会有这样的需求,用户从相册选择一张照片,返回展示的时候,除了展示照片还要让整体背景也是和照片相近颜色,最近自己写了一个图片加水印的项目,分别用swift和OC实现相关功能。
代码部分
-
主要逻辑:
将图片按比例缩小,因为后续遍历图片每个像素点,循环次数是图片width x height,如果直接原图去遍历,可能一次循环就要跑几十万、百万次,需要时间非常久,所以要将图片缩小。
获取图片的所有像素的RGB值,每组RGB使用数组存储(可以根据自己的需求过滤部分颜色),然后用Set将数组装起来。
统计Set里面相同次数最多的色值,即是整个图片的主题色
swift代码实现
public extension UIImage {
func subjectColor(_ completion: @escaping (_ color: UIColor?) -> Void){
DispatchQueue.global().async {
if self.cgImage == nil {
DispatchQueue.main.async {
return completion(nil)
}
}
let bitmapInfo = CGBitmapInfo(rawValue: 0).rawValue | CGImageAlphaInfo.premultipliedLast.rawValue
// 第一步 先把图片缩小 加快计算速度. 但越小结果误差可能越大
let thumbSize = CGSize(width: 40 , height: 40)
let colorSpace = CGColorSpaceCreateDeviceRGB()
guard let context = CGContext(data: nil,
width: Int(thumbSize.width),
height: Int(thumbSize.height),
bitsPerComponent: 8,
bytesPerRow: Int(thumbSize.width) * 4 ,
space: colorSpace,
bitmapInfo: bitmapInfo) else { return completion(nil) }
let drawRect = CGRect(x: 0, y: 0, width: thumbSize.width, height: thumbSize.height)
context.draw(self.cgImage! , in: drawRect)
// 第二步 取每个点的像素值
if context.data == nil{ return completion(nil)}
let countedSet = NSCountedSet(capacity: Int(thumbSize.width * thumbSize.height))
for x in 0 ..< Int(thumbSize.width) {
for y in 0 ..< Int(thumbSize.height){
let offset = 4 * x * y
let red = context.data!.load(fromByteOffset: offset, as: UInt8.self)
let green = context.data!.load(fromByteOffset: offset + 1, as: UInt8.self)
let blue = context.data!.load(fromByteOffset: offset + 2, as: UInt8.self)
let alpha = context.data!.load(fromByteOffset: offset + 3, as: UInt8.self)
// 过滤透明的、基本白色、基本黑色
if alpha > 0 && (red < 250 && green < 250 && blue < 250) && (red > 5 && green > 5 && blue > 5) {
let array = [red,green,blue,alpha]
countedSet.add(array)
}
}
}
//第三步 找到出现次数最多的那个颜色
let enumerator = countedSet.objectEnumerator()
var maxColor: [Int] = []
var maxCount = 0
while let curColor = enumerator.nextObject() as? [Int] , !curColor.isEmpty {
let tmpCount = countedSet.count(for: curColor)
if tmpCount < maxCount { continue }
maxCount = tmpCount
maxColor = curColor
}
let color = UIColor(red: CGFloat(maxColor[0]) / 255.0, green: CGFloat(maxColor[1]) / 255.0, blue: CGFloat(maxColor[2]) / 255.0, alpha: CGFloat(maxColor[3]) / 255.0)
DispatchQueue.main.async { return completion(color) }
}
}
}
OC代码实现
//根据图片获取图片的主色调
-(void)getSubjectColor:(void(^)(UIColor*))callBack {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 第一步 先把图片缩小 加快计算速度. 但越小结果误差可能越大
int bitmapInfo = kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedLast;
CGSize thumbSize = CGSizeMake(40, 40);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(NULL,thumbSize.width,thumbSize.height, 8, thumbSize.width*4, colorSpace,bitmapInfo);
CGRect drawRect = CGRectMake(0, 0, thumbSize.width, thumbSize.height);
CGContextDrawImage(context, drawRect, self.CGImage);
CGColorSpaceRelease(colorSpace);
// 第二步 取每个点的像素值
unsigned char* data = CGBitmapContextGetData (context);
if (data == NULL) {
dispatch_async(dispatch_get_main_queue(), ^{
callBack(nil);
});
};
NSCountedSet* cls = [NSCountedSet setWithCapacity: thumbSize.width * thumbSize.height];
for (int x = 0; x < thumbSize.width; x++) {
for (int y = 0; y < thumbSize.height; y++) {
int offset = 4 * (x * y);
int red = data[offset];
int green = data[offset + 1];
int blue = data[offset + 2];
int alpha = data[offset + 3];
// 过滤透明的、基本白色、基本黑色
if (alpha > 0 && (red < 250 && green < 250 && blue < 250) && (red > 5 && green > 5 && blue > 5)) {
NSArray *clr = @[@(red),@(green),@(blue),@(alpha)];
[cls addObject:clr];
}
}
}
CGContextRelease(context);
//第三步 找到出现次数最多的那个颜色
NSEnumerator *enumerator = [cls objectEnumerator];
NSArray *curColor = nil;
NSArray *MaxColor = nil;
NSUInteger MaxCount = 0;
while ((curColor = [enumerator nextObject]) != nil){
NSUInteger tmpCount = [cls countForObject:curColor];
if ( tmpCount < MaxCount ) continue;
MaxCount = tmpCount;
MaxColor = curColor;
}
UIColor * subjectColor = [UIColor colorWithRed:([MaxColor[0] intValue]/255.0f) green:([MaxColor[1] intValue]/255.0f) blue:([MaxColor[2] intValue]/255.0f) alpha:([MaxColor[3] intValue]/255.0f)];
dispatch_async(dispatch_get_main_queue(), ^{
callBack(subjectColor);
});
});
}
因为里面是两个for循环,时间复杂度是On^2,如果设置的width和Height比较大的话,会比较耗时,在主线程里面执行可能会卡住,所以使用了gcd开启子线程去执行,完成后回到主线程执行回调。
外部使用:
@objc func buttonClick(){
/// 4. 点击选图片时,展示这个 picker controller
present(imagePicker, animated: true) {
print("UIImagePickerController: presented")
}
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
guard let selectedImage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage else { return }
picker.dismiss(animated: true) { [unowned self] in
self.imageView.image = selectedImage
// Swift
selectedImage.subjectColor({[unowned self] color in
guard let subjectColor = color else { return }
self.view.backgroundColor = subjectColor
})
// OC
// selectedImage.getSubjectColor {[unowned self] color in
// guard let subjectColor = color else { return }
// self.view.backgroundColor = subjectColor
// }
}
}