CGContextDrawImage ЭКСТРЕМАЛЬНО медленно после того, как в него втянут большой UIImage

Кажется, что CGContextDrawImage (CGContextRef, CGRect, CGImageRef) выполняет MUCH WORSE при рисовании 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 все еще медленнее, независимо от порядка операций или ситуации с памятью. Возможно, есть некоторые внутренние ограничения CG, которые делают эффективное изображение на 16 МБ, тогда как изображение 21 МБ нельзя эффективно обрабатывать. Его буквально в 20 раз медленнее нарисовать размер камеры, чем 2048x2048. Как-то UIImage предоставляет свои данные CGImage намного быстрее, чем чистый объект CGImage. o.o

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

ОБНОВЛЕНИЕ 5 (2-й день): после создания вопросов в mroe, чем вчера, я получил сегодня что-то твердое. Я могу сказать наверняка следующее:

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

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

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

Ответ 1

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

  • Гомогенизируйте свои контексты и изображения в формате пикселей! Корень вопроса, который я задал, сводился к тому, что CGImages внутри UIImage использовали ТЕКСТЫЙ ФОРМАТ PIXEL в качестве моего контекста. Поэтому быстро. CGImages были другого формата и, следовательно, медленны. Осмотрите свои изображения с помощью CGImageGetAlphaInfo, чтобы узнать, какой формат пикселей они используют. Теперь я использую kCGImageAlphaNoneSkipLast КАЖДЫЙ, поскольку мне не нужно работать с альфой. Если вы не используете один и тот же формат пикселей везде, при рисовании изображения в контекст, кварц будет вынужден выполнять дорогостоящие пиксельные преобразования для КАЖДОГО пикселя. = SLOW

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

  • 1) Отпустите любые CGImages, созданные из контекста (т.е. созданные с помощью CGBitmapContextCreateImage). Прежде чем "тиснить" свой слой обратно в CGContextRef с помощью CGContextDrawLayerAtPoint. Это создает 3-4-кратное увеличение скорости при рисовании этого слоя. 2) Держите свой формат пикселей одинаковым везде! 3) Очистите объекты CG как можно дольше. Вещи, висящие в памяти, похоже, создают странные ситуации замедления, вероятно, потому, что есть обратные вызовы или проверки, связанные с этими сильными ссылками. Просто догадаться, но я могу сказать, что CLEANING UP MEMORY ASAP очень помогает в производительности.

Ответ 2

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

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