文章目录
  1. 1. 最终效果图
  2. 2. 解决方案
    1. 2.1. 绘制大图
    2. 2.2. 生成缩略图实时预览
  3. 3. 尝试过的方案
    1. 3.1. 创建UILabel
    2. 3.2. 转换成黑底透明字的图片
    3. 3.3. 把图片中的黑色部分转换成白色

最近因为公司业务需求需要生成一张超大的纯色底文字镂空的图片传给服务器端,做的时候遇到了不少问题,记录下解决问题的过程。

最终效果图

解决方案

绘制大图

生成一张4000x2000的画布,然后计算出文字的位置把文字画上去,再填充背景色,最后把文字部分变透明,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
NSData *lastdata = nil;
@autoreleasepool {
NSString *string = @"Baby";
CGRect rect = CGRectMake(0, 0, 4000, 2000);
//开始绘图
UIGraphicsBeginImageContext(rect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
// Draw the text upside-down
CGContextSaveGState(context);
CGContextTranslateCTM(context, 0, rect.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
UIFont *font = [UIFont fontWithName:@"HelveticaNeue-Bold" size:1300];
/// Make a copy of the default paragraph style
NSMutableParagraphStyle *paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
paragraphStyle.lineBreakMode = NSLineBreakByTruncatingTail;
paragraphStyle.alignment = NSTextAlignmentCenter;
NSDictionary *attributes = @{ NSFontAttributeName: font, NSParagraphStyleAttributeName: paragraphStyle};
//设置行间距
NSMutableAttributedString* attr = [[NSMutableAttributedString alloc]initWithString:string attributes:attributes];
[attr addAttribute:NSKernAttributeName value:@(100) range:NSMakeRange(0, attr.length-1)];
//计算文字的位置
CGSize size = [attr size];
CGRect r = CGRectMake(rect.origin.x,
rect.origin.y + (rect.size.height - size.height)/2.0,
rect.size.width,
size.height);
[attr drawInRect:r];
CGContextRestoreGState(context);
// Create an image mask from what we've drawn so far
CGImageRef alphaMask = CGBitmapContextCreateImage(context);
// Draw a white background (overwriting the previous work)
[[UIColor whiteColor] setFill];
CGContextFillRect(context, rect);
// Draw the image, clipped by the mask
CGContextSaveGState(context);
CGContextClipToMask(context, rect, alphaMask);
CGContextClearRect(context, rect);
CGContextRestoreGState(context);
CGImageRelease(alphaMask);
CGImageRef imgRef = CGBitmapContextCreateImage(context);
UIImage *img = [UIImage imageWithCGImage:imgRef];
CGImageRelease(imgRef);
UIGraphicsEndImageContext();
lastdata = UIImagePNGRepresentation(img);
NSLog(@"draw image app memory %@",[UIDevice usedMemory]);
}
NSLog(@"after @autoreleasepool app memory %@",[UIDevice usedMemory]);

通过这个方法绘制图片的时候占用的内存较小、速度也快。但是在绘制图片的过程中内存峰值会瞬间增长30M左右,所以加上 @autoreleasepool 在绘制完成之后立刻通知RunLoop进行内存回收,而不是等待线程执行到某个点时再自动进行内存回收。

生成缩略图实时预览

图片绘制完成之后,我们可以把大图保存到文件,再生成一张缩略图在界面上实时预览,减小内存的消耗。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//把生成的文件保存到文件
NSString *imagePath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
imagePath = [imagePath stringByAppendingPathComponent:@"mask.png"];
[lastdata writeToFile:imagePath atomically:YES];
//生成缩略图,也可以使用 CGImageSourceCreateWithURL 从文件中创建CGImageSourceRef
CGImageSourceRef src = CGImageSourceCreateWithData((__bridge CFDataRef)lastdata, NULL);
CFDictionaryRef options = (__bridge CFDictionaryRef) @{
(id) kCGImageSourceCreateThumbnailWithTransform : @YES,
(id) kCGImageSourceCreateThumbnailFromImageAlways : @YES,
(id) kCGImageSourceThumbnailMaxPixelSize : @(800), //缩略图长边的像素
};
CGImageRef thumbnail = CGImageSourceCreateThumbnailAtIndex(src, 0, options);
CFRelease(src);
UIImage *thumImage = [UIImage imageWithCGImage:thumbnail];
CGImageRelease(thumbnail);
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(10, 20, 300, 150)];
imageView.image = thumImage;
[self.view addSubview:imageView];

尝试过的方案

在得到上面的解决方案之前还尝试了其他的方案,最终因为占用内存太大、耗时太长被否决了,但是我觉得这些尝试还是让我学到点东西的,所以也记录了下来。

创建UILabel

先创建一个label,并设置label的属性得到自己想要基本的样式,但是必须 黑底白字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
NSData *lastdata = nil;
@autoreleasepool {
UILabel *label = [[UILabel alloc] init];
label.bounds = CGRectMake(0, 0, 4000, 2000);
label.font = [UIFont fontWithName:@"HelveticaNeue-Bold" size:1300];
label.text = @"Baby";
label.textAlignment = NSTextAlignmentCenter;
label.backgroundColor = [UIColor blackColor];
label.textColor = [UIColor whiteColor];
//把label转换成图片,不用转换成高分辨率的
UIGraphicsBeginImageContextWithOptions(label.bounds.size, NO, 1.0);
[label.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
lastdata = UIImagePNGRepresentation(image);
NSLog(@"draw image app memory %@",[UIDevice usedMemory]);
}
NSLog(@"after @autoreleasepool app memory %@",[UIDevice usedMemory]);

到这一步的时候就发现一个问题,截图完成之后内存不能被回收,过了大概1分钟左右才被自动回收,猜测和 layer 有关

转换成黑底透明字的图片

在网上找到一个方法可以把上述得到的黑底白字的图片转换成黑底透明字的图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
UIImage *image = [[UIImage imageWithData:lastdata] inverseMaskImage];
+ (UIImage *)imageWithColor:(UIColor *)color size:(CGSize)size
{
CGRect rect = CGRectMake(0, 0, size.width, size.height);
UIGraphicsBeginImageContextWithOptions(rect.size, NO, [UIScreen mainScreen].scale);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [color CGColor]);
CGContextFillRect(context, rect);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
/** 纯白色表示被遮罩区域将完全透明,纯黑色表示被遮罩区域将会原封不动保留下来,
灰色部分(0x000000~0xFFFFFF)表示被遮罩区域将会处理成半透明的效果 */
- (UIImage *)inverseMaskImage
{
UIImage *image = [UIImage imageWithColor:[UIColor blackColor] size:self.size];
CGImageRef maskRef = self.CGImage;
CGImageRef mask = CGImageMaskCreate(CGImageGetWidth(maskRef),
CGImageGetHeight(maskRef),
CGImageGetBitsPerComponent(maskRef),
CGImageGetBitsPerPixel(maskRef),
CGImageGetBytesPerRow(maskRef),
CGImageGetDataProvider(maskRef), NULL, false);
CGImageRef sourceImage = [image CGImage];
CGImageRef imageWithAlpha = sourceImage;
//add alpha channel for images that don't have one (ie GIF, JPEG, etc...)
//this however has a computational cost
if (CGImageGetAlphaInfo(sourceImage) == kCGImageAlphaNone) {
//copyImageAndAddAlphaChannel
CGImageRef retVal = NULL;
size_t width = CGImageGetWidth(sourceImage);
size_t height = CGImageGetHeight(sourceImage);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef offscreenContext = CGBitmapContextCreate(NULL, width, height, 8, 0, colorSpace,
kCGImageAlphaPremultipliedFirst);
if (offscreenContext != NULL) {
CGContextDrawImage(offscreenContext, CGRectMake(0, 0, width, height), sourceImage);
retVal = CGBitmapContextCreateImage(offscreenContext);
CGContextRelease(offscreenContext);
}
CGColorSpaceRelease(colorSpace);
imageWithAlpha = retVal;
}
CGImageRef masked = CGImageCreateWithMask(imageWithAlpha, mask);
CGImageRelease(mask);
//release imageWithAlpha if it was created by CopyImageAndAddAlphaChannel
if (sourceImage != imageWithAlpha) {
CGImageRelease(imageWithAlpha);
}
UIImage *retImage = [UIImage imageWithCGImage:masked scale:self.scale orientation:self.imageOrientation];
CGImageRelease(masked);
return retImage;
}

把图片中的黑色部分转换成白色

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
image = [image tintedImageWithColor:[UIColor whiteColor] style:UIImageTintStyleKeepingAlpha];
/**
* The image tinting styles.
**/
typedef enum __USImageTintStyle
{
/**
* Keep transaprent pixels (alpha == 0) and tint all other pixels.
**/
UIImageTintStyleKeepingAlpha = 1,
/**
* Keep non transparent pixels and tint only those that are translucid.
**/
UIImageTintStyleOverAlpha = 2,
/**
* Remove (turn to transparent) non transparent pixels and tint only those that are translucid.
**/
UIImageTintStyleOverAlphaExtreme = 3,
} UIImageTintStyle;
/** 根据给定的颜色和模式替换图片中的色彩 */
- (UIImage *)tintedImageWithColor:(UIColor*)color style:(UIImageTintStyle)tintStyle
{
if (!color)
return self;
CGFloat scale = self.scale;
CGSize size = CGSizeMake(scale * self.size.width, scale * self.size.height);
UIGraphicsBeginImageContext(size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(context, 0, size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGRect rect = CGRectMake(0, 0, size.width, size.height);
// ---
if (tintStyle == UIImageTintStyleKeepingAlpha)
{
CGContextSetBlendMode(context, kCGBlendModeNormal);
CGContextDrawImage(context, rect, self.CGImage);
CGContextSetBlendMode(context, kCGBlendModeSourceIn);
[color setFill];
CGContextFillRect(context, rect);
}
else if (tintStyle == UIImageTintStyleOverAlpha)
{
[color setFill];
CGContextFillRect(context, rect);
CGContextSetBlendMode(context, kCGBlendModeNormal);
CGContextDrawImage(context, rect, self.CGImage);
}
else if (tintStyle == UIImageTintStyleOverAlphaExtreme)
{
[color setFill];
CGContextFillRect(context, rect);
CGContextSetBlendMode(context, kCGBlendModeDestinationOut);
CGContextDrawImage(context, rect, self.CGImage);
}
// ---
CGImageRef bitmapContext = CGBitmapContextCreateImage(context);
UIImage *coloredImage = [UIImage imageWithCGImage:bitmapContext scale:scale orientation:UIImageOrientationUp];
CGImageRelease(bitmapContext);
UIGraphicsEndImageContext();
return coloredImage;
}