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

Кажется, что 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. оо

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

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

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

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

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

2 ответа

Решение

После долгих экспериментов я думаю, что нашел самый быстрый способ справиться с подобными ситуациями. Операция рисования выше, которая заняла 6+ секунд сейчас.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) Как можно скорее очистите объекты компьютерной графики. Кажется, что вещи, которые торчат в памяти, создают странные ситуации замедления, возможно, из-за обратных вызовов или проверок, связанных с этими сильными ссылками. Просто предположение, но я могу сказать, что ОЧИСТКА ПАМЯТИ КАК МОЖНО СКОРЕЕ помогает производительности.

У меня была похожая проблема. Мое приложение должно перерисовать изображение почти размером с экран. Проблема сводилась к тому, чтобы как можно быстрее нарисовать два изображения с одинаковым разрешением, которые не поворачивались и не переворачивались, а каждый раз масштабировались и располагались в разных местах экрана. В конце концов, я смог получить ~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. И если вы можете избежать масштабирования вашего изображения во время реального рисования, попробуйте сделать это. Рисование немасштабированного изображения происходит значительно быстрее - к сожалению, для меня это не было вариантом.
Другие вопросы по тегам