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 多个游戏对象太慢了?我的错误是啥?

Rabin Karp 在 Ruby 中的实现太慢了

PostgreSQL join 获取表中的所有行,太慢了

Qt 5.3 Webkit QWebView中的Javascript太慢了,在Windows上挂了MainWindow

洪水填充图像的图像处理

Swift:tableView.reloadData() 太慢了