刮刮乐小程序

使用 Core Graphic 实现的刮刮乐小程序。


刮刮乐小程序

源码地址:Github

主要代码

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
79
80
81
82
83
84
85
86
87
88
89
90
91
import UIKit

class maskImage: UIView {

private var imageSource:CGImageRef!

private var customMaskImage:CGImageRef = UIImage(named: "mask2.png")!.CGImage
private var maskBlack:CGImageRef = UIImage(named: "maskblack.png")!.CGImage


/*
http://stackoverflow.com/questions/24339145/how-do-i-write-a-custom-init-for-a-uiview-subclass-in-swift

The init(frame:) version is the default initializer. You must call it only after initializing your instance variables. If this view is being reconstituted from a Nib then your custom initializer will not be called, and instead the init(coder:) version will be called. Since Swift now requires an implementation of the required init(coder:), I have updated the example below and changed the let variable declarations to var and optional. In this case, you would initialize them in awakeFromNib() or at some later time.
*/


init(frame: CGRect,image:CGImageRef) {
super.init(frame: frame)

imageSource = image
}

required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
imageSource = UIImage(named: "images.jpeg")?.CGImage
}



// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func drawRect(rect: CGRect) {
// Drawing code

//1.
//开辟一个临时图像上下文,用于生成绘制路径
UIGraphicsBeginImageContext(self.frame.size)
var imageContext = UIGraphicsGetCurrentContext()

CGContextDrawImage(imageContext, CGRect(x: 0, y: 0, width: self.frame.width, height: self.frame.height), maskBlack)
CGContextSetRGBStrokeColor(imageContext, 1, 1, 1, 1)
CGContextSetLineWidth(imageContext, 20)
CGContextAddPath(imageContext, path)
CGContextStrokePath(imageContext)

var maskImage:CGImageRef = CGBitmapContextCreateImage(imageContext)
UIGraphicsEndImageContext()




var context = UIGraphicsGetCurrentContext()
CGContextSaveGState(context)

//2.
//进行坐标转化
CGContextTranslateCTM(context, 0,self.frame.height)
CGContextScaleCTM(context, 1, -1)






//3.
var mask:CGImageRef = CGImageMaskCreate(CGImageGetWidth(maskImage),
CGImageGetHeight(maskImage),
CGImageGetBitsPerComponent(maskImage),
CGImageGetBitsPerPixel(maskImage),
CGImageGetBytesPerRow(maskImage),
CGImageGetDataProvider(maskImage), nil, true);
var newImageContent = CGImageCreateWithMask(customMaskImage, mask)
4.
CGContextDrawImage(context, CGRect(x: 0, y: 0, width: self.frame.width, height: self.frame.height), imageSource)

CGContextDrawImage(context, CGRect(x: 0, y: 0, width: self.frame.width, height: self.frame.height), newImageContent)

CGContextRestoreGState(context)
}

var path = CGPathCreateMutable()
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
var p = (touches.first as! UITouch).locationInView(self)
CGPathMoveToPoint(path, nil, p.x, p.y)
}
override func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) {
var p = (touches.first as! UITouch).locationInView(self)
CGPathAddLineToPoint(path, nil, p.x, p.y)
setNeedsDisplay()
}
}

说明

drawRect 中:

1.开辟一个临时的图像设备上线文,首先设置该设备上下文的 RGB 线条颜色为白色,然后绘制一张黑色图片,然后在该设备上下文绘制线条路径,接着将该设备上线文生成为一张黑色背景白色线条的图像: maskImage,这张图像将会用于与遮罩层 (遮盖实际图片的灰色图像) 进行 mask 合成。

2.进行坐标转换,下面有详细介绍。

3.使用 CGImageMaskCreate 生成图像: mask,内容由 maskImage 提供,然后使用 CGImageCreateWithMask 函数将灰色遮罩层图像与 mask 进行合成,这样就生成了线条路径是透明的图片,图片其余部分由灰色填充。

4.首先在新获取的设备上下文绘制需要实际显示的图片,然后将合成后的透明路径图片绘制到设备上下文中,这样就达成了刮刮乐的效果。

坐标转换

使用 Core Graphic 读取的图片直接显示到屏幕上会出现一张相反的图片,这时需要进行坐标转换,分别用到的函数是:

1
2
3
4
5
6
7
8
9
10
11
12

var context = UIGraphicsGetCurrentContext()
CGContextSaveGState(context)

...

CGContextTranslateCTM(context, 0,self.frame.height)
CGContextScaleCTM(context, 1, -1)

...

CGContextRestoreGState(context)

这里用两张图片解释 CGContextTranslateCTM 和 CGContextScaleCTM 的坐标转换:

1.红线表示一条在 XY 轴中 0,0 到 100,200 的线条,原始的坐标系统是在手机屏幕左上角,红线往屏幕右下角方向绘制。

2.执行 CGContextTranslateCTM(context, self.frame.width/2, self.frame.height/2) 后坐标起始点 0,0 偏移到了屏幕中心,红线绘制变为屏幕中心点往屏幕右下角方向。

3.继续执行 CGContextScaleCTM(context, 1, -1) ,改变了 Y 轴的方向,红线变为屏幕中心点往屏幕右上角方向画。

CGContextScaleCTM 还可以改线 X 轴的方向,例如 CGContextScaleCTM(context, -1, -1),那么红线的方向就会变为从屏幕中心点向屏幕左上角方向画。

CGContextScaleCTM 还可以改变 X,Y 参数的大小调整 context 的缩放比例,例如 CGContextScaleCTM(context, 10, 10)

因此例子代码中的作用就是将图片反转,然后更改中心点位置,这样才能显示正常的图片。坐标转换完成后,记得使用 CGContextRestoreGState(context) 恢复之前坐标状态,否则会对后面的绘制造成影响。


参考资料:
Quartz 2D Programming Guide

stackoverflow