前言
有些时候,我们需要批量替换产品运营图片,比如添加logo,水印,或者改变图片尺寸,与其他背景图片合并等。
效果
实现
在图片上下文里面,我们需要合并背景图外,还需要添加从左到右的渐变,添加随机圆形,画一条曲线。
为了不太突兀,在原图里面获取像素点的色值来作为渐变色、圆形填充色以及贝塞尔曲线的填充色。
获取图片色值
先解压图片获取原始像素点数值,需要注意的是,它的坐标系和view的坐标系统不一样,左下为坐标零点。
- (NSArray<UIColor*> *)randomPixelColorWithImage:(UIImage *)image
colorCount:(NSInteger)colorCount{
CGImageRef imageRef = CGImageCreateCopy([image CGImage]);
size_t imageWidth = CGImageGetWidth(imageRef);
size_t imageHeight = CGImageGetHeight(imageRef);
size_t bytesPerRow = imageWidth * 4;
uint32_t* rgbImageBuf = (uint32_t*)malloc(bytesPerRow * imageHeight);
CGContextRef context = CGBitmapContextCreate(rgbImageBuf, imageWidth, imageHeight, 8, bytesPerRow, CGColorSpaceCreateDeviceRGB(),kCGImageAlphaPremultipliedFirst);
CGContextDrawImage(context, CGRectMake(0, 0, imageWidth, imageHeight), imageRef);
size_t pixelNum = imageWidth * imageHeight;
NSMutableArray *colors = [NSMutableArray array];
uint32_t* pCurPtr = rgbImageBuf;
uint8_t *ptr = (uint8_t *)pCurPtr;
float red = ptr[1]; float green = ptr[2]; float blue = ptr[3];
[colors addObject:[UIColor colorWithRed:red/255 green:green/255 blue:blue/255 alpha:1.0]];
ptr = (uint8_t *)pCurPtr+4*pixelNum-20;
float endRed = ptr[1];float endGreen = ptr[2]; float endBlue = ptr[3];
[colors addObject:[UIColor colorWithRed:endRed/255 green:endGreen/255 blue:endBlue/255 alpha:1.0]];
float middleRed = (red+endRed)/2;float middleGreen = (endGreen+green)/2; float middleBlue = (endBlue+blue)/2;
[colors addObject:[UIColor colorWithRed:middleRed/255 green:middleGreen/255 blue:middleBlue/255 alpha:1.0]];
for (int i = 0; i < colorCount - 1; i++) {
int wave = 40;
int random = arc4random()%2;
int redWave = ( random == 0)?arc4random()%wave:-arc4random()%wave;
int greenWave = (random == 0)?arc4random()%wave:-arc4random()%wave;
int blueWave = (random == 0)?arc4random()%wave:-arc4random()%wave;
[colors addObject:[UIColor colorWithRed:MIN(245, MAX(10, redWave+middleRed))/255 green:MIN(245, MAX(10, greenWave+middleGreen))/255 blue:MIN(245, MAX(10, blueWave+middleBlue))/255 alpha:1.0]];//第一个和中间像素点中间值为基准
}
CGContextRelease(context);
free(rgbImageBuf);
CGImageRelease(imageRef);
return colors;
}
绘制各个元素
绘制各种元素在同一个上下文中执行,除了添加以下元素,也可添加其他形状或者图片,最后合成一张图片。
CGImageRef并不是自动释放的,需要手动释放声明的变量。
UIImage *pic = [UIImage imageNamed:@"tape1a_000004_title"];
self.orignalImageView.image = pic;
CGImageRef bgImageRef = [UIImage imageNamed:@"bg"].CGImage;
NSArray *randomColors = [self randomPixelColorWithImage:pic colorCount:6];//第一个像素点颜色
UIImage *combineImage = [self createImageWithClassImage:pic bgImageRef:bgImageRef bgColor:randomColors.firstObject randomColors:randomColors];
self.combineImageView.image = combineImage;
- (UIImage *)createImageWithClassImage:(UIImage *)image
bgImageRef:(CGImageRef)bgImageRef
bgColor:(UIColor *)bgColor
randomColors:(NSArray<UIColor*>*)randomColor{
CGImageRef imageRef = CGImageCreateCopy([image CGImage]);
size_t bgWidth = CGImageGetWidth(bgImageRef);
size_t bgHeight = CGImageGetHeight(bgImageRef);
CGContextRef context = CGBitmapContextCreate(NULL, bgWidth, bgHeight, 8, 0, CGColorSpaceCreateDeviceRGB(), kCGImageAlphaPremultipliedFirst);
if (!context) return NULL;
CGContextSetFillColorWithColor(context, bgColor.CGColor);
//填充背景色
// CGContextFillRect(context,CGRectMake(0, 0, bgWidth, bgHeight));
//填充渐变背景色
CGFloat gradientLocations[] = { 1.0, 0.0 };
NSArray *gradientColors = @[(__bridge id) [randomColor[1] colorWithAlphaComponent:0.9].CGColor, (__bridge id) [randomColor[0] colorWithAlphaComponent:0.5].CGColor];
CGGradientRef gradient = CGGradientCreateWithColors(CGColorSpaceCreateDeviceRGB(), (__bridge CFArrayRef) gradientColors, gradientLocations);
CGContextDrawLinearGradient(context, gradient, CGPointZero, CGPointMake(bgWidth, bgHeight), kCGGradientDrawsBeforeStartLocation);
[self drawArcWithFillColor:randomColor[3] context:context];//画圆
[self drawArcWithFillColor:randomColor[4] context:context];
[self drawArcWithFillColor:randomColor[5] context:context];
[self drawArcWithFillColor:randomColor[6] context:context];
[self drawTopBezierLineFillColor:randomColor[2] context:context];
CGContextDrawImage(context, CGRectMake(0, 0, bgWidth, bgHeight), bgImageRef); // decodeif (!context) return NULL;
CGContextDrawImage(context, CGRectMake(180*4-9, (bgHeight-137*4)/2, 96*4, 137*4), imageRef); // decodeif (!context) return NULL;
CGImageRef newImageRef = CGBitmapContextCreateImage(context);
UIImage *combineImage = [UIImage imageWithCGImage:newImageRef];
CFRelease(context);
CGImageRelease(imageRef);
CGImageRelease(newImageRef);
return combineImage;
}
//画圆
- (void)drawArcWithFillColor:(UIColor *)fillColor
context:(CGContextRef)context {
CGContextSetFillColorWithColor(context, fillColor?[fillColor colorWithAlphaComponent:0.6].CGColor:[UIColor colorWithRed:1.0 green:0 blue:0 alpha:0.2].CGColor);
CGContextAddArc(context, arc4random()%1280, arc4random()%720, arc4random()%70+30, 0, M_PI*2, 0);
CGContextFillPath(context);
}
//上边边画贝塞尔曲线
- (void)drawBottomBezierLineFillColor:(UIColor *)fillColor
context:(CGContextRef)context{
UIBezierPath *bezierPath = [UIBezierPath bezierPath];
NSMutableArray *points = [NSMutableArray array];
CGPoint auxiliaryStartPoint = CGPointMake(-130, -100); //作为曲线的辅助点
[points addObject:[NSValue valueWithCGPoint:auxiliaryStartPoint]];
CGPoint startPoint = CGPointMake(0, -50);
[points addObject:[NSValue valueWithCGPoint:startPoint]];
CGFloat startx = 0;
for (int i = 0; i < 6; i++) {
startx = startx + 200+arc4random()%100;
CGFloat valueY = arc4random()%70+80;
if(i%2 == 1) {
valueY = arc4random()%50;
}
[points addObject:[NSValue valueWithCGPoint:CGPointMake(startx, valueY)]];
}
[points addObject:[NSValue valueWithCGPoint:CGPointMake(startx+130, -50)]];//曲线辅助点
for (NSInteger i = 0; i < points.count-3; i++) {
CGPoint p1 = [[points objectAtIndex:i] CGPointValue];
CGPoint p2 = [[points objectAtIndex:i+1] CGPointValue];
CGPoint p3 = [[points objectAtIndex:i+2] CGPointValue];
CGPoint p4 = [[points objectAtIndex:i+3] CGPointValue];
if (i == 0) {
[bezierPath moveToPoint:p2];
}
[self getControlPointx0:p1.x andy0:p1.y x1:p2.x andy1:p2.y x2:p3.x andy2:p3.y x3:p4.x andy3:p4.y path:bezierPath];
}
[bezierPath addLineToPoint:[[points objectAtIndex:points.count-1] CGPointValue]];//加上最后的曲线辅助点
[bezierPath addLineToPoint:startPoint];//合闭曲线
CGContextSetFillColorWithColor(context, fillColor?[fillColor colorWithAlphaComponent:0.4].CGColor:[UIColor colorWithRed:1.0 green:0 blue:0 alpha:0.2].CGColor);
CGContextAddPath(context, bezierPath.CGPath);
CGContextFillPath(context);
}
/*
* 计算平滑点
*/
- (void)getControlPointx0:(CGFloat)x0 andy0:(CGFloat)y0
x1:(CGFloat)x1 andy1:(CGFloat)y1
x2:(CGFloat)x2 andy2:(CGFloat)y2
x3:(CGFloat)x3 andy3:(CGFloat)y3
path:(UIBezierPath*) path{
CGFloat smooth_value =0.6;//平滑度,大多数是0.6 效果相对更好
CGFloat ctrl1_x;
CGFloat ctrl1_y;
CGFloat ctrl2_x;
CGFloat ctrl2_y;
CGFloat xc1 = (x0 + x1) /2.0;
CGFloat yc1 = (y0 + y1) /2.0;
CGFloat xc2 = (x1 + x2) /2.0;
CGFloat yc2 = (y1 + y2) /2.0;
CGFloat xc3 = (x2 + x3) /2.0;
CGFloat yc3 = (y2 + y3) /2.0;
CGFloat len1 = sqrt((x1-x0) * (x1-x0) + (y1-y0) * (y1-y0));
CGFloat len2 = sqrt((x2-x1) * (x2-x1) + (y2-y1) * (y2-y1));
CGFloat len3 = sqrt((x3-x2) * (x3-x2) + (y3-y2) * (y3-y2));
CGFloat k1 = len1 / (len1 + len2);
CGFloat k2 = len2 / (len2 + len3);
CGFloat xm1 = xc1 + (xc2 - xc1) * k1;
CGFloat ym1 = yc1 + (yc2 - yc1) * k1;
CGFloat xm2 = xc2 + (xc3 - xc2) * k2;
CGFloat ym2 = yc2 + (yc3 - yc2) * k2;
ctrl1_x = xm1 + (xc2 - xm1) * smooth_value + x1 - xm1;
ctrl1_y = ym1 + (yc2 - ym1) * smooth_value + y1 - ym1;
ctrl2_x = xm2 + (xc2 - xm2) * smooth_value + x2 - xm2;
ctrl2_y = ym2 + (yc2 - ym2) * smooth_value + y2 - ym2;
[path addCurveToPoint:CGPointMake(x2, y2) controlPoint1:CGPointMake(ctrl1_x, ctrl1_y) controlPoint2:CGPointMake(ctrl2_x, ctrl2_y)];
}
计算平滑点,可阅读画贝塞尔曲线 连续曲线 平滑曲线 曲线图表
批量绘制
到这基本算是完成了,对于图片比较多的情况,需要在自动释放池中执行提前释放,避免内存暴涨。
- (IBAction)combineAction:(UIBarButtonItem *)sender {
CGImageRef bgImageRef = [UIImage imageNamed:@"bg"].CGImage;
for (NSInteger i = 0; i < _imageNameList.count; i++) {
@autoreleasepool {
NSString *imageName = self.imageNameList[i];
NSString *imagePath = [NSString stringWithFormat:@"%@/%@",self.bundlePath,imageName];
UIImage *pic = [UIImage imageWithContentsOfFile:imagePath];
NSArray *randomColors = [self randomPixelColorWithImage:pic colorCount:6];//像素点颜色
UIImage *combineImage = [self createImageWithClassImage:pic bgImageRef:bgImageRef bgColor:randomColors.firstObject randomColors:randomColors];
[self savePicToHomeDirectoryWithName:imageName image:combineImage];//保存图片到沙盒
}
}
CGImageRelease(bgImageRef);
}
总结
- 通过位图上下文处理图片在YYImage\SDWebImage里面都有使用,相对UIGraphicsBeginImageContextWithOptions占用内存更小。
- CoreGraphics坐标系和UIKit不一样,可以做转换。
- CoreGraphics内存不是自动管理,需要手动释放。
参考:
iOS坐标系探究
CGContext图形上下文详解
探讨iOS 中图片的解压缩到渲染过程
iOS 画贝塞尔曲线 连续曲线 平滑曲线 曲线图表
iOS绘图指南-Core Graphics(三)-位图图形上下文
https://developer.apple.com/documentation/coregraphics/1455939-cgbitmapcontextcreate