- 2020年初,新冠病毒悄然来袭,上万的民众深受其害,不幸者丧失生命达三千余人,还好政府及时组织医疗团队、制定防护相关措施于四月初得以控制,并在2020年4月4日举国哀悼,哀悼逝去英雄!互联网各大网站、app界面全部变成黑白色以示哀悼。
网页端、安卓端的同学很快完成相关实现,只剩下iOS端干瞪眼,拜读了鸿洋大神的博客,了解安卓和网页的实现过程。尝试在iOS端尝试却未能成功。但是iPhone手机-->设置-->辅助功能-->显示与文字大小--> 色彩滤镜--> 灰度功能打开,整个手机都黑化了,但这总不能告诉用户打开吧,尴尬。
*最后绞尽脑汁,经高人指点:既然无法像安卓、网页大神那样“一行代码实现黑白化”,那就分步实施吧。于是我们分了三步走:
- 图片二值化处理
- color
- H5页面
*知识点:
- 颜色空间,苹果默认使用RGB
- RGB灰度处理算法
1. 图片的二值化处理
*图片的二值化处理,百度一下很多,这里我们使用的是bitmap,将每一个色值取出根据灰度算法计算后改变原来的色值。优点是生成的图片不会出现锯齿,模糊现象。核心代码:
- (UIImage *)grayImage {
const int RED =1;
const int GREEN =2;
const int BLUE =3;
// Create image rectangle with current image width/height
CGRect imageRect = CGRectMake(0,0, self.size.width* self.scale, self.size.height* self.scale);
int width = imageRect.size.width;
int height = imageRect.size.height;
// the pixels will be painted to this array
uint32_t *pixels = (uint32_t*) malloc(width * height *sizeof(uint32_t));
// clear the pixels so any transparency is preserved
memset(pixels,0, width * height *sizeof(uint32_t));
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
// create a context with RGBA pixels
CGContextRef context = CGBitmapContextCreate(pixels, width, height,8, width *sizeof(uint32_t), colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedLast);
// paint the bitmap to our context which will fill in the pixels array
CGContextDrawImage(context,CGRectMake(0,0, width, height), [self CGImage]);
for(int y = 0; y < height; y++) {
for(int x = 0; x < width; x++) {
uint8_t *rgbaPixel = (uint8_t*) &pixels[y * width + x];
// convert to grayscale using recommended method: http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale
uint32_t gray = 0.3 * rgbaPixel[RED] +0.59 * rgbaPixel[GREEN] +0.11 * rgbaPixel[BLUE];
// set the pixels to gray
rgbaPixel[RED] = gray;
rgbaPixel[GREEN] = gray;
rgbaPixel[BLUE] = gray;
}
}
// create a new CGImageRef from our context with the modified pixels
CGImageRef imageRef = CGBitmapContextCreateImage(context);
// we're done with the context, color space, and pixels
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
free(pixels);
// make a new UIImage to return
UIImage *resultUIImage = [UIImage imageWithCGImage:imageRef scale:self.scale orientation:UIImageOrientationUp];
// we're done with image now too
CGImageRelease(imageRef);
return resultUIImage;
}
2. 颜色color
- 我们想所有的控件不管是背景色、边框颜色、文字颜色等,最终都会调用UIColor中的一个API:
+ (UIColor *)colorWithRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha;
*因此我们只需运用runtime进行方法交换,之后在交换的方法中将RGB进行灰度算法处理,alpha跟随设置不做处理。得到的color也就变成了对应的灰度,将color赋值后确实有很大惊喜,全量设置颜色的控件都实现了灰度化。核心代码如下:
+ (UIColor *)changeGrayWithColor:(UIColor *)color Red:(CGFloat)r green:(CGFloat)g blue:(CGFloat)b alpha:(CGFloat)a {
CGFloat gray = r * 0.299 +g * 0.587 + b * 0.114;
UIColor *grayColor = [UIColor colorWithWhite:gray alpha:a];
return grayColor;
}
3. H5页面
*H5页面相对比较简单,至少从前端同事得到帮助。帮助书写了js代码,验证有效后,便是iOS端寻找注入时机。由于项目中WK和UIWeb并存,所有都要做处理。首先是UIWebView的注入,我们尝试在delegate的didFinishNavigation方法中注入,测试发现为时晚矣,界面加载出来后,显示的是正常颜色,然后再变成灰色,显然这是注入晚了。于是查看UIwebview的所有API,寻找注入时机。最后我们发现:
-(void)webView:(id)arg1 resource:(id)arg2 didFinishLoadingFromDataSource:(id)arg3 ;
- 因此又一次使用了runtime方法交换,拦截到上面API时,在其中注入js代码,完美的解决了闪一下变灰的问题,具体代码如下:
-(void)swizzled_webView:(id)arg1 resource:(id)arg2 didFinishLoadingFromDataSource:(id)arg3 {
[self grayWebaction:arg1];
[self swizzled_webView:arg1 resource:arg2 didFinishLoadingFromDataSource:arg3];
}
-(void)grayWebaction:(UIWebView *)web {
NSString *params = @"var filter = '-webkit-filter:grayscale(100%);-moz-filter:grayscale(100%); -ms-filter:grayscale(100%); -o-filter:grayscale(100%) filter:grayscale(100%);';document.getElementsByTagName('html')[0].style.filter = 'grayscale(100%)';";
[web stringByEvaluatingJavaScriptFromString:params];
}
*至于WKWebview,不同于UIWebview,可以在初始化时直接注入js代码,便能实现其效果,当然也是通过方法交换,趁此档口注入js,具体代码如下:
- (instancetype)lg_initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration {
// js脚本
NSString *jScript = @"var filter = '-webkit-filter:grayscale(100%);-moz-filter:grayscale(100%); -ms-filter:grayscale(100%); -o-filter:grayscale(100%) filter:grayscale(100%);';document.getElementsByTagName('html')[0].style.filter = 'grayscale(100%)';";
// 注入
WKUserScript *wkUScript = [[WKUserScript alloc] initWithSource:jScript injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];
WKUserContentController *wkUController = [[WKUserContentController alloc] init];
[wkUController addUserScript:wkUScript];
// 配置对象
WKWebViewConfiguration *wkWebConfig = [[WKWebViewConfiguration alloc] init];
wkWebConfig.userContentController = wkUController;
configuration = wkWebConfig;
WKWebView *webView = [self lg_initWithFrame:frame configuration:configuration];
return webView;
}