36 votes

Le moyen le plus efficace de dessiner une partie d'une image dans iOS

Compte tenu de l' UIImage et CGRect, ce qui est la façon la plus efficace (en mémoire et en temps) pour dessiner la partie de l'image correspondant à l' CGRect (sans mise à l'échelle)?

Pour référence, c'est la façon dont je suis actuellement le faire:

- (void)drawRect:(CGRect)rect {
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGRect frameRect = CGRectMake(frameOrigin.x + rect.origin.x, frameOrigin.y + rect.origin.y, rect.size.width, rect.size.height);    
    CGImageRef imageRef = CGImageCreateWithImageInRect(image_.CGImage, frameRect);
    CGContextTranslateCTM(context, 0, rect.size.height);
    CGContextScaleCTM(context, 1.0, -1.0);
    CGContextDrawImage(context, rect, imageRef);
    CGImageRelease(imageRef);
}

Malheureusement, cela semble extrêmement lent avec des moyennes des images et un haut setNeedsDisplay de la fréquence. Jouer avec UIImageViews'image et clipToBounds produit de meilleurs résultats (avec moins de flexibilité).

78voto

Eonil Points 19404

J'ai deviné que vous faites cela pour afficher une partie d'une image sur l'écran, parce que vous avez mentionné UIImageView. Et les problèmes d'optimisation toujours besoin de définir plus précisément.


Confiance à Apple pour Régulière des éléments d'INTERFACE utilisateur

En fait, UIImageView avec clipsToBounds est l'un de la manière la plus rapide/la plus simple des façons d'archiver votre but si votre objectif est simplement couper une zone rectangulaire d'une image (pas trop gros). Aussi, vous n'avez pas besoin d'envoyer de l' setNeedsDisplay message.

Ou vous pouvez essayer de mettre la UIImageView à l'intérieur d'un vide UIView et set clipping au conteneur de vue. Avec cette technique, vous pouvez transformer votre image librement par le paramètre transform de la propriété en 2D (mise à l'échelle, rotation, translation).

Si vous avez besoin de transformation 3D, vous pouvez toujours utiliser CALayer avec masksToBounds de la propriété, mais à l'aide de CALayer vous donnera de très peu de performances supplémentaires habituellement ne pas considérable.

De toute façon, vous avez besoin de connaitre tous les détails de bas niveau pour les utiliser correctement pour l'optimisation.


Pourquoi est-ce que l'un des moyens les plus rapides?

UIView est juste une fine couche sur le dessus de l' CALayer qui est appliqué sur le dessus de l'OpenGL qui est pratiquement une interface directe avec le GPU. Cela signifie UIKit est accéléré par GPU.

Donc, si vous les utilisez correctement (je veux dire, à l'intérieur conçu limites), il effectuera ainsi que simple OpenGL mise en œuvre. Si vous utilisez seulement un peu d'images à afficher, vous obtiendrez des performances acceptables avec UIView mise en œuvre, car il peut faire plein de l'accélération de la sous-jacentes OpenGL (qui signifie l'accélération GPU).

De toute façon si vous avez besoin extrême d'optimisation pour des centaines d'animation des sprites avec finement réglé pixel shaders comme dans un jeu de l'app, vous devriez vous utiliser OpenGL directement, car CALayer manque beaucoup d'options pour l'optimisation des niveaux inférieurs. De toute façon, au moins pour l'optimisation des trucs de l'INTERFACE utilisateur, il est incroyablement difficile d'être mieux qu'Apple.


Pourquoi votre méthode est plus lente que UIImageView?

Ce que vous devez savoir, c'est tout au sujet de l'accélération GPU. Dans tous les derniers ordinateurs, des graphiques rapides performances qu'avec le GPU. Ensuite, le point est de savoir si la méthode que vous utilisez est mis en œuvre sur le dessus de la carte graphique ou pas.

OMI, CGImage méthodes de dessin ne sont pas mis en œuvre avec le GPU. Je pense que j'ai lu mentionner à ce sujet sur la documentation d'Apple, mais je ne me souviens pas où. Donc je ne suis pas sûr à ce sujet. De toute façon, je crois, CGImage est mis en œuvre dans le PROCESSEUR car,

  1. Son API dirait qu'il a été conçu pour CPU, tels que bitmap interface d'édition de texte et de dessin. Ils ne correspondent pas à un GPU interface très bien.
  2. Bitmap contexte de l'interface permet un accès direct à la mémoire. Cela signifie qu'il est backend de stockage est situé dans la mémoire de l'unité centrale. Peut-être un peu différent sur l'architecture de mémoire unifiée (et aussi avec le Métal de l'API), mais de toute façon, la conception initiale de l'intention de l' CGImage devrait être pour le CPU.
  3. De nombreux récemment publié Apple d'autres Api de mentionner l'accélération GPU de manière explicite. Cela signifie que leurs aînés Api ne sont pas. Si il n'y a pas de mention spéciale, ce qui se fait habituellement dans le CPU par défaut.

Il semble donc tout à fait dans l'UC. Les opérations graphiques fait de l'unité centrale sont beaucoup plus lent que dans le GPU.

Simplement de détourage d'une image et de composition de l'image couches sont très simples et à bas prix des opérations pour les GPU (par rapport à l'UC), de sorte que vous pouvez vous attendre au UIKit bibliothèque utilisera cette parce que tout UIKit est implémenté sur OpenGL.


Au Sujet Des Limites

Parce que l'optimisation est une sorte de travail sur la micro-gestion, de chiffres précis et les petits faits sont très importants. Quelle est la taille moyenne? OpenGL sur iOS généralement des limites de taille de texture maximale de 1024 x 1024 pixels (peut-être plus dans les dernières versions). Si votre image est plus grande que cela, il ne fonctionne pas, ou les performances seront dégradées considérablement (je pense que UIImageView est optimisé pour les images dans les limites).

Si vous avez besoin d'afficher des images énormes avec écrêtage, vous devez utiliser une autre optimisation comme CATiledLayer et c'est une histoire totalement différente.

Et n'allez pas OpenGL, sauf si vous voulez connaître tous les détails de l'OpenGL. Il a besoin de plein de compréhension sur bas niveau des graphismes et 100 fois plus de code au moins.


Sur Un Avenir

Si elle n'est pas très susceptibles de se produire, mais CGImage aliments pour animaux (ou autre chose) n'a pas besoin d'être coincé dans le CPU seul. N'oubliez pas de vérifier la technologie de base de l'API que vous utilisez. Encore, GPU étoffes sont très différentes monstre de la CPU, puis de l'API gars généralement clairement et explicitement mention.

5voto

Scott Lahteine Points 735

Il serait, en définitive, être plus rapide, avec beaucoup moins de la création de l'image du sprite atlas, si vous pouviez définir non seulement l'image pour une UIImageView, mais aussi le haut à gauche décalage à l'affichage à l'intérieur de cette UIImage. C'est peut-être possible.

Pendant ce temps, j'ai créé ces fonctions utiles dans une classe utilitaire que j'utilise dans mes applications. Il crée une UIImage de la partie d'un autre UIImage, avec des options de rotation, échelle, et de le retourner à l'aide de la norme UIImageOrientation valeurs à spécifier.

Mon application crée beaucoup de UIImages lors de l'initialisation, et cela prend nécessairement du temps. Mais certaines images ne sont pas nécessaires jusqu'à un certain onglet est sélectionné. Pour donner l'apparence de chargement plus rapide j'ai pu créer dans un thread donné naissance au démarrage, juste attendre jusqu'à ce qu'il le fait si cet onglet est sélectionné.

+ (UIImage*)imageByCropping:(UIImage *)imageToCrop toRect:(CGRect)aperture {
    return [ChordCalcController imageByCropping:imageToCrop toRect:aperture withOrientation:UIImageOrientationUp];
}

// Draw a full image into a crop-sized area and offset to produce a cropped, rotated image
+ (UIImage*)imageByCropping:(UIImage *)imageToCrop toRect:(CGRect)aperture withOrientation:(UIImageOrientation)orientation {

            // convert y coordinate to origin bottom-left
    CGFloat orgY = aperture.origin.y + aperture.size.height - imageToCrop.size.height,
            orgX = -aperture.origin.x,
            scaleX = 1.0,
            scaleY = 1.0,
            rot = 0.0;
    CGSize size;

    switch (orientation) {
        case UIImageOrientationRight:
        case UIImageOrientationRightMirrored:
        case UIImageOrientationLeft:
        case UIImageOrientationLeftMirrored:
            size = CGSizeMake(aperture.size.height, aperture.size.width);
            break;
        case UIImageOrientationDown:
        case UIImageOrientationDownMirrored:
        case UIImageOrientationUp:
        case UIImageOrientationUpMirrored:
            size = aperture.size;
            break;
        default:
            assert(NO);
            return nil;
    }


    switch (orientation) {
        case UIImageOrientationRight:
            rot = 1.0 * M_PI / 2.0;
            orgY -= aperture.size.height;
            break;
        case UIImageOrientationRightMirrored:
            rot = 1.0 * M_PI / 2.0;
            scaleY = -1.0;
            break;
        case UIImageOrientationDown:
            scaleX = scaleY = -1.0;
            orgX -= aperture.size.width;
            orgY -= aperture.size.height;
            break;
        case UIImageOrientationDownMirrored:
            orgY -= aperture.size.height;
            scaleY = -1.0;
            break;
        case UIImageOrientationLeft:
            rot = 3.0 * M_PI / 2.0;
            orgX -= aperture.size.height;
            break;
        case UIImageOrientationLeftMirrored:
            rot = 3.0 * M_PI / 2.0;
            orgY -= aperture.size.height;
            orgX -= aperture.size.width;
            scaleY = -1.0;
            break;
        case UIImageOrientationUp:
            break;
        case UIImageOrientationUpMirrored:
            orgX -= aperture.size.width;
            scaleX = -1.0;
            break;
    }

    // set the draw rect to pan the image to the right spot
    CGRect drawRect = CGRectMake(orgX, orgY, imageToCrop.size.width, imageToCrop.size.height);

    // create a context for the new image
    UIGraphicsBeginImageContextWithOptions(size, NO, imageToCrop.scale);
    CGContextRef gc = UIGraphicsGetCurrentContext();

    // apply rotation and scaling
    CGContextRotateCTM(gc, rot);
    CGContextScaleCTM(gc, scaleX, scaleY);

    // draw the image to our clipped context using the offset rect
    CGContextDrawImage(gc, drawRect, imageToCrop.CGImage);

    // pull the image from our cropped context
    UIImage *cropped = UIGraphicsGetImageFromCurrentImageContext();

    // pop the context to get back to the default
    UIGraphicsEndImageContext();

    // Note: this is autoreleased
    return cropped;
}

3voto

malex Points 2992

Le moyen très simple de déplacer de gros de l'image à l'intérieur de UIImageView comme suit.

Laissez-nous avons l'image de taille (100, 400) représentant les 4 membres de certaines images l'une en dessous de l'autre. Nous voulons montrer la 2ème photo ayant offsetY = 100 en place UIImageView de taille (100, 100). La solution est:

UIImageView *iView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
CGRect contentFrame = CGRectMake(0, 0.25, 1, 0.25);
iView.layer.contentsRect = contentFrame;
iView.image = [UIImage imageNamed:@"NAME"];

Ici contentFrame est normalisé cadre relative à la vraie UIImagetaille. Ainsi, "0" signifie que nous commençons la partie visible de l'image à partir de la bordure gauche, "0.25" signifie que nous avons décalage vertical 100, "1" signifie que nous voulons montrer toute la largeur de l'image, et enfin, "0.25" signifie que nous voulons montrer à seulement 1/4 de la partie de l'image en hauteur.

Ainsi, à l'image des coordonnées, nous montrons l'image suivante

CGRect visibleAbsoluteFrame = CGRectMake(0*100, 0.25*400, 1*100, 0.25*400)
or CGRectMake(0, 100, 100, 100);

1voto

David Dunham Points 4620

Plutôt que de créer une nouvelle image (qui est coûteuse car elle alloue de la mémoire), pourquoi ne pas utiliser CGContextClipToRect ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X