Core Graphics实现各种艺术字效果

1.初览

在正式开始前先简单介绍下CoreGraphics,我会把CoreGraphics分解成概念和套路两个部分,具体框架设计思路和API用法不会涉及,可移步参考Quartz 2D Programming Guide

  • 概念
    概念好比武学中的内功心法,理解了这些,才能发挥出招式真正的威力,CoreGraphics中比较核心的概念是Graphics Contexts,这将是本文引入的唯一的一个概念。
  • Graphics Contexts(图形上下文)
    Context是个比较抽象的东西,它不仅仅是一个可以绘制的图层,还包含为当前图层设置的参数,如阴影,线条粗细,绘制模式等。可以类比成一个新建的Photoshop图层以及当前笔触,颜色等配置。对于移动平台,有三种常见的Context
    1.View Graphics Context: 由UIView自动创建,你重写UIView drawRect方法时,你的内容会画在这个上下文上。
    2.Bitmap Graphics Context: 绘制在该上下文的内容会以点阵形式存储在一块内存中。简单说,就是为图片开辟一块内存,然后在里面画东西,上下文帮你把图片内存抽象成一个Context(图层)了。
    3.PDF Graphics Context:顾名思义,跟PDF文件相关,本文不会涉及。
  • 套路
    就是惯用套路,这相当于武学中的招式。我一直有个疑问,如果一门武学没有招式,怎么判断这是哪个门子的武学? CoreGraphics里面就有相关的招式,这里将带入几个具备代表性的招式,以后看到别人用CoreGraphics写的看似无招的代码,其实仔细品读后会发现所谓的无招只是没有固定套路,招式还是在的。

第一招:拿取当前Graphics Context。

CGContextRef context = UIGraphicsGetCurrentContext();

通常是起始招式,接下来一般会用来为上下文设置参数比如说设置画线时的宽度CGContextSetLineWidth(context, 1),把上下文内容截取成一个位图CGBitmapContextCreateImage(context)等等。

第二招:开辟Bitmap Graphics Context

UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0);
draw something ...
UIGraphicsEndImageContext();

注意上下构成一套组合,是开启然后关闭一个Bitmap Graphics Context,在这区域内的所有绘制操作都是对于该Bitmap Context的,在代码块里面使用第一招UIGraphicsGetCurrentContext()拿到的就是这个Bitmap Graphics Context。

第三招: 保存和恢复当前Context状态

Set line width 5, black hair color ...
Draw hair...
CGContextSaveGState(context);//save line width 5, black color
Set line width 8, red color...
Draw hair ornaments...
CGContextRestoreGState(context);//restore line width 5, black color
Continue to draw hair...

这招又是一个组合块,会产生什么效果呢?举个栗子,你正在描绘一个人物的头部画像,画头发=>画装饰物=>修饰头发=>修饰装饰物...这样需要来回切换着画笔状态,实际过程中会有很多参数需要配置,这个招式让我们能保存恢复某些状态。当你使用CGContextSaveGState后接下来你更改画笔状态,画完后再使用CGContextRestoreGState可以将状态恢复到使用Save方法之前。关于哪些状态可以保存,请参考CGContextSaveGState Discussion部分

最后一招:扭转乾坤️

CGContextConcatCTM(context, CGAffineTransformMake(1, 0, 0, -1, 0, CGRectGetHeight(rect)));

这是你熟悉又陌生的线性变换操作,因为Core Graphics(原点左下角,y轴向上为正)使用的坐标系和UIKit(原点左上角,y轴向下为正)的坐标系是不一样的,在重写UIView drawRect的时候直接画上去的内容是一个相对x轴的镜像。因此需要做一次线性变换来得到正确的方位,该操作是将y变成-1*y 然后沿y轴平移,平移距离为CGRectGetHeight(rect)。

好了招式都介绍完了。

2.绘制

接下来要把学到的内容用于实战了,希望通过实战演练,大家能加深理解,逐渐达到无招的状态。代码长度不一,只列出关键部分,文底有全套实现地址。

  • 发光

效果图:

Core Graphics实现各种艺术字效果_第1张图片
glow.gif

实现:

//为文字设置阴影
CGContextSetShadowWithColor(context, CGSizeZero, self.glowSize, self.glowColor.CGColor);

第一招拿到context后,这个效果的核心代码就只有1句了,我都不好意思做解释。你甚至可以直接修改UILabel自带的阴影属性来达到这个效果。

  • 描边

效果图:

Core Graphics实现各种艺术字效果_第2张图片
stroke.gif

实现:

//设置边线宽度
CGContextSetLineWidth(context, self.outlineWidth);
//设置线条转角样式
CGContextSetLineJoin(context, kCGLineJoinRound);
//设置绘图模式为描线
CGContextSetTextDrawingMode(context, kCGTextStroke);

这个效果的核心只有3行,通过第一招拿到context,然后配置context。接下来调用super的draw方法,这时候就画了字的描边。如果要如图一样的黑色填充,把DrawingMode改成Fill的模式,再调用一遍super的draw方法即可。

  • 渐变

效果图:

Core Graphics实现各种艺术字效果_第3张图片
gradient.gif

实现:

//>>>第一部分
//第二招开始
UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0);
//把当前内容绘制在Bitmap Context上
[super drawTextInRect:rect];
//第一招
CGContextRef context = UIGraphicsGetCurrentContext();
//以当前context内容生成一张图片
CGImageRef mask = CGBitmapContextCreateImage(context);
//第二招结束
UIGraphicsEndImageContext();

//>>>第二部分
//第一招,此时是View Graphics context了
context = UIGraphicsGetCurrentContext();
//最后一招,扭转乾坤
CGContextConcatCTM(context, CGAffineTransformMake(1, 0, 0, -1, 0, CGRectGetHeight(rect)));
//注意是ClipTo,因此只有mask部分能被绘制上内容
CGContextClipToMask(context, rect, mask);

//>>>第三部分
//创建渐变
CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)color, NULL);
//绘制渐变,渐变只显示mask部分
CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, kCGGradientDrawsBeforeStartLocation|kCGGradientDrawsAfterEndLocation);

这个效果大体分为3个部分,第一部分把原来的字形画在一张图片里用作mask。第二部分使用mask裁剪当前view的context。第三部分在当前view的context上绘制一个线性渐变。除线性渐变外,还有径向渐变,具体可以参考结尾的Git代码。

  • 镂空

效果图:

Core Graphics实现各种艺术字效果_第4张图片
hollow.gif

实现:

//第一部分
UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0);
[super drawTextInRect:rect];
CGContextRef context = UIGraphicsGetCurrentContext();
CGImageRef image = CGBitmapContextCreateImage(context);
UIGraphicsEndImageContext();

//第二部分
context = UIGraphicsGetCurrentContext();
CGContextConcatCTM(context, CGAffineTransformMake(1, 0, 0, -1, 0, CGRectGetHeight(rect)));
//使用第一部分得到的image创建一个mask,这里得到的是一个反向的遮罩
CGImageRef mask = CGImageMaskCreate(CGImageGetWidth(image), CGImageGetHeight(image), CGImageGetBitsPerComponent(image), CGImageGetBitsPerPixel(image), CGImageGetBytesPerRow(image), CGImageGetDataProvider(image), CGImageGetDecode(image), CGImageGetShouldInterpolate(image));
CGContextClipToMask(context, rect, mask);

//第三部分
//设置为填充色
[self.maskColor set];
//使用颜色填充区域
CGContextFillRect(context, rect);

该实现跟渐变大体相似,不过这里创建了一个反向的mask,把字体区域给镂空了。然后其他部分填充为一个纯色,当然也可以用图片填充这个区域。还有一种镂空的实现是通过修改BlendMode剔除像素,这里不赘述了。

  • 3D

效果图:

Core Graphics实现各种艺术字效果_第5张图片
3d.gif

实现:

//循环绘制text到context,每次偏移几个像素
[self.text drawInRect:CGRectMake(rect.origin.x + i, rect.origin.y + i, rect.size.width, rect.size.height) withAttributes:attrs];

从动画中应该也可以看出端倪,所谓的3D效果其实是很多图层叠加起来的,因此真正的核心代码只有这一句。但相应的为了实现这种透视感,每一层的颜色和阴影都有些许变化。

  • 涂层

效果图:

Core Graphics实现各种艺术字效果_第6张图片
marked.gif

实现:

//循环绘制图片,图片偏移i = i + randomValue
[self.strokeTexture drawInRect:CGRectMake(i, midY - self.strokeWidth/2.f, self.strokeWidth, self.strokeWidth) blendMode:kCGBlendModeNormal alpha:self.maskAlpha];

这个效果的核心代码也只有这一句,基本思想是用一张笔刷灰度图,修改该灰度图的TintColor,然后绘制在context上,通过随机调整间隔,就达到了深浅相间的效果。控制文字和图片的绘制顺序,就形成了上下效果。

  • 故障

效果图:

Core Graphics实现各种艺术字效果_第7张图片
glitch.gif

实现:
这个效果的实现没有引入什么新的操作,是一些基本操作的组合。
1.用3中颜色先把文字画3遍

CGRect bottomRect = CGRectMake(rect.origin.x + self.bottomOffset.x, rect.origin.y + self.bottomOffset.y, rect.size.width, rect.size.height);
CGRect middleRect = CGRectMake(rect.origin.x + self.middleOffset.x, rect.origin.y + self.middleOffset.y, rect.size.width, rect.size.height);
self.textColor = self.bottomColor;
[super drawTextInRect:bottomRect];
self.textColor = self.middleColor;
[super drawTextInRect:middleRect];
self.textColor = self.topColor;
[super drawTextInRect:rect];

得到下面的效果

Core Graphics实现各种艺术字效果_第8张图片
step1.gif

2.为图片添加切片

//得到切片图片
CGImageRef sliceRef = CGImageCreateWithImageInRect(contentImage, imageSlice);
//把原上下文切片部分内容剔除
CGContextClearRect(context, contentSlice);
//把切片画到原上下文被剔除部分,左右随机平移一定距离
CGContextDrawImage(context, translatedRect, rotateRef);
Core Graphics实现各种艺术字效果_第9张图片
step2.gif
Core Graphics实现各种艺术字效果_第10张图片
step2_2.gif

3.添加随机的线段
代码还有很大优化空间,不列举了,说下基本实现思路吧

  • 构建一个循环体
  • 循环体内随机生成CGRect
  • 过滤掉重叠的Rect
Core Graphics实现各种艺术字效果_第11张图片
step3.gif
  • 材质

效果图:

Core Graphics实现各种艺术字效果_第12张图片
materia.png

实现:
这不是蒙图实现!这不是蒙图实现!这不是蒙图实现!蒙图很难实现这种有高低落差的光影效果。

//使用CIHeightFieldFromMask生成高低落差图
CIImage *inputImage = [CIImage imageWithCGImage:imageRef];
CIFilter *filter = [CIFilter filterWithName:@"CIHeightFieldFromMask"];
[filter setValue:inputImage forKey:kCIInputImageKey];
CIImage *outputImage = filter.outputImage;
CGImageRelease(imageRef); imageRef = NULL;

//使用CIShadedMaterial拼接材质
CIImage *materia = [CIImage imageWithCGImage:self.materiaImage.CGImage];
CIFilter *filterMaterial = [CIFilter filterWithName:@"CIShadedMaterial"];
[filterMaterial setValue:outputImage forKey:kCIInputImageKey];
[filterMaterial setValue:materia forKey:kCIInputShadingImageKey];
CIImage *finalEffect = filterMaterial.outputImage;
UIImage *finalImage = [UIImage imageWithCIImage:finalEffect];
[finalImage drawInRect:rect];

通过使用不同的材质球,来显示不同的材质效果,上图使用的材质图

Core Graphics实现各种艺术字效果_第13张图片
Golden.png

这个效果主要用到的是Core Image里面的filter,目的是引入实现特殊字体的另一个思路,通过第二招将文字变成图片,然后就可以组合使用Core Image Filter来实现更加复杂的效果。CoreImageFilterReference

附上代码地址ArtFontDemo

你可能感兴趣的:(Core Graphics实现各种艺术字效果)