Note that there are some explanatory texts on larger screens.

plurals
  1. POFlood Filling in iPad too SLOW
    primarykey
    data
    text
    <p>I am using Flood Filling for one of my Coloring app on iPad.</p> <p>The app basically fills color within the black line of the image and i am able to do this with no problem , but its too slow.</p> <p>Conclusion:</p> <p>I Finally managed to make an acceptable Floodfill for iPhone/iPad with following Class</p> <p>Please excuse if any zombie code :), suggestion for improvement always welcome</p> <p><strong>* How to use *</strong> Create the CanvasView Object and set the originalImage (This should be the uncolored/colored image with black lines and the area outside the black lines must be clear color) and you can access the coloredImage for the image after paint. </p> <p>The .h file</p> <pre><code>#import &lt;UIKit/UIKit.h&gt; #import "SoundEngine.h" struct COLOR { unsigned char red; unsigned char green; unsigned char blue; unsigned char alpha; }; typedef struct COLOR COLOR; @interface CanvasView : UIView { UIImage *originalImage; UIImage *coloredImage; int selectedColor; BOOL shouldShowOriginalImage; UIImageView *imageView; BOOL isImageDataFreed; unsigned char red; unsigned char green; unsigned char blue; unsigned char alpha1; int loopCounter; NSMutableArray *pixelDataArray; BOOL isFilling; BOOL hasColored; id delegate; CGPoint restartPoint; BOOL fillAtPoint; CGPoint currentTouchPoint; int regionCount; NSTimer *floadFillTimer; } @property (nonatomic, retain) UIImage *coloredImage; @property (nonatomic, retain) UIImage *originalImage; @property (nonatomic, retain) NSMutableArray *pixelDataArray; @property (nonatomic, assign) id delegate; @property (nonatomic, assign) BOOL shouldShowOriginalImage; @property (nonatomic, assign) BOOL hasColored; @property (assign) int regionCount; -(void)toggleImage; -(void)setColor:(int)colorIndex; -(void)freeImageData; -(CGColorRef)getColorForIndex:(int)index; -(void)initializeCanvas; -(void)prepareImageData; -(void)setColorForColoring; @end </code></pre> <p>and the .m file</p> <pre><code>#import "CanvasView.h" #import "PaintGame.h" #import "Color.h" #import "FarvespilletAppDelegate.h" #import "Stack.h" #import "Point.h" @implementation CanvasView @synthesize coloredImage,originalImage,pixelDataArray,delegate,shouldShowOriginalImage,hasColored; @synthesize regionCount; unsigned char *imageRawData; -(COLOR)getPixelColorAtIndex:(CGPoint)atPoint { COLOR aColor; aColor.red = 0; aColor.green = 0; aColor.blue = 0; aColor.alpha = 0; NSUInteger width = self.frame.size.width; NSUInteger height = self.frame.size.height; NSUInteger bytesPerRow = 4 * width; long int byteIndex = (bytesPerRow * ((NSUInteger)atPoint.y-1)) + (NSUInteger)atPoint.x*4; if((height * width * 4)&lt;=byteIndex) return aColor; @try { aColor.red = imageRawData[byteIndex]; aColor.green = imageRawData[byteIndex+1]; aColor.blue = imageRawData[byteIndex+2]; aColor.alpha = imageRawData[byteIndex+3]; } @catch (NSException * e) { NSLog(@"%@",e); } @finally { } return aColor; } -(void)setPixelColorAtPoint:(CGPoint)atPoint color:(COLOR)acolor { NSUInteger width = self.frame.size.width; NSUInteger height = self.frame.size.height; NSUInteger bytesPerRow = 4 * width; long int byteIndex = (bytesPerRow * ((NSUInteger)atPoint.y-1)) + (NSUInteger)atPoint.x*4; if((height * width * 4)&lt;=byteIndex) return; @try { imageRawData[byteIndex] = acolor.red; imageRawData[byteIndex+1] = acolor.green; imageRawData[byteIndex+2] = acolor.blue; imageRawData[byteIndex+3] = acolor.alpha; } @catch (NSException * e) { NSLog(@"%@",e); } @finally { } } -(void)initializeCanvas { imageView = [[UIImageView alloc] initWithFrame:self.bounds]; [self addSubview:imageView]; self.backgroundColor = [UIColor clearColor]; [imageView release]; isImageDataFreed = YES; isFilling = NO; } - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { // Initialization code. [self initializeCanvas]; } return self; } // Only override drawRect: if you perform custom drawing. // An empty implementation adversely affects performance during animation. - (void)drawRect:(CGRect)rect { // Drawing code. if(!imageRawData) [self prepareImageData]; if(shouldShowOriginalImage) imageView.image = originalImage; else imageView.image = coloredImage; } - (void)dealloc { [super dealloc]; [originalImage release]; [coloredImage release]; [pixelDataArray release]; } -(BOOL)isColorStandardForRed:(unsigned char)__red green:(unsigned char)__green blue:(unsigned char)__blue { if(0 == __red &amp;&amp; 0 == __green &amp;&amp; 0 == __blue) return NO; else return YES; } -(BOOL)checkForValidRegionAtPoint:(CGPoint)touchPoint { COLOR colorAtPoint = [self getPixelColorAtIndex:touchPoint]; loopCounter++; unsigned char _red = colorAtPoint.red; unsigned char _green = colorAtPoint.green; unsigned char _blue = colorAtPoint.blue; unsigned char _alpha1 = colorAtPoint.alpha; if(touchPoint.x &lt;= 0 || touchPoint.y &lt;= 0 || touchPoint.x &gt;= self.frame.size.width ||touchPoint.y &gt;= self.frame.size.height) return NO; if(red == _red &amp;&amp; green == _green &amp;&amp; blue == _blue &amp;&amp; alpha1 == _alpha1) return NO; if(_alpha1 &lt;= 225) return NO; if(_red &lt;= 50 &amp;&amp; _green &lt;= 50 &amp;&amp; _blue &lt;= 50 &amp;&amp; _alpha1 == 255) return NO; if(!([self isColorStandardForRed:_red green:_green blue:_blue])) return NO; return YES; } -(void)setColorAtPoint:(CGPoint)atPoint { //loopCounter++; COLOR aColor; aColor.red = red; aColor.green = green; aColor.blue = blue; aColor.alpha = alpha1; [self setPixelColorAtPoint:atPoint color:aColor]; } -(void)prepareImageData { if(!imageRawData) { CGImageRef imageRef = [coloredImage CGImage]; NSUInteger bytesPerPixel = 4; NSUInteger width = self.frame.size.width; NSUInteger height = self.frame.size.height;//CGImageGetHeight(imageRef); CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); NSUInteger bytesPerRow = bytesPerPixel * width; NSUInteger bitsPerComponent = 8; imageRawData = malloc(height * width * 4); CGContextRef context = CGBitmapContextCreate(imageRawData, width, height, bitsPerComponent, bytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast); CGColorSpaceRelease(colorSpace); CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef); CGContextRelease(context); } } -(void)cleanUpImageData { NSUInteger bytesPerPixel = 4; NSUInteger width = self.frame.size.width; CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); NSUInteger bytesPerRow = bytesPerPixel * width; CGContextRef ctx = CGBitmapContextCreate(imageRawData, self.frame.size.width, self.frame.size.height, 8, bytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast); CGImageRef newImageRef = CGBitmapContextCreateImage(ctx); CGContextRelease(ctx); self.coloredImage = [UIImage imageWithCGImage:newImageRef]; CGImageRelease(newImageRef); } -(void)refreshImage { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; [self cleanUpImageData]; imageView.image = coloredImage; [pool release]; } typedef struct queue_ { struct queue_ *next; } queue_t; typedef struct ffnode_ { queue_t node; int x, y; } ffnode_t; /* returns the new head of the queue after adding node to the queue */ queue_t* enqueue(queue_t *queue, queue_t *node) { if (node) { if(!queue) return node; queue_t *temp = queue; while(temp-&gt;next) temp = temp-&gt;next; temp-&gt;next = node; // node-&gt;next = queue; return queue; } return NULL; } /* returns the head of the queue and modifies queue to be the new head */ queue_t* dequeue(queue_t **queue) { if (queue) { queue_t *node = (*queue); if(node) { (*queue) = node-&gt;next; node-&gt;next = NULL; return node; } } return NULL; } ffnode_t* new_ffnode(int x, int y) { ffnode_t *node = (ffnode_t*)malloc(sizeof(ffnode_t)); node-&gt;x = x; node-&gt;y = y; node-&gt;node.next = NULL; return node; } -(void)floodFillAtPoint:(CGPoint)atPoint shouldRefresh:(BOOL)refresh { queue_t *head = NULL; ffnode_t *node = NULL; node = new_ffnode(atPoint.x, atPoint.y); head = enqueue(head, &amp;node-&gt;node); long int counter = 0; [self setColorAtPoint:atPoint]; while((node = (ffnode_t*)dequeue(&amp;head))) { counter++; CGPoint aPoint = CGPointMake(node-&gt;x, node-&gt;y); free(node); CGPoint bPoint = aPoint; bPoint.x+=1; if([self checkForValidRegionAtPoint:bPoint]) { ffnode_t *node1 = new_ffnode(bPoint.x, bPoint.y); head = enqueue(head, &amp;node1-&gt;node); [self setColorAtPoint:bPoint]; } bPoint = aPoint; bPoint.x-=1; if([self checkForValidRegionAtPoint:bPoint]) { ffnode_t *node1 = new_ffnode(bPoint.x, bPoint.y); head = enqueue(head, &amp;node1-&gt;node); [self setColorAtPoint:bPoint]; } bPoint = aPoint; bPoint.y+=1; if([self checkForValidRegionAtPoint:bPoint]) { ffnode_t *node1 = new_ffnode(bPoint.x, bPoint.y); head = enqueue(head, &amp;node1-&gt;node); [self setColorAtPoint:bPoint]; } bPoint = aPoint; bPoint.y-=1; if([self checkForValidRegionAtPoint:bPoint]) { ffnode_t *node1 = new_ffnode(bPoint.x, bPoint.y); head = enqueue(head, &amp;node1-&gt;node); [self setColorAtPoint:bPoint]; } } if(refresh) [self performSelectorOnMainThread:@selector(shouldRefresh) withObject:nil waitUntilDone:YES]; } -(void)shouldRefresh { self.regionCount += 1; //To detect if all the region/thread are completed; if YES then notify the delegate if(regionCount==9) { [floadFillTimer invalidate]; isFilling = NO; [delegate fillingStateChanged:isFilling]; } [self cleanUpImageData]; imageView.image = coloredImage; } -(void)floodFillInBackGroundAtPoint:(StackPoint*)point { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; [self floodFillAtPoint:point.point shouldRefresh:YES]; [pool release]; } -(void)floodFillInBackGroundAtPointWithRefresh:(StackPoint*)point { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; [self floodFillAtPoint:point.point shouldRefresh:YES]; [pool release]; } -(void)initiateFloodFillAtPoint:(CGPoint)touchPoint { loopCounter = 0; isFilling = YES; [delegate fillingStateChanged:isFilling]; hasColored = YES; regionCount = 0; floadFillTimer = [NSTimer timerWithTimeInterval:0.1 target:self selector:@selector(refreshImage) userInfo:nil repeats:YES]; [[NSRunLoop mainRunLoop] addTimer:floadFillTimer forMode:NSDefaultRunLoopMode]; StackPoint *stackPoint = [StackPoint pointWithPoint:touchPoint]; CGPoint floodPoint = touchPoint; while ([self checkForValidRegionAtPoint:floodPoint]) { floodPoint.x++; } floodPoint.x--; StackPoint *stackPoint1 = [StackPoint pointWithPoint:floodPoint]; floodPoint = touchPoint; while ([self checkForValidRegionAtPoint:floodPoint]) { floodPoint.x--; } floodPoint.x++; StackPoint * stackPoint2 = [StackPoint pointWithPoint:floodPoint]; floodPoint = touchPoint; while ([self checkForValidRegionAtPoint:floodPoint]) { floodPoint.y++; } floodPoint.y--; StackPoint *stackPoint3 = [StackPoint pointWithPoint:floodPoint]; floodPoint = touchPoint; while ([self checkForValidRegionAtPoint:floodPoint]) { floodPoint.y++; } floodPoint.y--; StackPoint *stackPoint4 = [StackPoint pointWithPoint:floodPoint]; floodPoint = touchPoint; while ([self checkForValidRegionAtPoint:floodPoint]) { floodPoint.x++; floodPoint.y++; } floodPoint.x--; floodPoint.y--; StackPoint *stackPoint5 = [StackPoint pointWithPoint:floodPoint]; floodPoint = touchPoint; while ([self checkForValidRegionAtPoint:floodPoint]) { floodPoint.x--; floodPoint.y--; } floodPoint.x++; floodPoint.y++; StackPoint * stackPoint6 = [StackPoint pointWithPoint:floodPoint]; floodPoint = touchPoint; while ([self checkForValidRegionAtPoint:floodPoint]) { floodPoint.x++; floodPoint.y--; } floodPoint.x--; floodPoint.y++; StackPoint *stackPoint7 = [StackPoint pointWithPoint:floodPoint]; floodPoint = touchPoint; while ([self checkForValidRegionAtPoint:floodPoint]) { floodPoint.x--; floodPoint.y++; } floodPoint.x++; floodPoint.y--; StackPoint *stackPoint8 = [StackPoint pointWithPoint:floodPoint]; [self performSelectorInBackground:@selector(floodFillInBackGroundAtPoint:) withObject:stackPoint]; [self performSelectorInBackground:@selector(floodFillInBackGroundAtPoint:) withObject:stackPoint]; [self performSelectorInBackground:@selector(floodFillInBackGroundAtPoint:) withObject:stackPoint1]; [self performSelectorInBackground:@selector(floodFillInBackGroundAtPoint:) withObject:stackPoint2]; [self performSelectorInBackground:@selector(floodFillInBackGroundAtPoint:) withObject:stackPoint3]; [self performSelectorInBackground:@selector(floodFillInBackGroundAtPoint:) withObject:stackPoint4]; [self performSelectorInBackground:@selector(floodFillInBackGroundAtPoint:) withObject:stackPoint5]; [self performSelectorInBackground:@selector(floodFillInBackGroundAtPoint:) withObject:stackPoint6]; [self performSelectorInBackground:@selector(floodFillInBackGroundAtPoint:) withObject:stackPoint7]; [self performSelectorInBackground:@selector(floodFillInBackGroundAtPoint:) withObject:stackPoint8]; } -(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { UITouch *aTouch = [touches anyObject]; CGPoint aPoint = [aTouch locationInView:imageView]; [self setColorForColoring]; //This will toggle from uncolored state to colored state, also resets the colored image if(shouldShowOriginalImage) { self.coloredImage = originalImage; shouldShowOriginalImage = !shouldShowOriginalImage; [self freeImageData]; [self prepareImageData]; [delegate coloringStarted]; } if([self checkForValidRegionAtPoint:aPoint] &amp;&amp; !isFilling) { [self setColorForColoring]; self.userInteractionEnabled = NO; [self initiateFloodFillAtPoint:aPoint]; self.userInteractionEnabled = YES; NSString *fileName = [NSString stringWithFormat:@"splat%d",(rand()%10)+1]; [[SoundEngine sharedSoundEngine] playSoundWithFileName:fileName delegate:nil]; } } -(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { UITouch *aTouch = [touches anyObject]; CGPoint aPoint = [aTouch locationInView:self]; [self setColorForColoring]; //This will toggle from uncolored state to colored state, also resets the colored image if(shouldShowOriginalImage) { self.coloredImage = originalImage; shouldShowOriginalImage = !shouldShowOriginalImage; [self freeImageData]; [self prepareImageData]; [delegate coloringStarted]; } if([self checkForValidRegionAtPoint:aPoint] &amp;&amp; !isFilling) { [self setColorForColoring]; self.userInteractionEnabled = NO; [self initiateFloodFillAtPoint:aPoint]; self.userInteractionEnabled = YES; NSString *fileName = [NSString stringWithFormat:@"splat%d",(rand()%10)+1]; [[SoundEngine sharedSoundEngine] playSoundWithFileName:fileName delegate:nil]; // [self cleanUpImageData]; // [self setNeedsDisplay]; } } -(void)toggleImage { shouldShowOriginalImage = !shouldShowOriginalImage; [self setNeedsDisplay]; } -(void)setColorForColoring { const CGFloat *colorComponents = CGColorGetComponents([self getColorForIndex:selectedColor]); red = (unsigned char)(colorComponents[0]*255); green = (unsigned char)(colorComponents[1]*255); blue = (unsigned char)(colorComponents[2]*255); alpha1 = (unsigned char)(CGColorGetAlpha([self getColorForIndex:selectedColor])*255); } -(void)setColor:(int)colorIndex { selectedColor = colorIndex; //const CGFloat *colorComponents = CGColorGetComponents([self getColorForIndex:selectedColor]); // red = (unsigned char)(colorComponents[0]*255); // green = (unsigned char)(colorComponents[1]*255); // blue = (unsigned char)(colorComponents[2]*255); // alpha1 = (unsigned char)(CGColorGetAlpha([self getColorForIndex:selectedColor])*255); } -(void)cleanBuffer { if(imageRawData) { int width = self.frame.size.width; int height = self.frame.size.height; int byteIndex = 0; for (int ii = 0 ; ii &lt; (width*height*4) ; ++ii) { imageRawData[byteIndex] = 0; imageRawData[byteIndex + 1] = 0; imageRawData[byteIndex + 2] = 0; imageRawData[byteIndex + 3] = 255; } } } -(void)freeImageData { self.pixelDataArray = nil; free(imageRawData); imageRawData = NULL; } -(CGColorRef)getColorForIndex:(int)index { switch (index) { case 1: return [[UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:1.0f] CGColor]; case 2: return [[UIColor colorWithRed:0.200f green:0.200f blue:0.200f alpha:1.0f] CGColor]; case 3: return [[UIColor redColor] CGColor]; case 4: return [[UIColor colorWithRed:0.062f green:0.658f blue:0.062f alpha:1.0f] CGColor]; case 5: return [[UIColor blueColor] CGColor]; case 6: return [[UIColor yellowColor] CGColor]; case 7: return [[UIColor orangeColor] CGColor]; case 8: return [[UIColor brownColor] CGColor]; case 9: return [[UIColor colorWithRed:0.7f green:0.7f blue:0.7f alpha:1.0f] CGColor]; case 10: return [[UIColor purpleColor] CGColor]; default: break; } return [[UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:1.0f] CGColor]; } @end </code></pre> <p>Additional Info:</p> <p>StackPoint.h</p> <pre><code> @interface StackPoint : NSObject { CGPoint point; } @property (nonatomic , assign) CGPoint point; +(StackPoint*)pointWithPoint:(CGPoint)_point; @end </code></pre> <p>StackPoint.m</p> <pre><code> @implementation StackPoint @synthesize point; +(StackPoint*)pointWithPoint:(CGPoint)_point { StackPoint *__point = [[StackPoint alloc] init]; __point.point = _point; return [__point autorelease]; } -(id)init { self = [super init]; if(self) { point = CGPointZero; } return self; } @end </code></pre> <p>I hope this comes of any help !!! Enjoy :)</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload