1 | //MARK: private method |
2 | /// 填充颜色 |
3 | /// |
4 | /// - Parameters: |
5 | /// - point: 种子点 |
6 | /// - color: 填充颜色 |
7 | private func floodFill(from point:CGPoint) { |
8 | if let imageRef = image?.cgImage { |
9 | let width = imageRef.width |
10 | let height = imageRef.height |
11 | let widthScale = CGFloat(width) / bounds.width |
12 | let heightScale = CGFloat(height) / bounds.height |
13 | //把相对于view的touch point 转换成image的像素点的坐标点 |
14 | let realPoint = CGPoint(x: point.x * widthScale, y: point.y * heightScale) |
15 | scanedLines = [:] |
16 | imageSize = CGSize(width: width, height: height) |
17 | pixels = Array<UInt32>(repeating: 0, count: width * height) |
18 | let colorSpace = CGColorSpaceCreateDeviceRGB() //像素点的颜色空间 |
19 | let bitsPerComponent = 8 //颜色空间每个通道占用的bit |
20 | let bytesPerRow = width * 4 //image每一行所占用的byte |
21 | let bitmapInfo = CGImageAlphaInfo.premultipliedLast.rawValue |
22 | if let context = CGContext(data: &(pixels), width: width, height: height, bitsPerComponent: bitsPerComponent, |
23 | bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo) { |
24 | context.draw(imageRef, in: CGRect(x: 0, y: 0, width: width, height: height)) |
25 | let pixelIndex = lrintf(Float(realPoint.y)) * width + lrintf(Float(realPoint.x)) |
26 | let newColorRgbaValue = newColor.rgbaValue |
27 | let colorRgbaValue = pixels[pixelIndex] |
28 | //如果点击的黑色边框,直接退出 |
29 | if isBlackColor(color: colorRgbaValue) { |
30 | return |
31 | } |
32 | //如果点击的颜色和新颜色一样,退出 |
33 | if compareColor(color: colorRgbaValue, otherColor: newColorRgbaValue, tolorance: colorTolorance) { |
34 | return |
35 | } |
36 | //存放种子点的栈 |
37 | seedPointList.push(realPoint) |
38 | while !seedPointList.isEmpty { |
39 | if let point = seedPointList.pop() { |
40 | let (xLeft,xRight) = fillLine(seedPoint: point, newColorRgbaValue: newColorRgbaValue, |
41 | originalColorRgbaValue: colorRgbaValue) |
42 | scanLine(lineNumer: Int(point.y) + 1, xLeft: xLeft, xRight: xRight, originalColorRgbaValue: colorRgbaValue) |
43 | scanLine(lineNumer: Int(point.y) - 1, xLeft: xLeft, xRight: xRight, originalColorRgbaValue: colorRgbaValue) |
44 | } |
45 | } |
46 | if let cgImage = context.makeImage() { |
47 | image = UIImage(cgImage: cgImage, scale: image?.scale ?? 2, orientation: .up) |
48 | } |
49 | } |
50 | } |
51 | } |
52 |
|
53 | /// 通过种子点向左向右填充 |
54 | /// |
55 | /// - Parameters: |
56 | /// - seedPoint: 种子点 |
57 | /// - newColorRgbaValue: 填充的新颜色的值 |
58 | /// - originalColorRgbaValue: 触摸点颜色的值 |
59 | /// - Returns: 种子点填充的左右区间 都是闭区间 |
60 | private func fillLine(seedPoint:CGPoint,newColorRgbaValue:UInt32,originalColorRgbaValue:UInt32) -> (Int,Int) { |
61 | let imageW = Int(imageSize.width) |
62 | let currntLineMinIndex = Int(seedPoint.y) * imageW |
63 | let currntLineMaxIndex = currntLineMinIndex + imageW |
64 | let currentPixelIndex = currntLineMinIndex + Int(seedPoint.x) |
65 | var xleft = Int(seedPoint.x) |
66 | var xright = xleft |
67 | if pixels.count >= currntLineMaxIndex { |
68 | var tmpIndex = currentPixelIndex |
69 | while tmpIndex >= currntLineMinIndex && |
70 | compareColor(color: originalColorRgbaValue, otherColor: pixels[tmpIndex], tolorance: colorTolorance){ |
71 | pixels[tmpIndex] = newColorRgbaValue |
72 | tmpIndex -= 1 |
73 | xleft -= 1 |
74 | } |
75 | tmpIndex = currentPixelIndex + 1 |
76 | while tmpIndex < currntLineMaxIndex && |
77 | compareColor(color: originalColorRgbaValue, otherColor: pixels[tmpIndex], tolorance: colorTolorance){ |
78 | pixels[tmpIndex] = newColorRgbaValue |
79 | tmpIndex += 1 |
80 | xright += 1 |
81 | } |
82 | } |
83 | return (xleft + 1,xright) |
84 | } |
85 |
|
86 |
|
87 | /// 从xLeft到xRight的扫描第lineNumer行 |
88 | /// |
89 | /// - Parameters: |
90 | /// - lineNumer: 行数 |
91 | /// - xLeft: 扫描线的最左侧 |
92 | /// - xRight: 扫描线的最右侧 |
93 | /// - originalColorRgbaValue: 触摸点颜色的值 |
94 | private func scanLine(lineNumer:Int,xLeft:Int,xRight:Int,originalColorRgbaValue:UInt32) { |
95 | if lineNumer < 0 || CGFloat(lineNumer) > imageSize.height - 1{ |
96 | return |
97 | } |
98 | var xCurrent = xLeft //当前被扫描的点的x位置 |
99 | let currentLineOriginalIndex = lineNumer * Int(imageSize.width) |
100 | var currentPixelIndex = currentLineOriginalIndex + xLeft //当前被扫描的点的所在像素点的位置 |
101 | var currntLineMaxIndex = currentLineOriginalIndex + xRight //当前扫描线需要扫描的最后一个点的位置 |
102 | //此处是对种子扫描线算法的一点小优化 |
103 | var leftSpiltIndex:Int? |
104 | if var scanLine = scanedLines[lineNumer] { |
105 | if scanLine.xLeft >= xRight || scanLine.xRight <= xLeft {//没有相交,什么也不做 |
106 | }else if scanLine.xLeft <= xLeft && scanLine.xRight >= xRight { //旧扫描与新扫描的范围关系是包含 |
107 | return |
108 | }else if scanLine.xLeft <= xLeft && scanLine.xRight <= xRight {//旧扫描与新扫描的范围关系是左包含右被包含 |
109 | xCurrent = scanLine.xRight + 1 |
110 | currentPixelIndex = currentLineOriginalIndex + scanLine.xRight + 1 |
111 | scanLine.xRight = xRight |
112 | scanedLines[lineNumer] = scanLine |
113 | }else if scanLine.xLeft >= xLeft && scanLine.xRight >= xRight {//旧扫描与新扫描的范围关系是左被包含右包含 |
114 | currntLineMaxIndex = currentLineOriginalIndex + scanLine.xLeft - 1 |
115 | leftSpiltIndex = currentLineOriginalIndex + scanLine.xLeft |
116 | scanLine.xLeft = xLeft |
117 | scanedLines[lineNumer] = scanLine |
118 | }else if scanLine.xLeft >= xLeft && scanLine.xRight <= xRight {//旧扫描与新扫描的范围关系是被包含 |
119 | scanLine.xLeft = xLeft |
120 | scanLine.xRight = xRight |
121 | scanedLines[lineNumer] = scanLine |
122 | } |
123 | }else { |
124 | scanedLines[lineNumer] = FillLineInfo(lineNumber: lineNumer, xLeft: xLeft, xRight: xRight) |
125 | } |
126 | while currentPixelIndex <= currntLineMaxIndex { |
127 | var isFindSeed = false |
128 | //找到此区间的种子点,种子点是存在非边界且未填充的像素点,这些相邻的像素点中最右边的一个 |
129 | while currentPixelIndex < currntLineMaxIndex && |
130 | compareColor(color: originalColorRgbaValue, otherColor: pixels[currentPixelIndex], tolorance: colorTolorance) { |
131 | isFindSeed = true |
132 | currentPixelIndex += 1 |
133 | xCurrent += 1 |
134 | } |
135 |
|
136 | if isFindSeed { |
137 | //如果找到种子点,需要判断while循环的退出条件是什么 |
138 | //如果是到区间最右边的倒数第二个点,则需要判断最右边的点是否和originalColorRgbaValue颜色一样,如果一样,则最右边的入栈,否则把上一个点入栈 |
139 | //如果是碰到了边界点退出的,则把当前点的上一个点入栈 |
140 | if compareColor(color: originalColorRgbaValue, otherColor: pixels[currentPixelIndex], tolorance: colorTolorance) && |
141 | currentPixelIndex == currntLineMaxIndex { |
142 | //若当旧扫描与新扫描的范围关系是左被包含右包含,需要扫描的范围应该是新扫描范围的左点到旧扫描范围的左点的上一个点 |
143 | //此时若扫描范围内的最右点颜色与originalColorRgbaValue一样,并且旧扫描范围的左点的颜色也与originalColorRgbaValue一样,则不需要入栈 |
144 | if leftSpiltIndex == nil || |
145 | !compareColor(color: originalColorRgbaValue, otherColor: pixels[leftSpiltIndex!], tolorance: colorTolorance){ |
146 | seedPointList.push(CGPoint(x: xCurrent, y: lineNumer)) |
147 | } |
148 | }else { |
149 | seedPointList.push(CGPoint(x: xCurrent - 1, y: lineNumer)) |
150 | } |
151 | } |
152 | currentPixelIndex += 1 |
153 | xCurrent += 1 |
154 | } |
155 | } |
156 |
|
157 | /// 判断颜色是否是黑色 |
158 | /// |
159 | /// - Returns: true 是 or false 不是 |
160 | private func isBlackColor(color:UInt32) -> Bool { |
161 | let colorRed = Int((color >> 0) & 0xff) |
162 | let colorGreen = Int((color >> 8) & 0xff) |
163 | let colorBlue = Int((color >> 16) & 0xff) |
164 | let colorAlpha = Int((color >> 24) & 0xff) |
165 |
|
166 | if colorRed < colorTolorance && |
167 | colorGreen < colorTolorance && |
168 | colorBlue < colorTolorance && |
169 | colorAlpha > 255 - colorTolorance{ |
170 | return true |
171 | } |
172 | return false |
173 | } |
174 |
|
175 | /// 是否是相似的颜色 |
176 | /// |
177 | /// - Returns: true 相似 or false 不相似 |
178 | private func compareColor(color:UInt32, otherColor:UInt32, tolorance:Int) -> Bool { |
179 | if color == otherColor { |
180 | return true |
181 | } |
182 | let colorRed = Int((color >> 0) & 0xff) |
183 | let colorGreen = Int((color >> 8) & 0x00ff) |
184 | let colorBlue = Int((color >> 16) & 0xff) |
185 | let colorAlpha = Int((color >> 24) & 0xff) |
186 |
|
187 | let otherColorRed = Int((otherColor >> 0) & 0xff) |
188 | let otherColorGreen = Int((otherColor >> 8) & 0xff) |
189 | let otherColorBlue = Int((otherColor >> 16) & 0xff) |
190 | let otherColorAlpha = Int((otherColor >> 24) & 0xff) |
191 |
|
192 | if abs(colorRed - otherColorRed) > tolorance || |
193 | abs(colorGreen - otherColorGreen) > tolorance || |
194 | abs(colorBlue - otherColorBlue) > tolorance || |
195 | abs(colorAlpha - otherColorAlpha) > tolorance { |
196 | return false |
197 | } |
198 | return true |
199 | } |
200 | extension UIColor { |
201 | /// 获取颜色的UInt32表示形式 |
202 | fileprivate var rgbaValue:UInt32 { |
203 | var red:CGFloat = 0 |
204 | var green:CGFloat = 0 |
205 | var blue:CGFloat = 0 |
206 | var alpha:CGFloat = 0 |
207 | getRed(&red, green: &green, blue: &blue, alpha: &alpha) |
208 | return UInt32(red * 255) << 0 | UInt32(green * 255) << 8 | UInt32(blue * 255) << 16 | UInt32(alpha * 255) << 24 |
209 | } |
210 | } |