├── .gitignore ├── 1.png ├── 2.jpg ├── FillImageView.h ├── FillImageView.m ├── LICENSE ├── LinkedListStack.h ├── LinkedListStack.m ├── README.md ├── ViewController.h └── ViewController.m /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | # CocoaPods 32 | # 33 | # We recommend against adding the Pods directory to your .gitignore. However 34 | # you should judge for yourself, the pros and cons are mentioned at: 35 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 36 | # 37 | # Pods/ 38 | 39 | # Carthage 40 | # 41 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 42 | # Carthage/Checkouts 43 | 44 | Carthage/Build 45 | 46 | # fastlane 47 | # 48 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 49 | # screenshots whenever they are needed. 50 | # For more information about the recommended setup visit: 51 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 52 | 53 | fastlane/report.xml 54 | fastlane/Preview.html 55 | fastlane/screenshots/**/*.png 56 | fastlane/test_output 57 | 58 | # Code Injection 59 | # 60 | # After new code Injection tools there's a generated folder /iOSInjectionProject 61 | # https://github.com/johnno1962/injectionforxcode 62 | 63 | iOSInjectionProject/ 64 | -------------------------------------------------------------------------------- /1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ironCheng/iFill/761781c73ea059223061645064e8ee3b8ce8fff2/1.png -------------------------------------------------------------------------------- /2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ironCheng/iFill/761781c73ea059223061645064e8ee3b8ce8fff2/2.jpg -------------------------------------------------------------------------------- /FillImageView.h: -------------------------------------------------------------------------------- 1 | // 2 | // FillImageView.h 3 | // iFill 4 | // 5 | // Created by iRon_iMac on 2018/7/12. 6 | // Copyright © 2018年 iRon_. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface FillImageView : UIImageView 12 | 13 | /* 14 | * 需要填充的颜色 15 | */ 16 | @property (nonatomic, strong) UIColor *newcolor; 17 | /* 18 | * 缩放比例 19 | */ 20 | @property (nonatomic, assign) CGFloat scaleNum; 21 | 22 | /* 23 | * 撤销 24 | */ 25 | - (void)revokeOption; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /FillImageView.m: -------------------------------------------------------------------------------- 1 | // 2 | // FillImageView.m 3 | // iFill 4 | // 5 | // Created by iRon_iMac on 2018/7/12. 6 | // Copyright © 2018年 iRon_. All rights reserved. 7 | // 8 | 9 | #import "FillImageView.h" 10 | #import "LinkedListStack.h" 11 | 12 | // 开启反锯齿 13 | #define Using_Antialiasing NO 14 | // 容差 15 | #define Tolerance 10 16 | 17 | 18 | @interface FillImageView () 19 | 20 | @property (nonatomic, strong) NSMutableArray *revokePointsArray; 21 | 22 | @end 23 | 24 | @implementation FillImageView 25 | 26 | #pragma mark - Public Method 27 | 28 | - (void)revokeOption 29 | { 30 | CGPoint lastPoint = [self.revokePointsArray.lastObject CGPointValue]; 31 | dispatch_async(dispatch_get_main_queue(), ^{ 32 | /* 那个点染成白色 */ 33 | UIImage *image = [self floodFillFromPoint:lastPoint withColor:[UIColor whiteColor]]; 34 | if (image) { 35 | self.image = image; 36 | } 37 | }); 38 | } 39 | 40 | #pragma mark - System Method 41 | 42 | - (instancetype)initWithFrame:(CGRect)frame 43 | { 44 | self = [super initWithFrame:frame]; 45 | if (self) { 46 | _revokePointsArray = [NSMutableArray array]; 47 | self.userInteractionEnabled = YES; 48 | self.multipleTouchEnabled = YES; 49 | self.newcolor = [UIColor redColor]; 50 | self.scaleNum = 1; 51 | } 52 | return self; 53 | } 54 | 55 | - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event 56 | { 57 | CGPoint point = [[[event allTouches] anyObject] locationInView:self]; 58 | NSArray *touchesArray = [[event allTouches] allObjects]; 59 | if (touchesArray.count == 1) { 60 | dispatch_async(dispatch_get_main_queue(), ^{ 61 | UIImage *image = [self floodFillFromPoint:point withColor:self.newcolor]; 62 | if (image) { 63 | self.image = image; 64 | } 65 | }); 66 | } 67 | } 68 | 69 | //- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 70 | //{ 71 | //} 72 | 73 | #pragma mark - Function 74 | 75 | /* 76 | * 泛洪填充算法 77 | * 泛洪算法常用的有四邻填充、八邻填充、基于扫描线填充法。这里用了基于扫描线填充 78 | */ 79 | - (UIImage *)floodFillFromPoint:(CGPoint)startPoint withColor:(UIColor *)newColor 80 | { 81 | CGPoint savePoint = startPoint; 82 | 83 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 84 | // 获取当前的image指针 85 | CGImageRef imageRef = [self.image CGImage]; 86 | 87 | NSUInteger imageWidth = CGImageGetWidth(imageRef); 88 | NSUInteger imageHeight = CGImageGetHeight(imageRef); 89 | // 实际大小跟屏幕的比例 90 | // CGFloat scaleNum = imageWidth / [UIScreen mainScreen].bounds.size.width; 91 | size_t w = startPoint.x * _scaleNum; 92 | size_t h = startPoint.y * _scaleNum; 93 | startPoint = CGPointMake(w, h); 94 | 95 | // 每个像素多少字节 96 | NSUInteger bytesPerPixel = CGImageGetBitsPerPixel(imageRef) / 8; 97 | NSUInteger bytesPerRow = CGImageGetBytesPerRow(imageRef); 98 | NSUInteger bitsPerComponent = CGImageGetBitsPerComponent(imageRef); 99 | 100 | // 创建保存图片数据的载体 101 | unsigned char *imageData = malloc(imageWidth * imageHeight * bytesPerPixel); 102 | // 存储空间所有位 都置0 103 | memset(imageData, 0, imageWidth * imageHeight * bytesPerPixel); 104 | 105 | CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef); 106 | if (kCGImageAlphaLast == (uint32_t)bitmapInfo || 107 | kCGImageAlphaFirst == (uint32_t)bitmapInfo) 108 | { 109 | bitmapInfo = (uint32_t)kCGImageAlphaPremultipliedLast; 110 | } 111 | // 开启绘图的上下文 112 | CGContextRef context = CGBitmapContextCreate(imageData, imageWidth, imageHeight, bitsPerComponent, bytesPerRow, colorSpace, bitmapInfo); 113 | // 关闭颜色空间 114 | CGColorSpaceRelease(colorSpace); 115 | // 绘制图片 116 | CGContextDrawImage(context, CGRectMake(0, 0, imageWidth, imageHeight), imageRef); 117 | 118 | /* 获取点击点的颜色 */ 119 | NSUInteger byteIndex = (bytesPerRow * roundf(startPoint.y)) + bytesPerPixel * roundf(startPoint.x); 120 | NSUInteger oldColorCode = getColorCode(byteIndex, imageData); 121 | // 如果是点击了边框 直接返回 122 | NSUInteger blackColor = getColorCodeFromUIColor([UIColor blackColor], bitmapInfo&kCGBitmapByteOrderMask); 123 | if (compareColor(oldColorCode, blackColor, 10)) { 124 | return nil; 125 | } 126 | // 如果新的颜色与本来旧的颜色一样 直接返回 127 | if (compareColor(oldColorCode, getColorCodeFromUIColor(newColor, bitmapInfo&kCGBitmapByteOrderMask), 0)) { 128 | return nil; 129 | } 130 | 131 | NSInteger newRed, newGreen, newBlue, newAlpha; 132 | NSUInteger newColorCode = getColorCodeFromUIColor(newColor, bitmapInfo&kCGBitmapByteOrderMask); 133 | 134 | newRed = ((0xff000000 & newColorCode) >> 24); 135 | newGreen = ((0x00ff0000 & newColorCode) >> 16); 136 | newBlue = ((0x0000ff00 & newColorCode) >> 8); 137 | newAlpha = (0x000000ff & newColorCode); 138 | 139 | /* 所有点的栈 */ 140 | LinkedListStack *points = [[LinkedListStack alloc] initWithCapacity:500 incrementSize:500 multiplier:imageHeight]; 141 | /* 反锯齿的栈 */ 142 | LinkedListStack *antiAliasingPoints = [[LinkedListStack alloc] initWithCapacity:500 incrementSize:500 multiplier:imageHeight]; 143 | 144 | NSInteger x = roundf(startPoint.x); 145 | NSInteger y = roundf(startPoint.y); 146 | /* 开始点先存进栈 */ 147 | [points pushFrontX:x andY:y]; 148 | 149 | NSUInteger color; 150 | /* 151 | * 空左边,空右边 152 | * 泛洪算法·(基于扫描线填充)的优化 153 | * 154 | */ 155 | BOOL spanLeft, spanRight; 156 | 157 | /* 栈里面每个node都要过一次循环 */ 158 | while ([points popFront:&x andY:&y] != Invalid_Node_Content) { 159 | 160 | byteIndex = (bytesPerRow * roundf(y)) + bytesPerPixel * roundf(x); 161 | color = getColorCode(byteIndex, imageData); 162 | 163 | /* 找到当前列的相同颜色最高点 */ 164 | while (y >= 0 && compareColor(oldColorCode, color, Tolerance)) { 165 | y--; 166 | 167 | if (y >= 0) { 168 | byteIndex = (bytesPerRow * roundf(y)) + bytesPerPixel * roundf(x); 169 | color = getColorCode(byteIndex, imageData); 170 | } 171 | } 172 | // 如果当前列的最高点有不同的颜色 173 | if (y >= 0 && !compareColor(oldColorCode, color, 0)) { 174 | // 加入反锯齿栈 175 | [antiAliasingPoints pushFrontX:x andY:y]; 176 | } 177 | // 上面多做了一次y--,所以这里补回来 178 | y++; 179 | 180 | spanLeft = spanRight = NO; 181 | byteIndex = (bytesPerRow * roundf(y)) + bytesPerPixel * roundf(x); 182 | color = getColorCode(byteIndex, imageData); 183 | 184 | /* (上面求出了当前列的相同颜色的最高点) 从最高点往下走,走完这一列,直至找到不同颜色的点 */ 185 | while (y < imageHeight && compareColor(oldColorCode, color, Tolerance) && newColorCode != color) { 186 | 187 | // 改变imageData里面的当前像素的颜色数值 188 | imageData[byteIndex + 0] = newRed; 189 | imageData[byteIndex + 1] = newGreen; 190 | imageData[byteIndex + 2] = newBlue; 191 | imageData[byteIndex + 3] = newAlpha; 192 | 193 | if (x > 0) { 194 | /* 当前点的左边点的颜色 */ 195 | byteIndex = (bytesPerRow * roundf(y)) + bytesPerPixel * roundf(x-1); 196 | color = getColorCode(byteIndex, imageData); 197 | 198 | if (!spanLeft && x > 0 && compareColor(oldColorCode, color, Tolerance)) { 199 | [points pushFrontX:(x - 1) andY:y]; 200 | /* 当前列的左边都不用检查了 */ 201 | spanLeft = YES; 202 | } else if (spanLeft && x > 0 && !compareColor(oldColorCode, color, Tolerance)) { 203 | /* 当前列往下走时,遇到一个左边是不同颜色的点 */ 204 | spanLeft = NO; 205 | } 206 | 207 | // 如果当前点的左边点是不同的颜色 208 | if (!spanLeft && x > 0 && !compareColor(oldColorCode, color, Tolerance) && !compareColor(newColorCode, color, Tolerance)) { 209 | // 加入反锯齿栈 210 | [antiAliasingPoints pushFrontX:(x - 1) andY:y]; 211 | } 212 | } 213 | 214 | if (x < imageWidth - 1) { 215 | /* 当前点的右边点的颜色 */ 216 | byteIndex = (bytesPerRow * roundf(y)) + bytesPerPixel * roundf(x+1); 217 | color = getColorCode(byteIndex, imageData); 218 | 219 | if (!spanRight && compareColor(oldColorCode, color, Tolerance)) { 220 | [points pushFrontX:(x + 1) andY:y]; 221 | /* 当前列的右边都不用检查了 */ 222 | spanRight = YES; 223 | } else if (spanRight && x > 0 && !compareColor(oldColorCode, color, Tolerance)) { 224 | /* 当前列往下走时,遇到一个右边是不同颜色的点 */ 225 | spanRight = NO; 226 | } 227 | 228 | // 如果当前点的右边点是不同的颜色 229 | if (!spanRight && !compareColor(oldColorCode, color, Tolerance) && !compareColor(newColorCode, color, Tolerance)) { 230 | // 加入反锯齿栈 231 | [antiAliasingPoints pushFrontX:(x + 1) andY:y]; 232 | } 233 | } 234 | 235 | // 往下走 236 | y++; 237 | 238 | if (y < imageHeight) { 239 | byteIndex = (bytesPerRow * roundf(y)) + roundf(x) * bytesPerPixel; 240 | color = getColorCode(byteIndex, imageData); 241 | } 242 | } 243 | 244 | /* 当前列最低点不同的话 加入反锯齿 */ 245 | if (y < imageHeight) { 246 | byteIndex = (bytesPerRow * roundf(y)) + roundf(x) * bytesPerPixel; 247 | color = getColorCode(byteIndex, imageData); 248 | if (!compareColor(oldColorCode, color, 0)) { 249 | [antiAliasingPoints pushFrontX:x andY:y]; 250 | } 251 | } 252 | 253 | }/* 栈里面每个node都循环完了 */ 254 | 255 | /* 256 | * 反锯齿部分 257 | * 遍历所有反锯齿栈里面的点 258 | */ 259 | while ([antiAliasingPoints popFront:&x andY:&y] != Invalid_Node_Content) { 260 | 261 | byteIndex = (bytesPerRow * roundf(y)) + bytesPerPixel * roundf(x); 262 | color = getColorCode(byteIndex, imageData); 263 | 264 | if (!compareColor(newColorCode, color, 0)) { 265 | 266 | NSInteger originalRed = ((0xff000000 & color) >> 24); 267 | NSInteger originalGreen = ((0x00ff0000 & color) >> 16); 268 | NSInteger originalBlue = ((0x0000ff00 & color) >> 8); 269 | NSInteger originalAlpha = ((0x000000ff & color)); 270 | 271 | if (Using_Antialiasing) { 272 | imageData[byteIndex + 0] = (newRed + originalRed) / 2; 273 | imageData[byteIndex + 1] = (newGreen + originalGreen) / 2; 274 | imageData[byteIndex + 2] = (newBlue + originalBlue) / 2; 275 | imageData[byteIndex + 3] = (newAlpha + originalAlpha) / 2; 276 | } else { 277 | imageData[byteIndex + 0] = originalRed; 278 | imageData[byteIndex + 1] = originalGreen; 279 | imageData[byteIndex + 2] = originalBlue; 280 | imageData[byteIndex + 3] = originalAlpha; 281 | } 282 | } 283 | 284 | //左边的点 285 | if (x > 0) { 286 | byteIndex = (bytesPerRow * roundf(y)) + bytesPerPixel * roundf(x-1); 287 | color = getColorCode(byteIndex, imageData); 288 | 289 | if (!compareColor(newColorCode, color, 0)) { 290 | NSInteger originalRed = ((0xff000000 & color) >> 24); 291 | NSInteger originalGreen = ((0x00ff0000 & color) >> 16); 292 | NSInteger originalBlue = ((0x0000ff00 & color) >> 8); 293 | NSInteger originalAlpha = ((0x000000ff & color)); 294 | 295 | if (Using_Antialiasing) { 296 | imageData[byteIndex + 0] = (newRed + originalRed) / 2; 297 | imageData[byteIndex + 1] = (newGreen + originalGreen) / 2; 298 | imageData[byteIndex + 2] = (newBlue + originalBlue) / 2; 299 | imageData[byteIndex + 3] = (newAlpha + originalAlpha) / 2; 300 | } else { 301 | imageData[byteIndex + 0] = originalRed; 302 | imageData[byteIndex + 1] = originalGreen; 303 | imageData[byteIndex + 2] = originalBlue; 304 | imageData[byteIndex + 3] = originalAlpha; 305 | } 306 | } 307 | } 308 | 309 | // 右边的点 310 | if (x < imageWidth - 1) { 311 | byteIndex = (bytesPerRow * roundf(y)) + bytesPerPixel * roundf(x + 1); 312 | color = getColorCode(byteIndex, imageData); 313 | 314 | if (!compareColor(newColorCode, color, 0)) { 315 | NSInteger originalRed = ((0xff000000 & color) >> 24); 316 | NSInteger originalGreen = ((0x00ff0000 & color) >> 16); 317 | NSInteger originalBlue = ((0x0000ff00 & color) >> 8); 318 | NSInteger originalAlpha = ((0x000000ff & color)); 319 | 320 | if (Using_Antialiasing) { 321 | imageData[byteIndex + 0] = (newRed + originalRed) / 2; 322 | imageData[byteIndex + 1] = (newGreen + originalGreen) / 2; 323 | imageData[byteIndex + 2] = (newBlue + originalBlue) / 2; 324 | imageData[byteIndex + 3] = (newAlpha + originalAlpha) / 2; 325 | } else { 326 | imageData[byteIndex + 0] = originalRed; 327 | imageData[byteIndex + 1] = originalGreen; 328 | imageData[byteIndex + 2] = originalBlue; 329 | imageData[byteIndex + 3] = originalAlpha; 330 | } 331 | } 332 | } 333 | 334 | // 上面的点 335 | if (y > 0) { 336 | byteIndex = (bytesPerRow * roundf(y - 1)) + bytesPerPixel * roundf(x); 337 | color = getColorCode(byteIndex, imageData); 338 | 339 | if (!compareColor(newColorCode, color, 0)) { 340 | NSInteger originalRed = ((0xff000000 & color) >> 24); 341 | NSInteger originalGreen = ((0x00ff0000 & color) >> 16); 342 | NSInteger originalBlue = ((0x0000ff00 & color) >> 8); 343 | NSInteger originalAlpha = ((0x000000ff & color)); 344 | 345 | if (Using_Antialiasing) { 346 | imageData[byteIndex + 0] = (newRed + originalRed) / 2; 347 | imageData[byteIndex + 1] = (newGreen + originalGreen) / 2; 348 | imageData[byteIndex + 2] = (newBlue + originalBlue) / 2; 349 | imageData[byteIndex + 3] = (newAlpha + originalAlpha) / 2; 350 | } else { 351 | imageData[byteIndex + 0] = originalRed; 352 | imageData[byteIndex + 1] = originalGreen; 353 | imageData[byteIndex + 2] = originalBlue; 354 | imageData[byteIndex + 3] = originalAlpha; 355 | } 356 | } 357 | } 358 | 359 | // 下面的点 360 | if (y < imageHeight) { 361 | byteIndex = (bytesPerRow * roundf(y + 1)) + bytesPerPixel * roundf(x); 362 | color = getColorCode(byteIndex, imageData); 363 | 364 | if (!compareColor(newColorCode, color, 0)) { 365 | NSInteger originalRed = ((0xff000000 & color) >> 24); 366 | NSInteger originalGreen = ((0x00ff0000 & color) >> 16); 367 | NSInteger originalBlue = ((0x0000ff00 & color) >> 8); 368 | NSInteger originalAlpha = ((0x000000ff & color)); 369 | 370 | if (Using_Antialiasing) { 371 | imageData[byteIndex + 0] = (newRed + originalRed) / 2; 372 | imageData[byteIndex + 1] = (newGreen + originalGreen) / 2; 373 | imageData[byteIndex + 2] = (newBlue + originalBlue) / 2; 374 | imageData[byteIndex + 3] = (newAlpha + originalAlpha) / 2; 375 | } else { 376 | imageData[byteIndex + 0] = originalRed; 377 | imageData[byteIndex + 1] = originalGreen; 378 | imageData[byteIndex + 2] = originalBlue; 379 | imageData[byteIndex + 3] = originalAlpha; 380 | } 381 | } 382 | } 383 | 384 | }/* 反锯齿栈里面每个node都循环完了 */ 385 | 386 | CGImageRef newCGImage = CGBitmapContextCreateImage(context); 387 | UIImage *result = [UIImage imageWithCGImage:newCGImage scale:[self.image scale] orientation:UIImageOrientationUp]; 388 | 389 | CGImageRelease(newCGImage); 390 | CGContextRelease(context); 391 | free(imageData); 392 | 393 | if ([self.revokePointsArray containsObject:@(savePoint)]) { 394 | [self.revokePointsArray removeObject:@(savePoint)]; 395 | } else { 396 | /* 存储已画的点 */ 397 | [self.revokePointsArray addObject:@(savePoint)]; 398 | } 399 | 400 | return result; 401 | } 402 | 403 | #pragma mark - Privated Method 404 | 405 | /* 颜色的数值 eg:0xffaabbcc 406 | * 转为连续的数 407 | */ 408 | NSUInteger getColorCode (NSUInteger byteIndex, unsigned char *imageData) { 409 | 410 | NSUInteger red = imageData[byteIndex]; 411 | NSUInteger green = imageData[byteIndex + 1]; 412 | NSUInteger blue = imageData[byteIndex + 2]; 413 | NSUInteger alpha = imageData[byteIndex + 3]; 414 | 415 | return (red << 24) | (green << 16) | (blue << 8) | alpha; 416 | } 417 | 418 | /* 419 | * UIColor 转为颜色的数值 420 | */ 421 | NSUInteger getColorCodeFromUIColor (UIColor *color, CGBitmapInfo orderMask) { 422 | NSInteger newRed, newGreen, newBlue, newAlpha; 423 | const CGFloat *components = CGColorGetComponents(color.CGColor); 424 | 425 | if (CGColorGetNumberOfComponents(color.CGColor) == 2) { 426 | // 只有黑白灰的种类 427 | /* (这里*255,eg: 0.1*255=>0x19) */ 428 | newRed = newGreen = newBlue = components[0] * 255; 429 | newAlpha = components[1] * 255; 430 | } else if (CGColorGetNumberOfComponents(color.CGColor) == 4) { 431 | // RGBA彩色种类 432 | /* 小端 */ 433 | if (orderMask == kCGBitmapByteOrder32Little) { 434 | newRed = components[2] * 255; 435 | newGreen = components[1] * 255; 436 | newBlue = components[0] * 255; 437 | newAlpha = 255; 438 | } else { 439 | newRed = components[0] * 255; 440 | newGreen = components[1] * 255; 441 | newBlue = components[2] * 255; 442 | newAlpha = 255; 443 | } 444 | } else { 445 | newRed = newGreen = newBlue = 0; 446 | newAlpha = 255; 447 | } 448 | 449 | NSUInteger newColor = (newRed << 24) | (newGreen << 16) | (newBlue << 8) | newAlpha; 450 | return newColor; 451 | } 452 | 453 | /* 对比颜色 454 | * 容差之内返回true,相差太大返回false 455 | */ 456 | bool compareColor (NSUInteger colorA, NSUInteger colorB, NSInteger tolorance) { 457 | if (colorA == colorB) { 458 | return true; 459 | } 460 | 461 | NSInteger redA = ((0xff000000 & colorA) >> 24); 462 | NSInteger greenA = ((0x00ff0000 & colorA) >> 16); 463 | NSInteger blueA = ((0x0000ff00 & colorA) >> 8); 464 | NSInteger alphaA = (0x000000ff & colorA); 465 | 466 | NSInteger redB = ((0xff000000 & colorB) >> 24); 467 | NSInteger greenB = ((0x00ff0000 & colorB) >> 16); 468 | NSInteger blueB = ((0x0000ff00 & colorB) >> 8); 469 | NSInteger alphaB = (0x000000ff & colorB); 470 | 471 | // labs()绝对值 472 | NSInteger distanceRed = labs(redB - redA); 473 | NSInteger distanceGreen = labs(greenB - greenA); 474 | NSInteger distanceBlue = labs(blueB - blueA); 475 | NSInteger distanceAlpha = labs(alphaB - alphaA); 476 | 477 | if (distanceRed > tolorance || distanceGreen > tolorance || distanceBlue > tolorance || distanceAlpha > tolorance) { 478 | return false; 479 | } 480 | 481 | return true; 482 | } 483 | 484 | @end 485 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 铁 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LinkedListStack.h: -------------------------------------------------------------------------------- 1 | // 2 | // LinkedListStack.h 3 | // iFill 4 | // 5 | // Created by iRon_iMac on 2018/7/10. 6 | // Copyright © 2018年 iRon_. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #define Final_Node_Offset -1 12 | #define Invalid_Node_Content INT_MIN 13 | 14 | typedef struct PointNode { 15 | NSInteger nextNodeOffset; 16 | NSInteger point; 17 | } PointNode; 18 | 19 | @interface LinkedListStack : NSObject 20 | 21 | - (id)initWithCapacity:(NSInteger)capacity incrementSize:(NSInteger)increment multiplier:(NSInteger)mul; 22 | - (id)initWithCapacity:(NSInteger)capacity; 23 | 24 | - (void)pushFrontX:(NSInteger)x andY:(NSInteger)y; 25 | - (NSInteger)popFront:(NSInteger *)x andY:(NSInteger *)y; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /LinkedListStack.m: -------------------------------------------------------------------------------- 1 | // 2 | // LinkedListStack.m 3 | // iFill 4 | // 5 | // Created by iRon_iMac on 2018/7/10. 6 | // Copyright © 2018年 iRon_. All rights reserved. 7 | // 8 | 9 | #import "LinkedListStack.h" 10 | 11 | @interface LinkedListStack () 12 | { 13 | /* 用NSData储存所有的node数据 */ 14 | NSMutableData *nodesCache; 15 | 16 | NSInteger freeNodeOffset; 17 | NSInteger topNodeOffset; 18 | NSInteger _cacheSizeIncrements; 19 | 20 | NSInteger multiplier; 21 | } 22 | 23 | @end 24 | 25 | @implementation LinkedListStack 26 | 27 | #pragma mark - System Method 28 | 29 | - (id)init 30 | { 31 | return [self initWithCapacity:500]; 32 | } 33 | 34 | #pragma mark - Public Method 35 | 36 | - (id)initWithCapacity:(NSInteger)capacity 37 | { 38 | return [self initWithCapacity:capacity incrementSize:500 multiplier:1000]; 39 | } 40 | 41 | - (id)initWithCapacity:(NSInteger)capacity incrementSize:(NSInteger)increment multiplier:(NSInteger)mul 42 | { 43 | self = [super init]; 44 | if (self) { 45 | _cacheSizeIncrements = increment; 46 | NSInteger bytesRequired = capacity * sizeof(PointNode); 47 | nodesCache = [[NSMutableData alloc] initWithLength:bytesRequired]; 48 | 49 | /* 从0开始初始化 */ 50 | [self initNodesAtOffset:0 count:capacity]; 51 | 52 | freeNodeOffset = 0; 53 | topNodeOffset = Final_Node_Offset; 54 | 55 | multiplier = mul; 56 | } 57 | return self; 58 | } 59 | 60 | - (void)pushFrontX:(NSInteger)x andY:(NSInteger)y 61 | { 62 | // x乘以一个数,便于储存 x = p / mul; y = p % mul; 63 | NSInteger p = multiplier * x + y; 64 | PointNode *node = [self getNextFreeNode]; 65 | node -> point = p; 66 | node -> nextNodeOffset = topNodeOffset; 67 | 68 | topNodeOffset = [self offsetOfNode:node]; 69 | } 70 | 71 | - (NSInteger)popFront:(NSInteger *)x andY:(NSInteger *)y 72 | { 73 | if (topNodeOffset == Final_Node_Offset) { 74 | return Invalid_Node_Content; 75 | } 76 | PointNode *node = [self nodeAtOffset:topNodeOffset]; 77 | NSInteger thisNodeOffset = topNodeOffset; 78 | 79 | // Remove this node from the queue 80 | topNodeOffset = node -> nextNodeOffset; 81 | NSInteger value = node -> point; 82 | 83 | node -> point = 0; 84 | node -> nextNodeOffset = freeNodeOffset; 85 | 86 | freeNodeOffset = thisNodeOffset; 87 | 88 | *x = value / multiplier; 89 | *y = value % multiplier; 90 | 91 | return value; 92 | } 93 | 94 | #pragma mark - Privated Method 95 | 96 | /* 从某个点开始初始化 */ 97 | - (void)initNodesAtOffset:(NSInteger)offset count:(NSInteger)count 98 | { 99 | PointNode *node = (PointNode *)nodesCache.mutableBytes + offset; 100 | for (int i = 0; i < count - 1; i++) { 101 | node->point = 0; 102 | node->nextNodeOffset = offset + i + 1; 103 | // 指针++ 因为NSData的指针是顺序递增的。 104 | node++; 105 | } 106 | 107 | /* 最后一个node指向final */ 108 | node->point = 0; 109 | node->nextNodeOffset = Final_Node_Offset; 110 | } 111 | 112 | - (PointNode *)getNextFreeNode 113 | { 114 | if (freeNodeOffset < 0) { 115 | NSInteger currentSize = nodesCache.length / sizeof(PointNode); 116 | /* 增加一部分长度 */ 117 | [nodesCache increaseLengthBy:_cacheSizeIncrements * sizeof(PointNode)]; 118 | 119 | [self initNodesAtOffset:currentSize count:_cacheSizeIncrements]; 120 | freeNodeOffset = currentSize; 121 | } 122 | 123 | // nodesCache.mutableBytes 所有数据的初始点 124 | PointNode *node = (PointNode *)nodesCache.mutableBytes + freeNodeOffset; 125 | freeNodeOffset = node->nextNodeOffset; 126 | 127 | return node; 128 | } 129 | 130 | - (NSInteger)offsetOfNode:(PointNode *)node 131 | { 132 | // nodesCache.mutableBytes 所有数据的初始点 133 | return node - (PointNode *)nodesCache.mutableBytes; 134 | } 135 | 136 | - (PointNode *)nodeAtOffset:(NSInteger)offset 137 | { 138 | return (PointNode *)nodesCache.mutableBytes + offset; 139 | } 140 | 141 | @end 142 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # iFill 2 | FloodFill Image 涂色书 3 | 使用泛洪算法实现的涂色书App。 4 | 5 | 相关博客: 6 | https://www.jianshu.com/p/b1b1fc033fd2 7 | -------------------------------------------------------------------------------- /ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // iFill 4 | // 5 | // Created by iRon_iMac on 2018/7/9. 6 | // Copyright © 2018年 iRon_. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // iFill 4 | // 5 | // Created by iRon_iMac on 2018/7/9. 6 | // Copyright © 2018年 iRon_. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "FillImageView.h" 11 | 12 | @interface ViewController () 13 | 14 | @property (nonatomic, strong) UIScrollView *scrollView; 15 | @property (nonatomic, strong) FillImageView *imageView; 16 | 17 | @end 18 | 19 | @implementation ViewController 20 | 21 | - (void)viewDidLoad { 22 | [super viewDidLoad]; 23 | 24 | [self setupUI]; 25 | } 26 | 27 | - (void)setupUI 28 | { 29 | CGFloat screenW = [UIScreen mainScreen].bounds.size.width; 30 | _scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 64, screenW, screenW)]; 31 | _scrollView.delegate = self; 32 | [self.view addSubview:self.scrollView]; 33 | _imageView = [[FillImageView alloc] initWithFrame:_scrollView.bounds]; 34 | self.imageView.image = [UIImage imageNamed:@"2.jpg"]; 35 | self.imageView.scaleNum = _imageView.image.size.width / [UIScreen mainScreen].bounds.size.width; 36 | self.imageView.newcolor = [UIColor redColor]; 37 | [self.scrollView addSubview:self.imageView]; 38 | 39 | self.scrollView.contentSize = _imageView.frame.size; 40 | self.scrollView.minimumZoomScale = 1; 41 | self.scrollView.maximumZoomScale = 5; 42 | self.scrollView.userInteractionEnabled = YES; 43 | 44 | UIButton *revokeBtn = [[UIButton alloc] initWithFrame:CGRectMake(20, CGRectGetMaxY(_scrollView.frame)+20, 200, 40)]; 45 | [revokeBtn addTarget:self action:@selector(revokeAction) forControlEvents:UIControlEventTouchUpInside]; 46 | [revokeBtn setTitle:@"撤销" forState:UIControlStateNormal]; 47 | [revokeBtn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; 48 | [self.view addSubview:revokeBtn]; 49 | } 50 | 51 | - (void)revokeAction 52 | { 53 | [self.imageView revokeOption]; 54 | } 55 | 56 | /* 实现这个协议方法才可以缩放相应的view */ 57 | - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView 58 | { 59 | return _imageView; 60 | } 61 | 62 | - (void)didReceiveMemoryWarning { 63 | [super didReceiveMemoryWarning]; 64 | // Dispose of any resources that can be recreated. 65 | } 66 | 67 | 68 | @end 69 | --------------------------------------------------------------------------------