iPad 中的洪水填充太慢了
Posted
技术标签:
【中文标题】iPad 中的洪水填充太慢了【英文标题】:Flood filling in iPad too slow 【发布时间】:2011-05-09 01:19:28 【问题描述】:我在 iPad 上为我的一个着色应用程序使用洪水填充。
该应用程序基本上在图像的黑线内填充颜色,我可以毫无问题地做到这一点,但它太慢了。
我首先使用递归洪水填充,它的性能是最差的(由于堆栈溢出),然后我能够使用堆栈将其转换为迭代,使用以下代码,但这太慢了
-(void)floodFillAtPoint:(CGPoint)atPoint
Stack *stack = [[Stack alloc] init];
[stack push:[StackPoint pointWithPoint:atPoint]];
StackPoint *currentPoint = nil;
int counter = 0;
while ((currentPoint = [stack pop]))
CGPoint aPoint = currentPoint.point;//CGPointMake(pointPixel.x, pointPixel.y);
[self setColorAtPoint:aPoint];
CGPoint bPoint = aPoint;
bPoint.x+=1;
if([self checkForValidRegionAtPoint:bPoint])
[stack push:[StackPoint pointWithPoint:bPoint]];
bPoint = aPoint;
bPoint.x-=1;
if([self checkForValidRegionAtPoint:bPoint])
[stack push:[StackPoint pointWithPoint:bPoint]];
bPoint = aPoint;
bPoint.y+=1;
if([self checkForValidRegionAtPoint:bPoint])
[stack push:[StackPoint pointWithPoint:bPoint]];
bPoint = aPoint;
bPoint.y-=1;
if([self checkForValidRegionAtPoint:bPoint])
[stack push:[StackPoint pointWithPoint:bPoint]];
counter++;
[stack release];
有人能推荐一种最适合 iPad 设备的替代方法吗?
【问题讨论】:
Floodfill in objective c 的可能重复项 @quixoto 的答案是“使用洪水填充”。我认为不是很相关,而且绝对不是重复的。 @RVN,你能在这里分享代码吗? @EmptyStack :我已经用代码 sn-p 更新了我的问题 @florjan ,我添加了 StackPoint 类 【参考方案1】:使用 Objective-C 对象来表示每个像素将非常缓慢并且几乎没有什么好处。
使用不同的数据结构来表示您的位图,例如各种 CG* 位图封装机制之一。然后直接旋转位图中的位。它会越来越快。
【讨论】:
嗨@bbum:我能理解你想说的话,但任何领先的开始都会有所帮助,链接或开发者指南中的主题 填充位图图像的洪水是计算机图形学 101 材料。谷歌搜索将显示数以万计的文件,这些文件正是在讨论这个问题。还有大量的 CoreGraphics 教程和示例可用。从 Apple 提供的 CoreGraphics 文档开始。 同意@bbum。如果你用 CG 位图像素的 C 数组直接用 ANSI C 重写你的 Objective C 东西,它可能会快很多。如果你不懂 C,你就不会懂 Objective C。 我使用了链表(队列)和一些结构体,速度大大提高,主要是使用的内存很低。感谢您的帮助,将尽快发布结果代码。 大家好,我已经尝试了上面的代码。它工作正常,但是我发现大图像很难。有人可以帮忙吗?【参考方案2】:(代表问题作者发布解决方案)_。
我终于设法通过以下课程为 iPhone/iPad 制作了一个可接受的填充。如果有任何僵尸代码,请原谅:)。欢迎提出改进建议。
如何使用
创建 CanvasView 对象并设置 originalImage(这应该是带有黑线的未着色/彩色图像,黑线以外的区域必须是清晰的颜色),您可以在绘制后访问图像的 colouredImage。
.h 文件
#import <UIKit/UIKit.h>
#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
还有.m文件
#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)<=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)<=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 && 0 == __green && 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 <= 0 || touchPoint.y <= 0 || touchPoint.x >= self.frame.size.width ||touchPoint.y >= self.frame.size.height)
return NO;
if(red == _red && green == _green && blue == _blue && alpha1 == _alpha1)
return NO;
if(_alpha1 <= 225)
return NO;
if(_red <= 50 && _green <= 50 && _blue <= 50 && _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->next)
temp = temp->next;
temp->next = node;
// node->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->next;
node->next = NULL;
return node;
return NULL;
ffnode_t* new_ffnode(int x, int y)
ffnode_t *node = (ffnode_t*)malloc(sizeof(ffnode_t));
node->x = x; node->y = y;
node->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, &node->node);
long int counter = 0;
[self setColorAtPoint:atPoint];
while((node = (ffnode_t*)dequeue(&head)))
counter++;
CGPoint aPoint = CGPointMake(node->x, node->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, &node1->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, &node1->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, &node1->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, &node1->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] && !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] && !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 < (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
附加信息:
StackPoint.h
@interface StackPoint : NSObject
CGPoint point;
@property (nonatomic , assign) CGPoint point;
+(StackPoint*)pointWithPoint:(CGPoint)_point;
@end
堆栈点.m
@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
【讨论】:
以上是关于iPad 中的洪水填充太慢了的主要内容,如果未能解决你的问题,请参考以下文章
Vector 中的 1000 多个游戏对象太慢了?我的错误是啥?