Redimensionner une UIImage

AliGatorAliGator Membre, Modérateur
juillet 2010 modifié dans Actualités #1
Suite à  ce post, je vous propose ici une méthode permettant de redimensionner une UIImage, ou plutôt de créer une version miniature d'une UIImage existante.

C'est par ici :
http://ressources.mediabox.fr/tutoriaux/apple/redimensionner_uiimage

Réponses

  • apocaalypsoapocaalypso Membre
    20:27 modifié #2
    Merci Ali, parce que moi j'en avais une mais en C que j'avais trouvé sur Internet :
    UIImage* resizedImage(UIImage *inImage, CGRect thumbRect)<br />{<br />	CGImageRef			imageRef = [inImage CGImage];<br />	CGImageAlphaInfo	alphaInfo = CGImageGetAlphaInfo(imageRef);<br />	<br />	// There&#39;s a wierdness with kCGImageAlphaNone and CGBitmapContextCreate<br />	// see Supported Pixel Formats in the Quartz 2D Programming Guide<br />	// Creating a Bitmap Graphics Context section<br />	// only RGB 8 bit images with alpha of kCGImageAlphaNoneSkipFirst, kCGImageAlphaNoneSkipLast, kCGImageAlphaPremultipliedFirst,<br />	// and kCGImageAlphaPremultipliedLast, with a few other oddball image kinds are supported<br />	// The images on input here are likely to be png or jpeg files<br />	if (alphaInfo == kCGImageAlphaNone)<br />		alphaInfo = kCGImageAlphaNoneSkipLast;<br />	<br />	// Build a bitmap context that&#39;s the size of the thumbRect<br />	CGContextRef bitmap = CGBitmapContextCreate(<br />												NULL,<br />												thumbRect.size.width,		// width<br />												thumbRect.size.height,		// height<br />												CGImageGetBitsPerComponent(imageRef),	// really needs to always be 8<br />												4 * thumbRect.size.width,	// rowbytes<br />												CGImageGetColorSpace(imageRef),<br />												alphaInfo<br />												);<br />	<br />	// Draw into the context, this scales the image<br />	CGContextDrawImage(bitmap, thumbRect, imageRef);<br />	<br />	// Get an image from the context and a UIImage<br />	CGImageRef	ref = CGBitmapContextCreateImage(bitmap);<br />	UIImage*	result = [UIImage imageWithCGImage:ref];<br />	<br />	CGContextRelease(bitmap);	// ok if NULL<br />	CGImageRelease(ref);<br />	<br />	return result;<br />}
    


    UIImage *nouvelleImage = resizedImage(image, CGRectMake(0, 0, 240, 128));
    
  • AliGatorAliGator Membre, Modérateur
    20:27 modifié #3
    Je vois que dans le code que tu postes, il rajoute une condition lui aussi sur le alphaInfo...

    Moi j'ai traité que le cas où y'avait une couche alpha et dans ce cas j'ai adapté la constante (car comme il dit dans ses commentaires, dans le Quartz2D Programming Guide y'a la liste des formats de contexte supportés et ils ne sont pas tous disponibles) avec un PremultipliedFirst/PremultipliedLast...

    Mais comme je le dis dans mon petit aparté en gris, je suis pas sûr d'avoir traité tous les cas pour l'alpha... Pour l'instant ça m'avait l'air de marcher avec ce que j'ai testé comme cas, mais faut p'tet en rajouter, genre justement le cas de AlphaNone en plus des 2 autres "if" que je fais sur ce alphaInfo...
  • Philippe49Philippe49 Membre
    20:27 modifié #4
    Thanks to Ali , le Grand Caà¯man 

    détail : M_PI/2 peut s'écrire M_PI_2.

    Des macros de math.h
    • Elles sont assez faciles à  retenir le "sur" étant traduit par "underscore".
    • consulter /usr/include/math.h pour plus d'infos

    M_E e
    M_LOG2E log2(e)
    M_LOG10E log10(e)
    M_LN2 ln(2)
    M_LN10 ln(10)
    M_PI π
    M_PI_2 π/2
    M_PI_4 π/4
    M_1_PI 1/Ï€
    M_2_PI 2/Ï€
    M_2_SQRTPI 2/√(π)
    M_SQRT2 √(2)
    M_SQRT1_2 1/√(2) 
  • AliGatorAliGator Membre, Modérateur
    20:27 modifié #5
    Ah oui tiens je la connaissais aussi cette macro M_PI_2 mais je l'ai oubliée au moment de coder ça ^^ Bien vu ;)

    (Du coup j'ai corrigé dans le message d'origine qui servira à  priori de snippet de référence)
  • Philippe49Philippe49 Membre
    20:27 modifié #6
    Pourquoi faut-il changer le alphaInfo dans ce code.
    On aurait tendance à  faire :

    CGBitmapInfo alphaInfo = CGImageGetBitmapInfo(img);	<br />	CGContextRef ctx = CGBitmapContextCreate(&nbsp;  .... alphaInfo ) ;<br />
    


    ?
  • Philippe49Philippe49 Membre
    août 2009 modifié #7
    dans 1250851399:

    Je vois que dans le code que tu postes, il rajoute une condition lui aussi sur le alphaInfo...

    Ici , je lis :
    <br />kCGImageAlphaNone"equivalent to kCGImageAlphaNoneSkipLast.
    


    Pourquoi ce test si les deux sont équivalents ?


    Par ailleurs, il y a quelque chose que je ne pige pas. Pour moi, il y a trois "images"
    • L'image initiale
    • Le contenu du BitmapContext après CGContextDrawImage(),
    • Et celle créée image par CGBitmapContextCreateImage()
    Pourquoi faudrait-il faire un lien entre les alphaInfo de ces trois images ?
    Comme la doc a l'air de dire que le mode premultiplied est plus rapide à  l'utilisation, je serais tenté de ne pas m'occuper du alphaInfo de l'image initial, et de prendre kCGImageAlphaPremultipliedFirst par exemple  dans la création du bitmap.
    Après si l'image est en kCGImageAlphaNone, peu importe, la fonction CGContextDrawImage() est sensée savoir se débrouiller pour faire le dessin correct dans mon context.

    Me gourres-je ?
  • AliGatorAliGator Membre, Modérateur
    20:27 modifié #8
    Il faut s'occuper du alphaInfo car les contextes graphiques sur iPhone ne supportent qu'un nombre limité de combinaisons.
    Par exemple on ne peut pas créer une image RGB 32 bits avec kCGImageAlphaFirst. Si on le fait on a un warning à  l'exécution nous indiquant que CGCreateBitmapContext ne supporte pas cette combinaison d'arguments, en cela a pour effet que cette fonction nous retourne un CGContext NULL, donc toute la suite foire.

    Voilà  pourquoi on est obligé de gérer ce cas de alphaInfo, que je ne gérais pas avant d'être confronté au problème et de réaliser cette limitation.

    Comme le disent les commentaires dans l'autre bout de code cité par apocaalypso, ceci est indiqué dans "Supported Pixel Formats" dans le "Quartz 2D Programming Guide". On voit que pour le colorspace RGB, tous les modes utilisant l'alpha sont de l'alpha prémultiplié (PremultipliedFirst ou PremultipledLast) et que les autres modes contenant de l'alpha ne sont pas supportés.
  • AliGatorAliGator Membre, Modérateur
    20:27 modifié #9
    C'est vrai que l'on pourrait imposer simplement un alphaInfo, genre kCGAlphaPremultipliedFirst, dans tous les cas. Quel que soit le mode alpha (présent ou non, prémultiplié ou non, first ou last) de l'image source.

    C'est juste que j'étais parti pour garder le colorspace de l'image d'origine parce que là  même si seul un oeil de graphiste en général peut voir la différence et qu'en général pour les autres elle ne saute pas aux yeux, toutes les couleurs d'un espace colorimétrique ne peuvent être forcément représenté dans un autre espace colorimétrique, il n'y a pas bijection parfaite (les gammuts des espaces colorimétriques ne sont pas identiques).

    Du coup dans la foulée j'ai voulu conserver aussi le alphaInfo au plus proche : dans un premier temps j'avais, tout comme pour le colorspace, gardé le même alphaInfo que l'image d'origine. Quand j'ai vu la limitation, j'ai rajouté le test pour exclure les cas interdits et les remplacer par le plus proche... Mais c'est vrai qu'en en imposant un ça devrait marcher aussi.


    Par contre je suis plutôt partant pour garder un contexte sans alpha si l'image d'origine n'a pas de couche alpha (donc n'avoir que 2 cas : kCGAlphaNone et kCGAlphaPremulitpliedLast par exemple, selon si l'image d'origine avait une couche alpha ou non), pour des raisons évidentes tant de potentielle occupation mémoire réduite (nous permettant de réduire à  24 bpp au lieu de 32) que de limitation des calculs à  effectuer (puisqu'il est conseillé, d'autant plus sur iPhone même si c'est surtout vrai pour l'affichage et sans doute moins pour les rendus offscreen mais quand même, de se passer de la couche alpha dans la mesure du possible pour ne travailler qu'avec de l'opaque), et pour ainsi limiter les compositions inutiles.
  • Philippe49Philippe49 Membre
    20:27 modifié #10
    Merci pour tes explications.
  • Philippe49Philippe49 Membre
    20:27 modifié #11
    Il n'y aurait pas une confusion entre l'énumération CGImageAlphaInfo et l'énumération CGBitmapInfo qui est ce que l'on doit mettre dans le dernier argument de CGBitmapContextCreate() ?
  • AliGatorAliGator Membre, Modérateur
    20:27 modifié #12
    Oui j'ai vu et suis conscient de cette "confusion"... Si tu trouves plus d'explications je suis preneur, parce qu'en fait pour cette partie j'avoue ne pas avoir testé exhaustivement (je le mets d'ailleurs dans mon message où je met le code) mais j'ai pourtant vu des versions de doc avec un AlphaInfo et d'autres avec un BitmapInfo, ça m'a un peu perturbé.
    C'est pas impossible que je me sois mélangé les pinceaux là  dessus (ou genre introduction de BitmapInfo qui serait nouveau avec le SDK 3.0 un truc du genre ?), faudrait creuser y compris avec des images de formats divers et variés en terme tant de format que d'alpha, de colorspace, de codage...
  • Philippe49Philippe49 Membre
    août 2009 modifié #13
    dans 1251069682:

    mais j'ai pourtant vu des versions de doc avec un AlphaInfo et d'autres avec un BitmapInfo, ça m'a un peu perturbé.

    Le code trouvé par Apoocalypso en est un exemple


    Les énumérations ne prennent pas les mêmes valeurs. Comment la fonction s'y retrouve-t-elle ?

    enum {<br />&nbsp;  kCGBitmapAlphaInfoMask = 0x1F,<br />&nbsp;  kCGBitmapFloatComponents = (1 &lt;&lt; 8),<br />&nbsp;  <br />&nbsp;  kCGBitmapByteOrderMask = 0x7000,<br />&nbsp;  kCGBitmapByteOrderDefault = (0 &lt;&lt; 12),<br />&nbsp;  kCGBitmapByteOrder16Little = (1 &lt;&lt; 12),<br />&nbsp;  kCGBitmapByteOrder32Little = (2 &lt;&lt; 12),<br />&nbsp;  kCGBitmapByteOrder16Big = (3 &lt;&lt; 12),<br />&nbsp;  kCGBitmapByteOrder32Big = (4 &lt;&lt; 12)<br />};<br />typedef uint32_t CGBitmapInfo;<br />
    


    <br />enum CGImageAlphaInfo {<br />&nbsp;  kCGImageAlphaNone,<br />&nbsp;  kCGImageAlphaPremultipliedLast,<br />&nbsp;  kCGImageAlphaPremultipliedFirst,<br />&nbsp;  kCGImageAlphaLast,<br />&nbsp;  kCGImageAlphaFirst,<br />&nbsp;  kCGImageAlphaNoneSkipLast,<br />&nbsp;  kCGImageAlphaNoneSkipFirst<br />};<br />typedef enum CGImageAlphaInfo CGImageAlphaInfo;
    



    [EDIT]  :) :) on a même le droit à  la faute de frappe  CGImageBitmapInfo dans la doc    :'( :'( B) :why?: :why?:
  • AliGatorAliGator Membre, Modérateur
    20:27 modifié #14
    Eh bien il fait le distingo car il sait qu'il peut recevoir les 2 types de contantes j'imagine ?
    Car si on regarde dans le CGImage.h on a [tt]typedef uint32_t CGBitmapInfo; /* Available in MAC OS X 10.4 & later. */[/tt] donc après il peut regarder les valeurs des divers bits via un masquage et faire un mix de ces 2 enums/constantes.

    Mais bon j'avoue que ça reste pas clair. Dans la doc de la fonction on voit bien CGBitmapInfo, dans l'utilisation et les exemples du Programming Guide on voit les constantes émanant de l'enum CGAlphaInfo...
  • Philippe49Philippe49 Membre
    20:27 modifié #15

    You use the constant kCGBitmapFloatComponents to indicate a bitmap format that uses floating-point values. For floating-point formats, you logically OR this constant with the appropriate constant from the previous list. For example, for a 128 bits per pixel floating-point format that uses premultiplied alpha, with the alpha located in the least significant bits of each pixel, you supply the following information to Quartz:

    kCGImageAlphaPremultipliedLast|kCGBitmapFloatComponents


    Du coup il ferait un masquage (& 0x0F par exemple) pour obtenir le choix en alpha, et un autre sur la partie haute pour le nombre de bits/pixel. Le double rôle de CGBitmapInfo n'étant qu'une maladresse. C'est ce qui est le plus compatible avec la doc, en effet.
  • muqaddarmuqaddar Administrateur
    juin 2010 modifié #16
    dans 1251050839:

    Il faut s'occuper du alphaInfo car les contextes graphiques sur iPhone ne supportent qu'un nombre limité de combinaisons.
    Par exemple on ne peut pas créer une image RGB 32 bits avec kCGImageAlphaFirst. Si on le fait on a un warning à  l'exécution nous indiquant que CGCreateBitmapContext ne supporte pas cette combinaison d'arguments, en cela a pour effet que cette fonction nous retourne un CGContext NULL, donc toute la suite foire.


    Alors, je viens de tester dans le simulateur avec le pickerView, et voilà  le message que j'ai :

    Mon Sep 28 17:03:25 hoksitan.home[42748] &lt;Error&gt;: CGBitmapContextCreate: unsupported parameter combination: 8 integer bits/component; 32 bits/pixel; 3-component colorspace; kCGImageAlphaNoneSkipFirst; 1280 bytes/row.<br />Mon Sep 28 17:03:25 hoksitan.home[42748] &lt;Error&gt;: CGContextConcatCTM: invalid context<br />Mon Sep 28 17:03:25 hoksitan.home[42748] &lt;Error&gt;: CGContextConcatCTM: invalid context<br />Mon Sep 28 17:03:25 hoksitan.home[42748] &lt;Error&gt;: CGContextConcatCTM: invalid context<br />Mon Sep 28 17:03:25 hoksitan.home[42748] &lt;Error&gt;: CGContextDrawImage: invalid context<br />Mon Sep 28 17:03:25 hoksitan.home[42748] &lt;Error&gt;: CGBitmapContextCreateImage: invalid context
    


    Donc, quelle est la mauvaise combinaison employée ?
    J'ai utilisé ton dernier code (en haut).

    J'avoue que votre discussion me dépasse un peu. ;)
  • muqaddarmuqaddar Administrateur
    septembre 2009 modifié #17
    Bon, vu que la photo choisie est en kCGImageAlphaNoneSkipFirst, j'ai vu que dans ce cas il faut 5 bits/component, et non 8...

    Donc avec ceci :

    CGContextRef ctx = CGBitmapContextCreate(NULL, dstSize.width, dstSize.height, 5, 0, CGImageGetColorSpace(img), alphaInfo);
    


    le code fonctionne.

    Il faut donc rajouter une variable supplémentaire poue gérer le nombre de bitsPerComponents en fonction de l'alphaInfo ?

    Voir ici : http://developer.apple.com/mac/library/qa/qa2001/qa1037.html
  • AliGatorAliGator Membre, Modérateur
    20:27 modifié #18
    Heu là  je capte pas trop non plus, vu que la combinaison que tu sembles avoir, pour moi ça correspond à  la 4e ligne du tableau : RGB, 8 BPC, kCGImageAlphaNoneSkipFirst et donc du coup 32 bits/pixels...
    Donc bah heu je vois pas trop (et à  vrai dire je trouve que c'est un peu prise de tête ce truc là ... ça doit pas être bien sorcier mais penser à  tous les cas ça me met un mal de crâne :D)
  • muqaddarmuqaddar Administrateur
    20:27 modifié #19
    D'autant plus qu'avec :

    if (alphaInfo == kCGImageAlphaNone)
    alphaInfo = kCGImageAlphaNoneSkipLast;

    ça semble marcher pour tout en restant sur 8 bits... :-)

    Bon, merci en tout cas, ta catégorie fait bien le boulot de redimmensionnement avec contraintes. ;-) Beau boulot.


  • muqaddarmuqaddar Administrateur
    20:27 modifié #20
    J'ai un petit soucis de mémoire que je n'arrive pas à  comprendre ni régler avec le debugguer.

    <br />UIImage *thumbImage;<br />NSData * thumbImageData;<br /><br />thumbImage = [image resizedImageToFitInSize:CGSizeMake(75.0, 75.0) scaleIfSmaller:NO];		<br />thumbImageData = [NSData dataWithData:UIImageJPEGRepresentation(thumbImage, 0.60)];<br />
    


    J'ai ce message après la dernière ligne :
    malloc: *** error for object 0x1066000: pointer being freed was not allocated<br />*** set a breakpoint in malloc_error_break to debug
    


    Je ne vois absolument pas ce que je pourrais releaser ici que je n'ai pas attribué !
    L'appli ne plante pas, mais je n'aime pas ça.




  • AliGatorAliGator Membre, Modérateur
    20:27 modifié #21
    Et si tu n'utilises pas ma méthode resizedImageToFitInSize mais que tu appliques uniquement ta 2e ligne (sur ta grande image "image" et non la miniature "thumbImage", donc), tu as le même warning dans tes logs ?

    PS : Quel est l'intéret de passer par dataWithData ? et non pas d'attribuer directement le résultat de UIImageJPEGRepresentation à  thumbImageData ?
  • muqaddarmuqaddar Administrateur
    20:27 modifié #22
    dans 1255091830:

    PS : Quel est l'intéret de passer par dataWithData ? et non pas d'attribuer directement le résultat de UIImageJPEGRepresentation à  thumbImageData ?


    Aucun, je faisais juste un test par désespoir ce matin...

    dans 1255091830:

    Et si tu n'utilises pas ma méthode resizedImageToFitInSize mais que tu appliques uniquement ta 2e ligne (sur ta grande image "image" et non la miniature "thumbImage", donc), tu as le même warning dans tes logs ?


    Alors, plus d'erreur si je commente la ligne qui redimmensionne !!
    Ce matin, je suis allé voir ta méthode, et tout me paraissait nikel, tu renvoyais une image autoreleasée, du classique quoi.

    Mieux :

    // reduce fullImage<br />		fullImage = [image resizedImageToFitInSize:self.view.bounds.size scaleIfSmaller:NO];		<br />		fullImageData = UIImageJPEGRepresentation(fullImage, 0.75);<br />		<br />		// reduce thumbImage<br />		thumbImage = [image resizedImageToFitInSize:CGSizeMake(75.0, 75.0) scaleIfSmaller:NO];		<br />		thumbImageData = UIImageJPEGRepresentation(thumbImage, 0.60);
    


    Pas d'erreur pour la fullImage, mais erreur pour l'image qui doit être réduite... T'as une idée ? (c'est ds le simulateur, et les images font la tailles de l'écran de l'iPhone)
  • AliGatorAliGator Membre, Modérateur
    20:27 modifié #23
    Non, je vois pas trop là  (et pas le temps dernièrement, ayant deux projets en parallèle à  mener...)

    Essaye avec une taille multiple de 4 ou puissance de deux au lieu de 75x75 pour essayer d'isoler le pb ?
    Essaye avec des valeurs de compression autre que 0.75 ou 0.60 ?
  • muqaddarmuqaddar Administrateur
    20:27 modifié #24
    dans 1255099343:

    Non, je vois pas trop là  (et pas le temps dernièrement, ayant deux projets en parallèle à  mener...)

    Essaye avec une taille multiple de 4 ou puissance de deux au lieu de 75x75 pour essayer d'isoler le pb ?
    Essaye avec des valeurs de compression autre que 0.75 ou 0.60 ?


    Bon, je m'y suis remis (au bug).
    Alors à  force de tests, il s'avère qu'il survient si une des dimensions de l'image demandée est infèrieure à  sa taille réelle (donc pour tout ce qui est vignette miniature par exemple).

    Ainsi, je fais le test dans le simulateur, si l'image originale fait 320*215, le bug apparaà®t si je demande du 214*214, mais pas à  partir de 215*215... !!!

    J'ai mis des pointeurs partout dans ton code, et l'erreur apparaà®t à  la suite de son exécution MAIS PAS pendant que je le parcourre :

    thumbImage = [image resizedImageToFitInSize:CGSizeMake(75.0, 75.0) scaleIfSmaller:NO];		<br />		thumbImageData = UIImageJPEGRepresentation(thumbImage, 0.60);
    


    c'est à  dire à  la deuxième ligne ici...


    malloc: *** error for object 0x204d000: pointer being freed was not allocated
    *** set a breakpoint in malloc_error_break to debug
  • AliGatorAliGator Membre, Modérateur
    juin 2010 modifié #25
    Petit déterrage de sujet car j'ai fait une mise à  jour du code. De plus maintenant le code est versionné sur github, ce qui permet de suivre les changements et évolutions de version de ce code
    Du coup j'en ai profité pour mettre à  jour l'article sur le blog de PommeDev (qui pointe sur mon github maintenant comme ça l'article pointera tjs sur la dernière version du code).


    ChangeLog :
    • Je n'utilise plus CGBitmapContextCreate, la fameuse fonction pour laquelle on avait parfois des combinaisons {colospace+bitsPerComponent+alphaInfo} qui ne marchaient pas, faisant foirer la création du contexte bitmap et donc méthode...
    • A la place j'utilise UIGraphicsBeginImageContext, plus simple à  utiliser et surtout qui n'a plus ce problème
    • J'ai refait une passe sur la gestion des imageOrientation pour toutes les gérer correctement.


    J'ai testé (via le projet d'exemple que je fournis également sur GitHub d'ailleurs) toutes les orientations de l'iPhone lorsque je prend une photo de la camera, et toutes ces orientations sont bien prises en compte. J'ai également test avec des images de ma Library.
    Je n'ai pas testé avec des images diverses et variées provenant d'images du filesystem ou créées à  partir de NSData ou autre, mais bon.
  • muqaddarmuqaddar Administrateur
    juin 2010 modifié #26
    Merci pour la mise à  jour ! :-)
Connectez-vous ou Inscrivez-vous pour répondre.