проблема с производительностью заливки заливкой на iPhone/iPad

Я работаю над приложением для рисования и реализую алгоритм заливки. Вот код, который я реализую: 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 секунд, а также происходит сбой с ошибкой памяти.

У кого-нибудь была такая проблема с заливкой и как ее решить?


person Bond    schedule 15.03.2013    source источник
comment
Я думаю, что это проблема с управлением памятью. Позаботьтесь об освобождении и распределении объектов   -  person user247    schedule 15.03.2013
comment
Вы пытались использовать ARC для этого конкретного файла.   -  person skyline    schedule 15.03.2013
comment
я использую ARC для всех файлов.   -  person Bond    schedule 15.03.2013
comment
ARC работает только для классов NSFoundation. Поскольку вы используете много кода CoreGraphics, вам нужно быть очень осторожным при их выпуске. каждый malloc() должен быть освобожден с помощью free(). В вашем методе updateImage вы также должны освободить Imageref, поскольку вы назначаете ему новый Imageref.Release с помощью CGImageRelease(imageref) .   -  person kaar3k    schedule 16.03.2013


Ответы (2)


Что касается производительности, вы играете с индексами и заставляете повторно вычислять абсолютные адреса почти при каждом доступе к пикселю (поскольку фактический доступ к пикселю находится в следующем вызове (рекурсивной) подпрограммы). Работа напрямую с адресами пикселей обычно намного быстрее (особенно для горизонтальных (слева‹->справа) прогонов).

Я не знаю, сколько накладных расходов добавляет диспетчеризация метода ObjC, но я бы подумал о переписывании fludFillFrom:bytesPerRow:, testByte:againstColor: и setPixel:toColor: как прямых функций C. (протестируйте и установите как статические встроенные строки.)

person geowar    schedule 15.03.2013

Мудрый сбой: я не вижу никакого кода, препятствующего обтеканию заливки вокруг левого/правого концов изображения или верхней или нижней части данных пикселей. Отсутствие ограничения левого/правого потока может привести к неожиданным результатам. Выход за верх/низ, скорее всего, приведет к нелегальному доступу к памяти (сбою).

Структура цвета, вероятно, должна быть определена как четыре uint8_t (не int). Я бы также определил imageData как указатель на эту структуру (с размером свойства) и позволил компилятору работать с компонентами .red, .green и .blue вместо жесткого кодирования смещений +0, +1, +2.

Почти везде, где вы жестко кодируете (предполагаемые) значения, я бы вместо этого использовал «sizeof (x)».

   NSUInteger bytesPerPixel = sizeof(color);
person geowar    schedule 15.03.2013