Tourner une image
UniX
Membre
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.
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.
Connectez-vous ou Inscrivez-vous pour répondre.
Réponses
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.
.
Tu peux aussi utiliser setFlipped: sur une 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.
http://www.objective-cocoa.org/forum/index.php?topic=1535.0
http://www.objective-cocoa.org/forum/index.php?topic=755.0
Si quelqu'un peut m'éclairer ...
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.
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).
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 :
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.
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 ....
(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]
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.
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 :
(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
Il manque juste un 3e point pour retomber sur ses pattes :
3) Une translation de (-width/2,-height/2)
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]
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.
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 ...
(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 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.
Ce code est à comprendre ainsi :
Si on s'arrête là (qu'on commente la ligne (1)), l'image est dessinée avec son centre en 0,0.
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 :
...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} :
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.
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 : 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