ios performance core-graphics cgcontextdrawimage

ios - CGContextDrawImage es EXTREMADAMENTE lento después de un gran UIImage dibujado en él



performance core-graphics (2)

Parece que CGContextDrawImage (CGContextRef, CGRect, CGImageRef) se comporta MUCHO MÁS PEQUEÑO al dibujar una CGImage que fue creada por CoreGraphics (es decir, con CGBitmapContextCreateImage) que cuando dibuja la CGImage que respalda un UII Vea este método de prueba:

-(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]); }

Así que supongo que la pregunta es, # 1, ¿qué puede estar causando esto y # 2 hay alguna forma de evitarlo, es decir, otras formas de crear un CGImageRef que puede ser más rápido? Me doy cuenta de que podría convertir todo a UIImages primero, pero esa es una solución tan fea. Ya tengo el CGContextRef sentado allí.

ACTUALIZACIÓN: ¿Esto parece no ser necesariamente cierto cuando se dibujan imágenes pequeñas? Eso puede ser una pista: este problema se amplifica cuando se usan imágenes grandes (es decir, fotos de cámara de tamaño completo). 640x480 parece ser bastante similar en términos de tiempo de ejecución con cualquier método

ACTUALIZACIÓN 2: Ok, así que he descubierto algo nuevo ... En realidad, NO es el respaldo de CGImage que está cambiando el rendimiento. Puedo cambiar el orden de los 2 pasos y hacer que el método UIImage se comporte lentamente, mientras que el CGImage "simple" será súper rápido. Parece que lo que hagas en segundo lugar sufrirá de un rendimiento terrible. Este parece ser el caso A MENOS QUE libre de memoria llamando a CGImageRelease en la imagen que creé con CGBitmapContextCreateImage. Entonces el método respaldado por UIImage será rápido posteriormente. Lo inverso no es cierto. ¿Lo que da? La memoria "llena" no debería afectar el rendimiento de esta manera, ¿verdad?

ACTUALIZACIÓN 3: Hablé demasiado pronto. La actualización anterior es válida para las imágenes con un tamaño de 2048x2048, pero al avanzar hasta 1936x2592 (tamaño de la cámara), el método CGImage simple sigue siendo mucho más lento, independientemente del orden de las operaciones o la situación de la memoria. Tal vez hay algunos límites internos de CG que hacen que una imagen de 16 MB sea eficiente, mientras que la imagen de 21 MB no se puede manejar de manera eficiente. Es literalmente 20 veces más lento para dibujar el tamaño de la cámara que un 2048x2048. De alguna manera, UIImage proporciona sus datos CGImage mucho más rápido que un objeto CGImage puro. oo

ACTUALIZACIÓN 4: Pensé que esto podría tener que ver con el almacenamiento en caché de la memoria, pero los resultados son los mismos si el UIImage se carga con el [UIImage imageWithContentsOfFile] como si se usara [UIImage imageNamed].

ACTUALIZACIÓN 5 (Día 2): Después de crear más preguntas que las contestadas ayer, hoy tengo algo sólido. Lo que puedo decir con seguridad es lo siguiente:

  • Las imágenes CG detrás de un UIImage no usan alfa. (kCGImageAlphaNoneSkipLast). Pensé que tal vez eran más rápidos de dibujar porque mi contexto FUE utilizando alfa. Así que cambié el contexto para usar kCGImageAlphaNoneSkipLast. Esto hace que el dibujo sea MUCHO más rápido, A MENOS QUE:
  • Dibujar en un CGContextRef con un UIImage FIRST, hace que TODOS los dibujos subsiguientes sean lentos

Esto lo probé 1) creando primero un contexto no alfa (1936x2592). 2) Rellénelo con cuadrados de 2x2 de colores aleatorios. 3) El fotograma completo para dibujar una imagen CG en ese contexto fue RÁPIDO (.17 segundos) 4) Experimento repetido, pero se completó el contexto con una imagen CG dibujada respaldando un UIImage. El dibujo de imagen de fotograma completo posterior fue de 6+ segundos. SLOWWWWW.

De alguna manera, dibujar en un contexto con un (III) UIImage ralentiza drásticamente todos los dibujos subsiguientes en ese contexto.


Bueno, después de un montón de experimentación, creo que he encontrado la manera más rápida de manejar situaciones como esta. La operación de dibujo por encima de la cual tomaba más de 6 segundos ahora .1 segundos. SÍ. Esto es lo que descubrí:

  • ¡Homogeneiza tus contextos e imágenes con un formato de píxel! La raíz de la pregunta que formulé se redujo al hecho de que las Imágenes CG dentro de un UIImage estaban utilizando EL MISMO FORMATO DE PIXEL como mi contexto. Por lo tanto rápido. Los CGImages eran un formato diferente y por lo tanto lento. Inspecciona tus imágenes con CGImageGetAlphaInfo para ver qué formato de píxel utilizan. Estoy usando kCGImageAlphaNoneSkipLast EN TODAS PARTES ahora, ya que no necesito trabajar con alfa. Si no usa el mismo formato de píxeles en todas partes, al dibujar una imagen en un contexto, Quartz se verá obligado a realizar costosas conversiones de píxeles para CADA píxel. = LENTO

  • UTILIZAR CGLayers! Esto hace que el rendimiento de dibujo fuera de pantalla sea mucho mejor. Cómo funciona esto es básicamente lo siguiente. 1) cree un CGLayer desde el contexto usando CGLayerCreateWithContext. 2) realice cualquier dibujo / configuración de las propiedades de dibujo en el CONTEXTO DE ESTA CAPA que se obtiene con CGLayerGetContext. LEA cualquier píxel o información del contexto ORIGINAL. 3) Cuando termine, "estampe" este CGLayer nuevamente en el contexto original usando CGContextDrawLayerAtPoint. Esto es RÁPIDO siempre y cuando tenga en cuenta:

  • 1) Libere cualquier CGImages creado desde un contexto (es decir, aquellos creados con CGBitmapContextCreateImage) ANTES de "estampar" su capa nuevamente en el CGContextRef usando CGContextDrawLayerAtPoint. Esto crea un aumento de velocidad de 3-4x al dibujar esa capa. 2) ¡Mantén tu formato de píxel igual en todas partes! 3) Limpia los objetos CG tan pronto como puedas. Las cosas que permanecen en la memoria parecen crear situaciones extrañas de desaceleración, probablemente porque hay devoluciones de llamada o cheques asociados con estas referencias fuertes. Solo una suposición, pero puedo decir que LIMPIAR LA MEMORIA lo antes posible ayuda enormemente al rendimiento.


Tuve un problema similar. Mi aplicación tiene que volver a dibujar una imagen casi tan grande como el tamaño de la pantalla. El problema se redujo a dibujar lo más rápido posible dos imágenes de la misma resolución, ni giradas ni volteadas, sino que se escalaron y se colocaron en diferentes lugares de la pantalla cada vez. Después de todo, pude obtener ~ 15-20 FPS en iPad 1 y ~ 20-25 FPS en iPad4. Entonces ... espero que esto ayude a alguien:

  1. Exactamente como dijo la máquina de escribir , tienes que usar el mismo formato de píxel. Usar uno con AlphaNone da un impulso de velocidad. Pero aún más importante, argb32_image call en mi caso hizo numerosas llamadas convirtiendo píxeles de ARGB a BGRA. Entonces, el mejor valor de bitmapInfo para mí fue (en ese momento; existe la posibilidad de que Apple pueda cambiar algo aquí en el futuro): const CGBitmabInfo g_bitmapInfo = kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipLast; const CGBitmabInfo g_bitmapInfo = kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipLast;
  2. CGContextDrawImage puede funcionar más rápido si el argumento de rectángulo se integró (por CGRectIntegral). Parece tener más efecto cuando la imagen se escala por un factor cercano a 1.
  3. Usar capas en realidad ralentizó las cosas para mí. Probablemente algo se cambió desde 2011 en algunas llamadas internas.
  4. Es importante establecer la calidad de interpolación para el contexto más bajo que el valor predeterminado (por CGContextSetInterpolationQuality). Recomendaría usar (IS_RETINA_DISPLAY ? kCGInterpolationNone : kCGInterpolationLow) . Las macros IS_RETINA_DISPLAY se toman de here .
  5. Asegúrese de obtener CGColorSpaceRef de CGColorSpaceCreateDeviceRGB () o similar al crear un contexto. Se informaron algunos problemas de rendimiento para obtener espacio de color fijo en lugar de solicitar el del dispositivo.
  6. Heredar la clase de vista de UIImageView y simplemente establecer una imagen propia de la imagen creada a partir del contexto me resultó útil. Sin embargo, lea sobre el uso de UIImageView primero si desea hacer esto, ya que requiere algunos cambios en la lógica del código (porque drawRect: ya no se llama).
  7. Y si puede evitar escalar su imagen en el momento del dibujo real, intente hacerlo. Dibujar imágenes sin escalar es significativamente más rápido; desafortunadamente, para mí eso no era una opción.