Tourner une image

UniXUniX Membre
07:49 modifié dans API AppKit #1
Salut à  tous.

Je me trouve face à  un pb tout bête ...!
Je voudrais afficher une NSImage dans une custom view, mais en la tournant auparavant. Et je ne sais pas comment faire ....

J'ai préparé une NSAffineTansform, mais je ne sais pas comment l'appliquer ensuite à  l'image.
Merci pour vos suggestions.

Réponses

  • BruBru Membre
    07:49 modifié #2
    Tu n'as pas besoin de NSAffineTransform.

    A l'initialisation de ta customView, tu fais un
    [self setFrameRotation:180];
    et tout ce qui sera dessiner dans la méthode drawRect: le sera avec une rotation de 180° par exemple.

    A toi d'appliquer la rotation désirée.

    .
  • 07:49 modifié #3
    Quelle méthode utilises tu pour dessiner ton image ? Certaines tiennent compte de la rotation de la vue alors que d'autre non...
    Tu peux aussi utiliser setFlipped: sur une image.
  • UniXUniX Membre
    07:49 modifié #4
    Oui, mais ma custom view ne contient pas que cette image ....

    J'ai du code qui dessine ma custom view (dessin d'images, de texte ...) et à  un moment donné, je dois dessiner une image qu'il faut d'abord faire pivoter à  un angle donné.

    Pour l'instant, j'utilise drawAtPoint: fromRect: operation: fraction: pour dessiner les images.

    Un exemple simple qui explique bien ce que je veux faire : une pendule.
    Tu dessines d'abord le fond, puis tu dois dessiner ensuite les aiguilles en prenant soin de les orienter auparavant pour qu'elles indiquent l'heure exacte.
  • UniXUniX Membre
    07:49 modifié #6
    Ben en fait j'avais vu ces 2 topics, mais ce qui m'a bloqué, c'est que je ne comprends pas l'utilisation de concat ...

    Si quelqu'un peut m'éclairer ... :)
  • AliGatorAliGator Membre, Modérateur
    07:49 modifié #7
    Il faut voir le "traitement de ton image par une affineTransform" comme une sorte de "chaine de traitement".

    Tu peux mettre dans ta "chaine de traitement" plusieurs traitements, des briques qui s'empilent et qui s'executeront les unes après les autres.

    Le concat permet juste de rajouter la transformation affine à  la chaine de traitement de ton image.

    Quand tu n'as qu'une transformation affine à  faire tu peux ne pas voir l'intérêt.
    Mais si tu fais une rotation autour d'un point puis une rotation autour d'un autre, selon l'ordre dans lequel tu fais ces 2 rotation ça ne donnera pas le même résultat.
    Donc selon l'ordre dans lequel tu vas effectuer tes "concat" les rotations vont s'executer dans un ordre ou dans un autre.


    En fait je ne sais pas si tu as des restes de tes cours de maths sur les matrices et les transformations, mais une transformation affine se fait par multiplication matricielle bien souvent.
    Si A est ton point de départ et R ta transformation affine (rotation), alors le point résultat de la rotation de A est obtenu par B=R*A (multiplication matricielle).
    Si tu veux appliquer une rotation R puis une rotation S à  ton point A, dans cet ordre, tu pourras écrire B=R*A puis C=S*B=S*R*A.
    Conclusion ? C = (S*R)*A donc la composition des 2 rotations représentées par les matrices R et S revient à  une rotation correspondant à  la matrice S*R=T
    C'est ce que fait le "concat", il fait la multiplication de ta transformation avec celle "en mémoire"/"en cours"

    C'est comme cela que fonctionne AffineTransform en fait : l'avantage est que si tu lui demandes de faire une suite de 10 transformations affines, il va calculer le produit des 10 matrices (à  chaque fois que tu fais un "concat" il va multiplier la dernière matrice par celle que tu concat), et au final au moment d'appliquer la transformation, il n'aura qu'une matrice à  appliquer sur ton image, et non pas 10.
  • 07:49 modifié #8
    Dans ces cas-là , il est nettement mieux que tu postes à  la suite de ces sujets pour y demander des explications complémentaires, ou bien alors à  la limite si tu tiens à  créer un nouveau sujet de préciser que tu as déjà  vu ce qui était proposé et de dire en quoi l'explication est insuffisante. Je fais cette remarque en pensant aux éventuels visiteurs qui auraient le même problème (tout serait alors centralisé) ou même à  toi: on aurait directement pu répondre à  la question de fond plutôt que de tourner autour du pot avant de savoir que ce tu voulais un complément d'information sur un truc existant.

    Bon un élément de réponse maintenant: la clé de la compréhension pour les transformations affines est la notion d'état d'un contexte (le graphicsstate de save(restore)graphicsstate). L'état comprends différents éléments qui sont la position de l'origine, la rotation du contexte, un facteur de zoom,...

    le role de la transformation affine est de décrire les modifications d'état d'un contexte (déplacer l'origine, effectuer une rotation,....) et le concat veut tout simplement dire que tu souhaites appliquer la modification correspondante à  l'état du contexte courant.

    Pour une rotation, n'oublie pas qu'elle se fait autour de 0.0 et donc tu dois lui associer une translation pour que l'image soit correctement affichée.

    Je te conseille de chipoter avec l'exemple circleview (/Developer/Examples/AppKit).
  • UniXUniX Membre
    07:49 modifié #9
    Tu as raison Renaud, et c'est noté pour le prochain coup ...

    Bon, avec vos conseils, j'ai tenté des choses, mais pour l'instant en vain, mon image ne s'affiche pas ... :(

    Voilà  mon code à  l'heure actuelle :
    ... affichage de l&#39;image de fond de la vue<br />NSImage *aiguille = [NSImage imageNamed:@&quot;Aiguille_compteur&quot;];<br />NSAffineTransform *transformRotation = [NSAffineTransform transform];<br />NSAffineTransform *transformTranslation = [NSAffineTransform transform];<br />float angleRotation;<br /> ... calcul de l&#39;angle de rotation<br />[transformRotation rotateByDegrees:angleRotation];<br />[transformTranslation translateXBy:[aiguille size].width / 2.0 yBy:[aiguille size].width / 2.0];<br />[transformRotation appendTransform:transformTranslation];<br />		<br />[aiguille lockFocus];<br />[transformRotation concat];<br />[aiguille drawAtPoint:NSMakePoint(-[aiguille size].width/2,-[aiguille size].height/2) fromRect:NSZeroRect operation: NSCompositeSourceOver fraction: 1.0];<br />[aiguille unlockFocus];
    
  • avril 2006 modifié #10
    Bon, plusieurs remarques:
    1. tu n'es pas obligé de créer plusieurs transformations et de faire un append. Tu peux directement tout faire dans la meme.
    2. ne pas utiliser lock et unlock focus mais [NSGraphicsContext saveGraphicsState] et [NSGraphicsContext restoreGraphicsState].
    3. J'avais déjà  fait une rotation d'image (autour de son centre) il y a de ça un certain temps. La translation associée est beaucoup plus compliquée que ça, mais je ne m'en rappelle plus trop.
  • UniXUniX Membre
    avril 2006 modifié #11
    Ben là  je suis super perplexe .... car c'est pas que ça fonctionne mal, ça ne fonctionne pas du tout ...! Je dois avoir un gros soucis quelquepart ...

    J'ai simplifié en virant la translation dans un premier temps, et en affichant l'image à  un point arbitraire situé à  (30,30).

    Toujours pas d'image affichée ....

    ... affichage de l&#39;image de fond de la vue<br />NSImage *aiguille = [NSImage imageNamed:@&quot;Aiguille_compteur&quot;];<br />NSAffineTransform *transformRotation = [NSAffineTransform transform];<br />float angleRotation;<br /> ... calcul de l&#39;angle de rotation<br />[transformRotation rotateByDegrees:angleRotation];<br />		<br />[[NSGraphicsContext currentContext]saveGraphicsState];<br />[transformRotation concat];<br />[aiguille drawAtPoint:NSMakePoint(30,30) fromRect:NSMakeRect(0,0,[aiguille size].width,[aiguille size].height) operation: NSCompositeSourceOver fraction: 1.0];<br />[[NSGraphicsContext currentContext]restoreGraphicsState];
    
  • avril 2006 modifié #12
    Bon j'ai retrouver le bout de code. J'ai fait ça il y a quasi 2 ans, donc ne me demandez pas les détails, je serais incapable de vous répondre, mais si ça peut aider.

    (attention, la rotation est en radians, la rotation de l'image se fait autour de son centre, si tu veux dessiner une aiguille, n'oublie pas de modifier l'image pour que le point d'ancrage de l'aiguille soit au centre de l'image)

    [tt] NSAffineTransform *transform = [NSAffineTransform transform];
    float x, y,
    height = [image size].height,
    width = [image size].width;

    x = width/2 - (width * cos(_imageRotation) - height*sin(_imageRotation))/2;
    y = height/2 - (width * sin(_imageRotation) + height*cos(_imageRotation))/2;
    [transform translateXBy:x yBy:y];
    // je suppose que l'image était dessinée en 0,0 donc il faudra que tu ajoutes une translation
    // pour la positionner correctement, le ne fait pas dans le drawAtPoint (compte tenu des
    // transformations qui ont été faites, le point ne sera pas là  où tu penses
    [transform rotateByRadians:_imageRotation];



    NSGraphicsContext *context = [NSGraphicsContext currentContext];
    [context saveGraphicsState];
    [transform concat];
    [image drawAtPoint:NSZeroPoint
     fromRect:NSZeroRect
    operation:NSCompositeSourceOver
     fraction:_opacity];
    [context restoreGraphicsState];
    [/tt]
  • 07:49 modifié #13
    dans 1145030648:

    Ben là  je suis super perplexe .... car c'est pas que ça fonctionne mal, ça ne fonctionne pas du tout ...! Je dois avoir un gros soucis quelquepart ...

    J'ai simplifié en virant la translation dans un premier temps, et en affichant l'image à  un point arbitraire situé à  (30,30).


    Mais n'oublie pas qu'en faisant une transformation affine, le système de coordonées du contexte courant change. Pour donner un bete exemple, tu dessines ton aiguilles en 30,30 ...mais si tu appliques une transformation contenant par exemple juste une rotation de 45° (sens horlogique), le 30,30 du contexte courant est placé en 0,42 dans le système de coordonées de la vue. Je te laisse l'exercice si tu veux combiner à  celà  une translation.
  • AliGatorAliGator Membre, Modérateur
    avril 2006 modifié #14
    Je préviens tout de suite que je n'ai jamais pratiqué les NSAffineTransform en Cocoa, donc mon post est à  prendre en tant que tel.

    Mais Renaud, plutôt que de calculer à  la main le 'x' et le 'y' que tu dois appliquer pour la translation (avec tes cosinus/sinus), puis faire un translate et un rotate dans la même AffineTransform, je pense que tu aurais tout aussi bien pu faire :
    • 1) Une AffineTransform qui fait la translation de width/2, height/2, et tu la concat
    • 2) Une AffineTransform qui fait la rotation, et tu fais un concat

    (Ou peut-être dans l'ordre inverse je sais pas trop ? Mais je crois que c'est bien ce sens là )

    Et justement ce système de concat permettant de combiner les transformations affines, c'est Cocoa qui se chargera de faire la correction que toi tu as appliqué dans ton calcul de x et y avec ton cos et ton sin.
    C'est justement l'idée du concat. Tu lui dis "je vais faire une translation de mon image pour placer son centre en 0,0" et ensuite "suite à  cette transformation, je ferais une rotation de tant de degrés".

    Le concat se chargera de faire la combinaison (multiplication matricielle) et donc appliquera automatiquement les effets de la rotation sur la translation.

    Ca a l'avantage de séparer les blocs clairement (je fais d'abord une translation, que j'applique, puis une rotation) au lieu que tu te paluches les calculs à  la main au risque de te planter (translation et rotation simultanément)

    Pour plus d'infos si ça vous intéresse : quelques explications simples sur les transformations (rotations/translations) par des matrices 4x4
  • Eddy58Eddy58 Membre
    07:49 modifié #15
    dans 1145033097:

    • 1) Une AffineTransform qui fait la translation de width/2, height/2, et tu la concat
    • 2) Une AffineTransform qui fait la rotation, et tu fais un concat



    Il manque juste un 3e point pour retomber sur ses pattes : ;)
    3) Une translation de (-width/2,-height/2)
  • AliGatorAliGator Membre, Modérateur
    avril 2006 modifié #16
    Oui tout à  fait mais en fait justement c'est là  que je viens de tilter :

    La formule avec les cosinus/sinus de Renaud, c'est pas pour calculer les marges à  rajouter de sorte que ton image soit entièrement visible ??

    En effet imagine, on veut faire une rotation de 20° autour du centre de l'image.
    Donc on translate pour ramener le centre de l'image en 0,0, ensuite on fait la rotation, et ensuite... si on fait la translation inverse de {-w/2,-h/2}, on replace le centre de l'image là  où il était... mais l'image étant du coup "rotationnée" donc "de travers", y'a des coins de l'image qui partent dans les négatifs (et n'apparaissent donc pas si on dessine l'image en {0,0} car sortent de la NSView)

    Tu vois ce que je veux dire ?

    Après faut savoir ce qu'on veut faire exactement : juste une rotation d'un angle donné autour du centre de l'image... et c'est tout, ou bien la redéplacer ensuite pour qu'elle soit visible entièrement, donc en rajoutant les marges adéquates.

    Et c'est là  que je me demande si le calcul du cosinus/sinus de Renaud c'était pas juste pour calculer les marges à  rajouter...
    Une autre solution est de dessiner l'image en plein milieu de la NSView (enfin la dessiner au bon endroit de sorte que le centre de l'image soit au centre de la NSView) comme ça même quand l'image est tournée on la voit en entier...

    [Fichier joint supprimé par l'administrateur]
  • 07:49 modifié #17
    dans 1145035530:

    dans 1145033097:

    • 1) Une AffineTransform qui fait la translation de width/2, height/2, et tu la concat
    • 2) Une AffineTransform qui fait la rotation, et tu fais un concat



    Il manque juste un 3e point pour retomber sur ses pattes : ;)
    3) Une translation de (-width/2,-height/2)


    Presque. En fait il faut dessiner l'image en -width/2,-height/2 (et faire non une translation) et c'est bon, je viens de faire quelques tests. ça permet d'éviter les calculs avec les sinus/cosinus, ce qui n'est pas plus mal.
  • UniXUniX Membre
    07:49 modifié #18
    Bon, merci à  vous tous pour votre aide précieuse, qui me fait avancer dans la bonne direction.

    Voilà  mon besoin : il s'agit donc d'une aiguille de compteur. Je fais donc une translation pour amener le point de rotation de l'aiguille en 0,0. Ensuite je lui fais une rotation pour qu'elle indique la valeur appropriée, et enfin, je la translate pour l'amener au centre du cadran qui est au milieu de ma custom view.

    J'ai bien réussi les étapes 1 et 2 (mon code ci-dessous le fais parfaitement, et mon aiguille tourne autour du point 0,0), mais je bute sur le point 3.
    J'ai essayé en faisant une nouvelle NSAffineTransform avec un concat, en rajoutant une translation dans la première NSAffineTransform, en changeant le point de dessin de l'image, mais en vain ...

    ... affichage du cadran<br />NSImage *aiguille = [NSImage imageNamed:@&quot;Aiguille_compteur&quot;];<br />NSAffineTransform *transform = [NSAffineTransform transform];<br />float angleRotation;<br />... calcul de angleRotation<br />[transform rotateByDegrees:angleRotation];<br />[transform translateXBy:-([aiguille size].width / 2.0) yBy:-([aiguille size].height / 2.0)];<br />		<br />[NSGraphicsContext saveGraphicsState];<br />[transform concat];<br />[aiguille drawAtPoint:NSZeroPoint fromRect:NSZeroRect operation: NSCompositeSourceOver fraction: 1.0];<br />[NSGraphicsContext restoreGraphicsState];
    
  • AliGatorAliGator Membre, Modérateur
    avril 2006 modifié #19
    Bon après plusieurs essais voilà  ma conclusion sur les NSAffineTransform :
    (En espérant que ça t'aide ou que ça en aide d'autres à  conceptualiser les transformations et du coup résoudre ton problème)

    1) Faire tout dans une même NSAT ou faire plusieurs NSAT avec des concat ne change rien : qu'on fasse
    [transform1 translateXBy:width/2 yBy:height/2];<br />[transform2 rotateByRadians:_imageRotation];
    
    puis qu'on fasse un [tt][transform1 concat][/tt] d'abord, et [tt][transform2 concat][/tt] ensuite, tout ça revient à  utiliser un seul transform et appliquer le [tt]translateXBy:yBy:[/tt] et le [tt]rotateByRadians:[/tt], dans cet ordre, sur la même NSAffineTransform.



    2) Les transformations se font dans l'ordre inverse de leur application. En tout cas c'est la façon de voir les choses qui me semble la plus simple à  appréhender.
    [transform translateXBy:width/2 yBy:height/2];     // (1)<br />[transform rotateByRadians:_imageRotation];        // (2)<br />[transform translateXBy:-width/2 yBy:-height/2];   // (3)<br />
    

    Ce code est à  comprendre ainsi :
    • (3) = déplacer l'image d'un delta négatif pour placer le centre de l'image au centre du repère (si on ne laisse que (3) et qu'on commente (1) et (2), c'est ce qui se passe)
    • (2) = faire une rotation autour du centre du repère (qui se trouve aussi être le centre de l'image grace au (3))
      Si on s'arrête là  (qu'on commente la ligne (1)), l'image est dessinée avec son centre en 0,0.
    • (1) = on redécale l'image de {width/2,height/2} pour replacer le centre de l'image là  où il était. On aurait pu aussi commenter ce point (1) et faire un drawAtPoint en width/2, height/2, qui va dessiner l'image en plaçant le (0,0) de ce "repère attaché à  l'image" au point {w/2,h/2} de la NSView.


    Juste pour s'en assurer, si on commente le (3) et laisse que le (1) et le (2), si vous avez bien suivi, ça va faire :
    • une rotation autour du point en {0,0} qui pour le coup est le coin de l'image (coin en bas à  gauche, ou en haut à  gauche si on est en isFlipped:YES),
    • puis un translation de ce coin en {w/2,h/2}.

    ...ce qui fera la même chose que si on faisait la translation {w/2,h/2} d'abord, puis une rotation... mais autour de ce point {w/2,h/2} (c'est plus facile d'imaginer le résultat en le prennant dans ce sens je trouve)

    Conclusion : pour faire une rotation d'une image autour d'un point {Cx,Cy} :
    [transform translateXBy:Cx yBy:Cy];<br />[transform rotateByRadians:_imageRotation];<br />[transform translateXBy:-Cx yBy:-Cy];
    
  • UniXUniX Membre
    07:49 modifié #20
    o:) o:) o:)

    AliGator, merci pour ce cours sur les "NSAT" !
    En tout cas pour moi, ça m'a éclairci le brouillard ! Et j'ai résolu mon problème .... j'avais pas percuté qu'il fallait mettre les opérations dans l'ordre inverse d'exécution ...!

    Merci.
  • AliGatorAliGator Membre, Modérateur
    avril 2006 modifié #21
    Pour tout te dire moi non plus je n'avais pas percuté qu'il fallait les mettre dans l'ordre inverse... avant de faire tout plein de tests dans tous les sens dans un projet dédié :)

    Au passage, une petite astuce pour moins se prendre le chou (attention, ça devrait marcher mais j'ai pas testé intensivement non plus avec tout plein de transformations autour) : créer une catégorie sur NSAffineTransform pour rajouter ça :
    @interface NSAffineTransform (RotationAddOn)	<br />-(void)rotateByRadians:(float)angle aroundPoint:(NSPoint)pt;<br />-(void)rotateByDegrees:(float)angle aroundPoint:(NSPoint)pt;<br />@end<br /><br />@implementation NSAffineTransform (Rotation_AddOn)<br />-(void)rotateByRadians:(float)angle aroundPoint:(NSPoint)pt<br />{<br />	[self translateXBy:pt.x yBy:pt.y];<br />	[self rotateByRadians:angle];<br />	[self translateXBy:-pt.x yBy:-pt.y];<br />}<br />-(void)rotateByDegrees:(float)angle aroundPoint:(NSPoint)pt<br />{<br />	[self translateXBy:pt.x yBy:pt.y];<br />	[self rotateByDegrees:angle];<br />	[self translateXBy:-pt.x yBy:-pt.y];<br />}<br />@end<br />
    
    Voilà .

    Mais avant que tu ne poses la question ici et que je cherche un bon bout de temps dessus pour comprendre dans quel sens ça se passe et tout et tout, j'étais au même point que toi et complètement embrouillé sur les NSAT ;) :)
Connectez-vous ou Inscrivez-vous pour répondre.