CGContextDrawImage является ОЧЕНЬ медленным после большого UIImage, нарисованного в нем - PullRequest
28 голосов
/ 27 сентября 2011

Кажется, что CGContextDrawImage (CGContextRef, CGRect, CGImageRef) выполняет гораздо больше при рисовании CGImage, который был создан CoreGraphics (то есть с CGBitmapContextCreateImage), чем при рисовании CGImage, который поддерживает UIImage.Посмотрите этот метод тестирования:

-(void)showStrangePerformanceOfCGContextDrawImage
{
    ///Setup : Load an image and start a context:
    UIImage *theImage = [UIImage imageNamed:@"reallyBigImage.png"];
    UIGraphicsBeginImageContext(theImage.size);
    CGContextRef ctxt = UIGraphicsGetCurrentContext();    
    CGRect imgRec = CGRectMake(0, 0, theImage.size.width, theImage.size.height);


    ///Why is this SO MUCH faster...
    NSDate * startingTimeForUIImageDrawing = [NSDate date];
    CGContextDrawImage(ctxt, imgRec, theImage.CGImage);  //Draw existing image into context Using the UIImage backing    
    NSLog(@"Time was %f", [[NSDate date] timeIntervalSinceDate:startingTimeForUIImageDrawing]);

    /// Create a new image from the context to use this time in CGContextDrawImage:
    CGImageRef theImageConverted = CGBitmapContextCreateImage(ctxt);

    ///This is WAY slower but why??  Using a pure CGImageRef (ass opposed to one behind a UIImage) seems like it should be faster but AT LEAST it should be the same speed!?
    NSDate * startingTimeForNakedGImageDrawing = [NSDate date];
    CGContextDrawImage(ctxt, imgRec, theImageConverted);
    NSLog(@"Time was %f", [[NSDate date] timeIntervalSinceDate:startingTimeForNakedGImageDrawing]);


}

Итак, я думаю, вопрос в том, # 1, что может быть причиной этого, и # 2, есть ли способ обойти это, то есть другие способы создания CGImageRef, который может быть быстрее?Я понимаю, что могу сначала преобразовать все в UIImages, но это такое уродливое решение.У меня уже есть CGContextRef, сидящий там.

ОБНОВЛЕНИЕ: Это, кажется, не обязательно верно при рисовании маленьких изображений?Это может быть подсказкой - эта проблема усиливается при использовании больших изображений (то есть полноразмерных изображений с камеры).640x480, похоже, довольно схожи по времени выполнения с любым из методов

ОБНОВЛЕНИЕ 2: Хорошо, поэтому я обнаружил что-то новое ... На самом деле это НЕ поддержка CGImage, которая меняет производительность.Я могу перевернуть порядок двух шагов и заставить метод UIImage вести себя медленно, тогда как «голый» CGImage будет очень быстрым.Кажется, что бы вы ни делали вторым, вы будете страдать от ужасного выступления.Это, кажется, имеет место, ЕСЛИ Я не освобождаю память, вызывая CGImageRelease для изображения, которое я создал с CGBitmapContextCreateImage.Тогда метод с поддержкой UIImage будет быстрым впоследствии.Обратное это не правда.Что дает?«Переполненная» память не должна влиять на производительность, как это, не так ли?

ОБНОВЛЕНИЕ 3: Говорили слишком рано.Предыдущее обновление относится к изображениям размером 2048x2048, но с шагом до 1936x2592 (размер камеры) голый метод CGImage все еще медленнее, независимо от порядка операций или ситуации с памятью.Возможно, существуют некоторые внутренние ограничения компьютерной графики, которые делают изображение размером 16 МБ эффективным, тогда как изображение размером 21 МБ не может быть эффективно обработано.Буквально в 20 раз медленнее нарисовать размер камеры, чем 2048x2048.Каким-то образом UIImage предоставляет свои данные CGImage намного быстрее, чем чистый объект CGImage.oO

ОБНОВЛЕНИЕ 4: Я подумал, что это может быть связано с некоторыми вещами кэширования памяти, но результаты одинаковы, независимо от того, загружен ли UIImage с некэширующим [UIImage imageWithContentsOfFile], как если бы [UIImage imageNamed] былиспользуется.

ОБНОВЛЕНИЕ 5 (День 2): После создания вопросов Mroe, на которые были даны ответы вчера, у меня что-то твердое сегодня.Что я могу сказать наверняка, так это:

  • CGImage, стоящие за UIImage, не используют альфа.(KCGImageAlphaNoneSkipLast).Я подумал, что, может быть, их быстрее нарисовать, потому что в моем контексте использовалась альфа.Поэтому я изменил контекст, чтобы использовать kCGImageAlphaNoneSkipLast.Это делает рисование НАМНОГО быстрее, ЕСЛИ НЕ:
  • Рисование в CGContextRef с UIImage FIRST, замедляет ВСЕ последующее рисование изображений

Я доказал это 1) сначала создав неальфа-контекст (1936x2592).2) Заполнил его случайными цветными квадратами 2х2.3) Полный кадр, рисующий CGImage в этом контексте, был БЫСТРОМ (.17 секунд). 4) Повторял эксперимент, но заполнил контекст нарисованным CGImage, поддерживающим UIImage.Последующее рисование полнокадрового изображения заняло 6+ секунд.SLOWWWWW.

Каким-то образом рисование в контексте с помощью (большого) UIImage резко замедляет все последующие рисования в этом контексте.

Ответы [ 2 ]

41 голосов
/ 29 сентября 2011

Ну, после ТОННА экспериментов, я думаю, что нашел самый быстрый способ справиться с подобными ситуациями. Операция рисования выше, которая занимала 6+ секунд, теперь 0,1 секунды. ДА. Вот что я обнаружил:

  • Гомогенизируйте свои контексты и изображения в пиксельном формате! Корень вопроса, который я задал, сводился к тому факту, что CGImages внутри UIImage использовали ФОРМАТ ОДНОГО ПИКСЕЛА в качестве моего контекста. Поэтому быстро. CGImages были другого формата и поэтому медленные. Проверьте ваши изображения с помощью CGImageGetAlphaInfo, чтобы увидеть, какой формат пикселей они используют. Я использую kCGImageAlphaNoneSkipLast ВЕЗДЕ сейчас, так как мне не нужно работать с альфа. Если вы не используете один и тот же формат пикселей везде, при рисовании изображения в контексте Quartz будет вынужден выполнять дорогостоящие преобразования пикселей для КАЖДОГО пикселя. = Медленный

  • ИСПОЛЬЗУЙТЕ CGLayers! Это значительно повышает производительность рисования вне экрана. Как это работает в основном следующим образом. 1) создать CGLayer из контекста, используя CGLayerCreateWithContext. 2) выполнить любой рисунок / настройку свойств чертежа в КОНТЕКСТЕ ЭТОГО СЛОЯ, полученном с CGLayerGetContext. ЧИТАЙТЕ любые пиксели или информацию из ОРИГИНАЛЬНОГО контекста. 3) Когда закончите, «поставьте» этот CGLayer обратно в исходный контекст, используя CGContextDrawLayerAtPoint. Это БЫСТРО, если вы помните:

  • 1) Освободите все CGImages, созданные из контекста (то есть созданные с помощью CGBitmapContextCreateImage) ДО «штамповки» вашего слоя обратно в CGContextRef с помощью CGContextDrawLayerAtPoint. Это создает увеличение скорости в 3-4 раза при рисовании этого слоя. 2) Сохраняйте свой формат пикселей везде одинаковым! 3) Как можно скорее очистите объекты компьютерной графики. Кажется, что вещи, которые торчат в памяти, создают странные ситуации замедления, возможно, из-за обратных вызовов или проверок, связанных с этими сильными ссылками. Просто предположение, но я могу сказать, что ОЧИСТКА ПАМЯТИ КАК МОЖНО СКОРЕЕ повышает производительность.

13 голосов
/ 22 апреля 2013

У меня была похожая проблема. Мое приложение должно перерисовать изображение почти размером с экран. Проблема сводилась к тому, чтобы как можно быстрее нарисовать два изображения с одинаковым разрешением, которые не поворачивались и не переворачивались, а каждый раз масштабировались и располагались в разных местах экрана. В конце концов, я смог получить ~ 15-20 FPS на iPad 1 и ~ 20-25 FPS на iPad4. Так что ... надеюсь, это кому-нибудь поможет:

  1. Точно так же, как пишущая машинка , вы должны использовать тот же формат пикселей. Использование одного с AlphaNone дает повышение скорости. Но что еще более важно, вызов argb32_image в моем случае делал многочисленные вызовы, конвертируя пиксели из ARGB в BGRA. Таким образом, лучшим значением bitmapInfo для меня было (в то время, есть вероятность, что Apple может что-то здесь изменить в будущем): const CGBitmabInfo g_bitmapInfo = kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipLast;
  2. CGContextDrawImage может работать быстрее, если аргумент прямоугольника был сделан целочисленным (с помощью CGRectIntegral). Кажется, имеет больший эффект, когда изображение масштабируется с коэффициентом, близким к 1.
  3. Использование слоев на самом деле замедлило вещи для меня. Возможно, что-то изменилось с 2011 года в некоторых внутренних звонках.
  4. Важное значение имеет установка качества интерполяции для контекста ниже значения по умолчанию (по CGContextSetInterpolationQuality). Я бы порекомендовал использовать (IS_RETINA_DISPLAY ? kCGInterpolationNone : kCGInterpolationLow). Макросы IS_RETINA_DISPLAY взяты из здесь .
  5. Убедитесь, что вы получаете CGColorSpaceRef из CGColorSpaceCreateDeviceRGB () и т.п. при создании контекста. Сообщалось о некоторых проблемах с производительностью для получения фиксированного цветового пространства вместо запроса устройства.
  6. Наследование класса представления из UIImageView и простая установка self.image к изображению, созданному из контекста, оказались полезными для меня. Тем не менее, сначала прочитайте об использовании UIImageView, если вы хотите это сделать, поскольку для этого требуются некоторые изменения в логике кода (поскольку drawRect: больше не вызывается).
  7. И если вы можете избежать масштабирования изображения во время фактического рисования, попробуйте сделать это. Рисование немасштабированного изображения происходит значительно быстрее - к сожалению, для меня это было невозможно.
...