Drawrect с CGBitmapContext работает слишком медленно

Итак, у меня есть базовое приложение для рисования, которое позволяет мне рисовать линии. Я рисую растровое изображение вне экрана, а затем представляю изображение в drawRect. Он работает, но работает слишком медленно, обновляется примерно через полсекунды после того, как вы нарисовали его пальцем. Я взял код и адаптировал его из этого руководства, http://www.youtube.com/watch?v=UfWeMIL-Nu8&feature=relmfu, как вы можете видеть в комментариях, люди также говорят, что это слишком медленно, но парень не ответил.

Так как мне его ускорить? или есть способ лучше? любые указатели будут оценены.

Вот код в моем DrawView.m.

-(id)initWithCoder:(NSCoder *)aDecoder {
     if ((self=[super initWithCoder:aDecoder])) {
         [self setUpBuffer];
     }

     return self;
}

-(void)setUpBuffer {
     CGContextRelease(offscreenBuffer);

     CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();

     offscreenBuffer = CGBitmapContextCreate(NULL, self.bounds.size.width, self.bounds.size.height, 8, self.bounds.size.width*4, colorSpace, kCGImageAlphaPremultipliedLast);
     CGColorSpaceRelease(colorSpace);

     CGContextTranslateCTM(offscreenBuffer, 0, self.bounds.size.height);
     CGContextScaleCTM(offscreenBuffer, 1.0, -1.0);
}


-(void)drawToBuffer:(CGPoint)coordA :(CGPoint)coordB :(UIColor *)penColor :(int)thickness {

     CGContextBeginPath(offscreenBuffer);
     CGContextMoveToPoint(offscreenBuffer, coordA.x,coordA.y);
     CGContextAddLineToPoint(offscreenBuffer, coordB.x,coordB.y);
     CGContextSetLineWidth(offscreenBuffer, thickness);
     CGContextSetLineCap(offscreenBuffer, kCGLineCapRound);
     CGContextSetStrokeColorWithColor(offscreenBuffer, [penColor CGColor]);
     CGContextStrokePath(offscreenBuffer);

}

- (void)drawRect:(CGRect)rect {
    CGImageRef cgImage = CGBitmapContextCreateImage(offscreenBuffer);
    UIImage *image =[[UIImage alloc] initWithCGImage:cgImage];
    CGImageRelease(cgImage);
    [image drawInRect:self.bounds];

}

Отлично работает на симуляторе, но не на устройстве, я полагаю, что это как-то связано со скоростью процессора.

Я использую ARC.


person Eager Logic    schedule 29.06.2012    source источник
comment
Может быть, вызов: [self setNeedsDisplay]; или [self setNeedsDisplayInRect:rect]; в методе, который рисует линию, может ускорить обновление?   -  person Rich Tolley    schedule 30.06.2012
comment
Вы использовали инструменты, чтобы определить, на что вы тратите время? Одна очевидная вещь, которая выскакивает, - это бессмысленное создание UIImage. CGContextDrawImage ... может напрямую использовать CGimage.   -  person jrturton    schedule 01.07.2012
comment
Какое устройство iOS вы используете для тестирования?   -  person Basel    schedule 02.07.2012
comment
@jrturton метод drawrect - это то место, где тратится время   -  person Eager Logic    schedule 02.07.2012
comment
См. Этот ответ: stackoverflow.com/questions/10410106/   -  person Basel    schedule 02.07.2012
comment
@jammycoder ну да, мы знали, что это будет drawRect! Вы можете дважды щелкнуть по этой строке, и он откроет ваш фактический метод, рядом с каждой строкой будет номер, указывающий затраченное время.   -  person jrturton    schedule 02.07.2012
comment
@jrturton эта строка [image drawInRect:self.bounds];   -  person Eager Logic    schedule 02.07.2012
comment
Зачем вам каждый раз перерисовывать изображение. Вы можете просто разместить UIImageView под своим пользовательским представлением.   -  person xingzhi.sg    schedule 03.07.2012


Ответы (7)


Я попытался исправить ваш код, однако, поскольку вы, кажется, опубликовали только половину его, я не мог заставить его работать (копирование + вставка кода приводит к множеству ошибок, не говоря уже о том, чтобы начать настройку производительности).

Однако есть несколько советов, которые можно использовать, чтобы ОЧЕНЬ повысить производительность.

Первый и, вероятно, наиболее заметный, - это -setNeedsDisplayInRect: а не -setNeedsDisplay. Это будет означать, что он перерисовывает только тот маленький прямоугольник, который изменился. Для iPad 3 с разрешением 1024 * 768 * 4 пикселей это большая работа. Уменьшение этого значения примерно до 20 * 20 или меньше для каждого кадра значительно повысит производительность.

CGRect rect;
rect.origin.x = minimum(coordA.x, coordB.x) - (thickness * 0.5);
rect.size.width = (maximum(coordA.x, coordB.x) + (thickness * 0.5)) - rect.origin.x;
rect.origin.y = minimum(coordA.y, coordB.y) - (thickness * 0.5);
rect.size.height = (maximum(coordA.y, coordB.y) + (thickness * 0.5)) - rect.origin.y;
[self setNeedsDisplayInRect:rect];

Еще одно большое улучшение, которое вы могли бы сделать, - это рисовать CGPath только для этого текущего касания (что вы и делаете). Однако затем вы рисуете это сохраненное / кешированное изображение в прямоугольнике рисования. Итак, снова перерисовывается каждый кадр. Лучше всего сделать вид отрисовки прозрачным, а затем использовать UIImageView за ним. UIImageView - лучший способ отображать изображения на iOS.

- DrawView (1 finger)
   -drawRect:
- BackgroundView (the image of the old touches)
   -self.image

В этом случае представление рисования будет рисовать только текущее касание, только ту часть, которая меняется каждый раз. Когда пользователь поднимает палец, вы можете кэшировать его в UIImage, нарисовать его поверх текущего фонового / кешированного изображения UIImageView и установить imageView.image на новое изображение.

Этот последний бит при объединении изображений включает в себя рисование 2 полноэкранных изображений в CGContext вне экрана, что приведет к задержке, если это сделано в основном потоке, вместо этого это должно быть сделано в фоновом потоке, а затем результат будет возвращен в основной поток.

* touch starts *
- DrawView : draw current touch
* touch ends *
- 'background thread' : combine backgroundView.image and DrawView.drawRect
    * thread finished *
    send resulting UIImage to main queue and set backgroundView.image to it;
    Clear DrawView's current path that is now in the cache;

Все это вместе может сделать очень плавное приложение для рисования со скоростью 60 кадров в секунду. Однако виды обновляются не так быстро, как хотелось бы, поэтому рисунок при более быстром перемещении фигуры выглядит неровным. Это можно улучшить, используя UIBezierPath вместо CGPaths.

CGPoint lastPoint = [touch previousLocationInView:self];
CGPoint mid = midPoint(currentPoint, lastPoint);
-[UIBezierPath addQuadCurveToPoint:mid controlPoint:lastPoint];
person Kyle Howells    schedule 07.07.2012

Причина, по которой он медленный, заключается в том, что в каждом кадре вы создаете растровое изображение и пытаетесь его нарисовать.

Вы просили, как это сделать лучше? Вы смотрели пример кода Apple для приложения для рисования на iOS?? Если вам это не нравится, вы всегда можете использовать cocos2d, который предоставляет класс CCRenderTexture (и образец код).

В настоящее время вы используете метод, который, как вы уже знаете, неэффективен.

person Paul de Lange    schedule 02.07.2012

При таком подходе, я полагаю, вам следует рассмотреть возможность использования фонового потока для всей тяжелой работы по рендерингу изображений и основного потока только для обновлений пользовательского интерфейса, т.е. е.

__block UIImage *__imageBuffer = nil;

- (UIImage *)drawSomeImage
{
    UIGraphicsBeginImageContext(self.bounds);

    // draw image with CoreGraphics

    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext();

    return image;
}

- (void)updateUI
{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        // prepare image on background thread

        __imageBuffer = [self drawSomeImage];

        dispatch_async(dispatch_get_main_queue(), ^{

            // calling drawRect with prepared image

            [self setNeedsDisplay];

        });
    });
}

- (void)drawRect
{
    // draw image buffer on current context

    [__imageBuffer drawInRect:self.bounds];
}

Я опускаю некоторые детали, чтобы сделать оптимизацию более понятной. Еще лучше перейти на UIImageView. Таким образом, вы можете избавиться от критически важного - (void)drawDect метода и обновить свойство изображения UIImageView, когда изображение будет готово.

person voromax    schedule 03.07.2012

Что ж, я думаю, вам нужно изменить свою логику. Вы можете получить очень хорошую идею с помощью этой ссылки http://devmag.org.za/2011/04/05/bzier-curves-a-tutorial/, и если вы считаете, что у вас нет времени на понимание, вы можете перейти непосредственно к этому коду https://github.com/levinunnink/Smooth-Line-View :) Надеюсь, это поможет вы много.

person Mashhadi    schedule 04.07.2012

Используйте CgLayer для кеширования ваших путей, прочтите документацию, лучше всего подходит для оптимизации.

person Ranjit    schedule 05.07.2012

Я сделал именно это. Проверьте приложение Pixelate в AppStore. Чтобы рисовать, я использовал плитки в своем коде. В конце концов, когда вы поворачиваете экран и рисуете что-то, вам нужно перерисовывать все изображение, а это очень тяжелая операция. Если вам нравится, как движется Pixelate, вот как я это сделал:

1) Разделить мое изображение на n x m плиток. Это было сделано для того, чтобы я мог изменять эти значения и получать плитки большего / меньшего размера. В худшем случае (пользователь нажимает на пересечение 4 плиток) вам придется перерисовать эти 4 плитки. Не все изображение.

2) Создайте трехмерную матрицу, в которой я хранил информацию о пикселях каждой плитки. Итак, matrix[0][0][0] было красным значением (каждый пиксель имеет значение RGB или RGBA, в зависимости от того, используете ли вы png или jpgs) первого пикселя первой плитки.

3) Получите место, на которое нажал пользователь, и вычислите плитки, которые необходимо изменить.

4) Измените значения в матрице и обновите плитки, которые необходимо обновить.

ПРИМЕЧАНИЕ. Это, безусловно, не лучший вариант. Это просто альтернатива. Я упомянул об этом, потому что думаю, что он близок к тому, что у вас есть сейчас. И у меня это сработало на iPhone 3GS. Если вы нацеливаетесь на> = iPhone 4, все должно быть в порядке.

С уважением,

Георгий

person George    schedule 06.07.2012

Какой бы метод вы ни предложили, он слишком неэффективен, потому что создавать изображение каждый раз, когда вы двигаете пальцем, неуместно.

Если это только пути, которые вам нужно нарисовать, тогда укажите CGMutablePathref в качестве переменной-члена, а в прямоугольнике рисования просто переместитесь в указанную точку с помощью функций CGPath.

И что еще более важно, при обновлении вида вызывайте setNeedsDisplayInRect, передавая только ту область, которую вам нужно нарисовать. Надеюсь, это сработает для вас.

person Sj.    schedule 05.07.2012