[Résolu] Fading sur une image... Je n'y arrive pas :-(

LeChatNoirLeChatNoir Membre, Modérateur
janvier 2006 modifié dans API AppKit #1
Salut,

je suis encore dans mes animations...
Merci à  Renaud et mpergrand pour leurs réponses sur le précédent topic !

Voilà  ce que j'aimerai faire (et ce que j'ai comme architecture) :
Dans une fenêtre, j'ai une NSImageView.
Sur une action particulière, je vais déclencher un NSTimer qui se chargera d'afficher une image constuite à  la volée (en fonction de la sélection) en fondu.

Donc ma fonction se chargeant de l'animation devra :
* faire un fondu sortant de la précédente image,
* faire un fondu entrant de la nouvelle.

Pour ça, je compte utiliser les méthode "compositeToPoint...delta" et "dissolve....delta".

Mais comment dois je m'y prendre ?

A des fins de tests, dans mon timer, je fais un lockFocus sur mon NSImageView et un simple composite avec un delta à  1 mais l'image n'apparaà®t pas (malgré un setNeedDisplay).

Elle n'apparait que quand je fais un setImage :-(

Peut être devrais je faire comme conseiller par Renaud : une sous classe de NSView ?
Ou peut être plus simplement utiliser les méthodes CG pour faire varier le RGB ? (mais je ne connais pas bien. Chacha peut être ?)

Je compte poursuivre mes tests ce soir mais je m'en remets à  vos lumières en attendant !

Merci !

Réponses

  • 10:39 modifié #2
    La sous classe serait très rapide à  réaliser, et surtout tu ne devras pas t'embêter avec les lockFocus, dont tu as visiblement du mal à  saisir la portée ;). Là  tout ce que tu aurais à  faire est mettre un timer qui change une variable d'instance (qui représente l'opacité de l'image), qui serait l'argument delta dans:
    - (void)drawAtPoint:(NSPoint)point fromRect:(NSRect)srcRect operation:(NSCompositingOperation)op fraction:(float)delta (NSImage)
    (ça y est tu l'as eu au complet cette fois)

    La ImageView est intéressante si tu veux gérer le drag&drop des images, avec des bindings et ce genre de truc, mais juste pour l'affichage d'une image avec des animations et des transformations c'est pas vraiment fait pour.
  • LeChatNoirLeChatNoir Membre, Modérateur
    janvier 2006 modifié #3
    dans 1138014540:

    tu ne devras pas t'embêter avec les lockFocus, dont tu as visiblement du mal à  saisir la portée ;).


    Oui bon effectivement, j'ai un peu du mal...
    Mais je vais me ranger à  l'avis d'un sage et sous classer. Bien que ma méthode, même si un peu pourrie, aurait du fonctionner et je ne comprend pas pourquoi elle ne fonctionne pas...

    Passons.

    Je sous classe NSView. Soit.
    Je met une variable d'instance qui représente ma transparence.

    Dans la méthode drawRect(NSRect aRect), je fais ma tambouille pour récupérer mon image et je fais mon [mon imagerécup drawAtPoint...delta] (celle que tu me donnes si gentiement  :))

    Si y avait déjà  une image, faut que je m'arrange pour la "dissolver" avant et comme c'est l'image qui est dans moi (self), je sais la retrouver pour la "dissolver".

    Ouais, c'est cool comme solution. Je m'en voyais bcp plus avec ma NSImageView qui me sert à  rien (pas de drag, pas de drop, rien d'un NSControl quoi).

    Mais...
    Car il y a [glow=red,2,300]toujours[/glow] un mais  ::)
    Mon timer (qui est dans le controller qui déclenche l'animation), il appelle quoi ?
    Il peut pas appeler le drawRect de ma vue quand même ! Ca se fait pas !

    Ou alors j'implémente ça dans une méthode autre (perso) de ma NSView et rien dans le drawRect ?


  • Eddy58Eddy58 Membre
    10:39 modifié #4
    dans 1138021152:

    Mon timer (qui est dans le controller qui déclenche l'animation), il appelle quoi ?
    Il peut pas appeler le drawRect de ma vue quand même ! Ca se fait pas !

    Tout à  fait, il faut qu'il appelle une méthode à  toi qui elle contient un setNeedsDisplay:YES.:)
  • fouffouf Membre
    10:39 modifié #5
    Tu peux ajouter deux méthodes accesseurs à  ta sous-classe (que tu associes à  une variables d'instance)  pour faire varier le taux de transparence. Ensuite, tu as deux choix :
    - soit tu implémente le fading dans le controller et alors tu effectueras des setAlpha: les uns après les autres en utilisant la valeur de diminution. Ca te donneras un truc comme :
    <br />[_vue setAlpha:[_vue alpha]-0.1];<br />
    


    - tu implémente directement le changement dans la vue, auquel cas tu pourras implementer une méthode fadeIn ou fadeOut qui fera le changement de transparence elle-même et que tu appeleras directement depuis le timer. Ca te donneras :
    <br />- (void)fadeOut{<br />[self setAlpha:_alpha-_fadingValue];<br />}<br />
    

    Ensuite, à  toi de savoir quand appeler setNeedsDisplay: : soit dans setAlpha: soit autre part ;)
  • LeChatNoirLeChatNoir Membre, Modérateur
    10:39 modifié #6
    Cool !
    Je m'en vais implémenter tout ça ce soir (si le temps me le permet !).

    Merci les gars !
  • AliGatorAliGator Membre, Modérateur
    janvier 2006 modifié #7
    Moi je verrai plus le NSTimer comme variable d'instance de ta sous-classe de NSView.
    Ta sous-classe devra avoir 2 variables d'instances NSImage aussi (image1 et image2), pour garder l'image précédent et suivante.
    Et des méthodes dans ta sous-classe devront être du genre :
    • [tt](void)fadeToImage:(NSImage*)[/tt] --> met la NSImage dans une variable image2, et démarre ton timer
      -> Le timer va faire des appels à  [tt]drawAtPoint:...delta[/tt] (en utilisant image1 et image2 + faisant varier la valeur de l'alpha/delta) + un appel à  setNeedsDisplay:YES après chaque.
    • [tt](void)setDelay:(int)[/tt] qui va fixer la vitesse de transition (pour ton Timer)
    • Lorsque l'animation est terminée, passer image2 dans la variable image1, passer image2 à  nil (comme ça on est prêt pour le coup d'après). Puis faire un appel à  une méthode de delegate genre [tt][delegate animationDidStop][/tt] (à  toi de créer la catégorie de NSObject pour rajouter cette méthode de délégué) pour indiquer que l'animation est finie (peut toujours servir)

    et dans ton drawRect :
    • Si image2 est nil, il n'y a pas d'animation/transition à  faire, juste un [tt]drawAtPoint[/tt] de image1 (qui si elle est nil aussi, ne dessinera rien, au passage, ce qui est très bien aussi)
    • 2. Sinon si image2 est non-nil, faire un [tt]drawAtPoint...compositingMachin...delta[/tt] entre image1 et image2.

    Si tout ce passe comme je l'imagine, le fait que image1 soit nil la toute première fois (toute première image) ne posera pas de problème pour le compositing. et les fois suivantes, ça fera un fondu entre les 2 images (image1 qui est l'image précédente et image2 qui est l'image à  afficher)

    Enfin moi c'est comme ça que je vois les choses. La classe gère tout (c'est son rôle, c'est pas à  ton contrôlleur qui la gère de gérer l'alpha et tout), et toi de l'extérieur tu appelles juste fadeToImage: pour faire un fondu entre la dernière image (celle que ta vue affiche actuellement) et la prochaine image (qui deviendra, à  la fin de la transition, la nouvelle "dernière image")
    Le timer est intégré à  ta sous-classe perso, t'as rien à  gérer à  l'extérieur.
    C'est à  ça que sert l'encapsulation dans ta classe. Sinon si ton contrôlleur doti gérer le timer et tout, autant limite ne pas sous-classer ;)
  • mpergandmpergand Membre
    janvier 2006 modifié #8
    Petit essai...

    Je n'utilise pas de timer  ::)

    [EDIT]

    Ca pullule de bugs par ici, saleté de bestioles !
    2e essai  :)

    [Fichier joint supprimé par l'administrateur]
  • LeChatNoirLeChatNoir Membre, Modérateur
    10:39 modifié #9
    Slt et merci à  tous pour votre aide.
    Voilà  ce que j'en ai sortis.
    Une sous classe de NSView qui fait un fondu entrant/sortant entre 2 images.
    La boucle est un peu bancale et certainement améliorable mais je n'ai pas de besoins particuliers en terme de perf donc ca me convient.

    Pour l'utiliser, il suffit de copier/coller le code dans une classe Obj-C, de glisser deposer une custom view dans IB. De lui donner le "Custom Type" de la classe et d'appeler quand on le souhaite la méthode fadeImageIn: avec l'image sur laquelle on veut un fondu entrant. Si la vue affichait précédemment une image, il y a un petit fondu sortant avant.

    Encore merci à  tous !

    Le header
    <br />/* GGfadeView */<br />#import &lt;Cocoa/Cocoa.h&gt;<br /><br />enum compositeOperation<br />{<br />	fadeIn, fadeOut<br />};<br /><br />@interface GGfadeView : NSView<br />{<br />	NSImage * toDisplay;<br />	NSImage * toDissolve;<br />	float delta;<br />	NSTimer * timer;<br />	int compositeWay;<br />	int counter;<br />}<br />-(void)fadeImageIn:(NSImage *)anImage;<br />@end<br /><br />
    


    L'implémentation :
    <br />#import &quot;GGfadeView.h&quot;<br /><br />@implementation GGfadeView<br /><br />- (id)initWithFrame:(NSRect)frameRect<br />{<br />	if ((self = [super initWithFrame:frameRect]) != nil) {<br />		// Add initialization code here<br />		toDissolve=nil;<br />		toDisplay=nil;<br />		timer=nil;<br />		counter=1;<br />	}<br />	return self;<br />}<br /><br />- (void)drawRect:(NSRect)rect<br />{<br />	if ([timer isValid])<br />	{<br />		if (compositeWay==fadeIn)<br />			[toDisplay drawAtPoint:NSZeroPoint fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:delta];<br />		else<br />			[toDissolve dissolveToPoint:NSZeroPoint fromRect:[self bounds] fraction:delta];			<br />	}<br />	else<br />		[toDisplay drawAtPoint:NSZeroPoint fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1];<br />	<br />}<br /><br />-(void)fadeImageIn:(NSImage *)anImage<br />{<br />	if (toDisplay)<br />		[toDisplay release];<br />	toDisplay=[anImage retain];<br />	<br />	if (timer)<br />	{<br />		[timer invalidate];<br />		counter=1;}<br />	<br />	timer=[NSTimer scheduledTimerWithTimeInterval:0.04<br />			&nbsp;  									&nbsp; target:self <br />			&nbsp;  									&nbsp; selector:@selector(stepFade:) <br />			&nbsp;  									&nbsp; userInfo:nil<br />			&nbsp;  									&nbsp; repeats:YES]; <br />}<br /><br />-(void)stepFade:(NSTimer *)aTimer<br />{<br />	<br />	static maxLoop=10;<br />	<br />	if (counter==1)<br />	{<br />		if (toDissolve)<br />		{<br />			delta=1;<br />		}<br />		else<br />		{<br />			delta=0;<br />			counter=5;<br />		}<br />	}<br />	<br />	if (counter&gt;4)<br />		compositeWay=fadeIn;<br />	else <br />		compositeWay=fadeOut;<br /><br />	delta+=(compositeWay==fadeIn?0.2:-0.2);<br />	<br />	counter++;<br />	if (counter&gt;maxLoop)<br />	{<br />		counter=1;<br />		if (toDissolve)<br />			[toDissolve release];<br />		toDissolve=[toDisplay retain];<br />		[timer invalidate];<br />		timer=nil;<br />	}<br />	else<br />		[self setNeedsDisplay:YES];<br />}<br />@end<br /><br />
    
Connectez-vous ou Inscrivez-vous pour répondre.