我正在研究绘画应用程序并实现泛洪填充算法。 这是我正在实现的代码: https://github.com/OgreSwamp/ObjFloodFill/blob/master/src/FloodFill.m
和viewController.h文件
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
typedef struct {
int red;
int green;
int blue;
int alpha;
} color;
@interface ViewController : UIViewController
{
AppDelegate *appDelegate;
UIImageView *mainImage;
UIView *loadingView;
unsigned char *imageData;
UIActivityIndicatorView *activityIndicator;
color selColor;
color newColor;
BOOL boolVariable;
int maxByte;
}
- (IBAction)fn_btnRed:(id)sender;
- (IBAction)fn_btnGreen:(id)sender;
- (IBAction)fn_btnBlue:(id)sender;
- (IBAction)fn_btnSave:(id)sender;
-(void)alertActivityClose;
@end
Viewcontroller.m文件
#import "ViewController.h"
#import "FloodFill.h"
@implementation ViewController
#pragma mark - View lifecycle
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
- (void)setupImageData
{
CGImageRef imageRef = mainImage.image.CGImage;
if (imageRef == NULL) { return; }
NSUInteger width = CGImageGetWidth(imageRef);
NSUInteger height = CGImageGetHeight(imageRef);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
NSUInteger bytesPerPixel = 4;
NSUInteger bytesPerRow = bytesPerPixel * width;
NSUInteger bitsPerComponent = 8;
maxByte = height * width * 4;
imageData = malloc(height * width * 4);
CGContextRef context = CGBitmapContextCreate(imageData, width, height, bitsPerComponent, bytesPerRow, colorSpace,
kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGColorSpaceRelease(colorSpace);
CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
CGContextRelease(context);
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
mainImage = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"scaledWhite.png"]];
[self.view addSubview:mainImage];
newColor.red = 255;
newColor.green = 94;
newColor.blue = 0;
[self setupImageData];
}
return self;
}
- (void)updateImage
{
CGImageRef imageRef = mainImage.image.CGImage;
if (imageRef == NULL) { return; }
NSUInteger width = CGImageGetWidth(imageRef);
NSUInteger height = CGImageGetHeight(imageRef);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
NSUInteger bytesPerPixel = 4;
NSUInteger bytesPerRow = bytesPerPixel * width;
NSUInteger bitsPerComponent = 8;
CGContextRef context = CGBitmapContextCreate(imageData, width, height, bitsPerComponent, bytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast );
imageRef = CGBitmapContextCreateImage (context);
mainImage.image = [UIImage imageWithCGImage:imageRef];
CGContextRelease(context);
if (boolVariable==YES)
{
UIImageWriteToSavedPhotosAlbum(mainImage.image, nil, nil, nil);
}
boolVariable=NO;
}
- (void)setPixel:(NSUInteger)byte toColor:(color)color
{
imageData[byte] = color.red;
imageData[byte+1] = color.green;
imageData[byte+2] = color.blue;
}
- (BOOL)testByte:(NSInteger)byte againstColor:(color)color
{
if (imageData[byte] == color.red && imageData[byte+1] == color.green && imageData[byte+2] == color.blue) {
return YES;
}
else
{
return NO;
}
}
// This is where the flood fill starts. Its a basic implementation but crashes when filling large sections.
- (void)floodFillFrom:(NSInteger)byte bytesPerRow:(NSInteger)bpr {
int u = byte - bpr;
int r = byte + 4;
int d = byte + bpr;
int l = byte - 4;
if ([self testByte:u againstColor:selColor]) {
[self setPixel:u toColor:newColor];
[self floodFillFrom:u bytesPerRow:bpr];
}
if ([self testByte:r againstColor:selColor]) {
[self setPixel:r toColor:newColor];
[self floodFillFrom:r bytesPerRow:bpr];
}
if ([self testByte:d againstColor:selColor]) {
[self setPixel:d toColor:newColor];
[self floodFillFrom:d bytesPerRow:bpr];
}
if ([self testByte:l againstColor:selColor]) {
[self setPixel:l toColor:newColor];
[self floodFillFrom:l bytesPerRow:bpr];
}
}
-(void)shiftingOnMainThread
{
[self performSelectorOnMainThread:@selector(updateImage) withObject:nil waitUntilDone:YES];
}
- (void)startFillFrom:(NSInteger)byte bytesPerRow:(NSInteger)bpr
{
if (imageData[byte] == 0 && imageData[byte+1] == 0 && imageData[byte+2] == 0)
{
return;
}
else if ([self testByte:byte againstColor:newColor])
{
NSLog(@"Same Fill Color");
}
else
{
// code goes here
NSLog(@"Color to be replaced");
[self floodFillFrom:byte bytesPerRow:bpr];
[self updateImage];
}
}
- (void)selectedColor:(CGPoint)point
{
CGImageRef imageRef = mainImage.image.CGImage;
if (imageRef == NULL) { return; }
if (imageData == NULL) { return; }
NSInteger width = CGImageGetWidth(imageRef);
NSInteger byteNumber = 4*((width*round(point.y))+round(point.x));
selColor.red = imageData[byteNumber];
selColor.green = imageData[byteNumber + 1];
selColor.blue = imageData[byteNumber + 2];
NSLog(@"Selected Color, RGB: %i, %i, %i",selColor.red, selColor.green, selColor.blue);
NSLog(@"Byte:%i",byteNumber);
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView:mainImage];
[self selectedColor:location];
CGImageRef imageRef = mainImage.image.CGImage;
NSInteger width = CGImageGetWidth(imageRef);
NSInteger height = CGImageGetHeight(imageRef);
int x = 0;
x |= (selColor.red & 0xff) << 24;
x |= (selColor.green & 0xff) << 16;
x |= (selColor.blue & 0xff) << 8;
x |= (selColor.alpha & 0xff);
int y = 0;
y |= (newColor.red & 0xff) << 24;
y |= (newColor.green & 0xff) << 16;
y |= (newColor.blue & 0xff) << 8;
y |= (newColor.alpha & 0xff);
[NSThread detachNewThreadSelector:@selector(shiftingOnMainThread) toTarget:self withObject:nil];
}
-(void)alertActivityClose
{
[activityIndicator stopAnimating];
[activityIndicator hidesWhenStopped];
loadingView.hidden=YES;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
- (IBAction)fn_btnRed:(id)sender
{
newColor.red = 255;
newColor.green = 0;
newColor.blue = 0;
}
- (IBAction)fn_btnGreen:(id)sender
{
newColor.red = 0;
newColor.green = 255;
newColor.blue = 0;
}
- (IBAction)fn_btnBlue:(id)sender
{
newColor.red = 0;
newColor.green = 0;
newColor.blue = 255;
}
- (IBAction)fn_btnSave:(id)sender
{
boolVariable=YES;
[self updateImage];
}
@end
如果用户持续使用该应用程序并且需要大约20-30秒来填充一小部分,同时还会因内存错误而崩溃,则会出现性能问题。
有没有人遇到这种洪水填充的问题以及如何解决它?
答案 0 :(得分:0)
崩溃明智:我没有看到任何代码阻止填充物缠绕图像的左/右端或像素数据的顶部或底部。不限制左/右洪水可能会产生意外结果。超越顶部/底部很可能会导致非法内存访问(崩溃)。
颜色结构应该定义为四个uint8_t(不是int)。我还将imageData定义为指向此(属性大小)结构的指针,让编译器处理.red,.green&amp; .blue组件而不是硬编码+ 0,+ 1,+ 2偏移。
几乎在你硬编码(假设)值的任何地方,我都会使用“sizeof(x)”。
NSUInteger bytesPerPixel = sizeof(color);
答案 1 :(得分:0)
性能方面,您正在使用索引并强制在几乎每个像素访问上重新计算绝对地址(因为实际的像素访问是在下一次调用(递归)子例程中)。直接使用像素地址通常要快得多(特别是对于水平(左 - 右)运行)。
我不知道ObjC方法调度增加了多少开销,但我考虑重写floodFillFrom:bytesPerRow:,testByte:againstColor:和setPixel:toColor:作为strait C函数。 (测试并设置为静态内联。)