Contextes graphiques

MickMick Membre
19:43 modifié dans API AppKit #1
Bonjour à  tous,
J'ai un petit soucis sur les graphicContexts. Voilà , j'ai compris qu'avant que la méthode draw d'une view soit appelée, le "current" graphic context est bidouillé pour que tout ce passe bien durant le tracé (pas de "fuite" à  l'extérieur de la vue etc...)

Mon soucis : imaginons un controleur quelconque qui reçoit une info comme quoi une donnée a été changée. Ce controleur en informe une vue qui doit changer de place un truc graphique ou ajouter un truc. Comme je n'envoie pas le message setNeedsDisplay, le graphicContext n'est pas correct et c'est sur la fenêtre que ça dessine (je ne vois que ce qui "déborde")... Suis-je obligé de redessiner entièrement ma vue ? Y a-t-il moyen de ne dessiner  que ce qui est nécessaire autrement que par un setNeedDisplayInRect: ? Comment paramétrer correctement le graphicContext pour cela ? (la doc de la classe ne laisse pas trop apparaà®tre de persepectives satisfaisantes...)

Réponses

  • zoczoc Membre
    19:43 modifié #2
    Le seul moyen propre, c'est bien setNeedDisplay/setNeedDisplayInRect. C'est de plus la seule façon de respecter le pattern MVC (La vue affiche et ne fait que ca, le controlleur contrôle et ce n'est pas son rôle d'afficher).


    Ceci dit rien n'empêche au controlleur de préparer/prémacher des structures de données afin que la vue puisse se réafficher efficacement.
  • AliGatorAliGator Membre, Modérateur
    19:43 modifié #3
    Etant donné comment sont conçues la plupart des Composition Loops & Rendering Loop, dont celles de MacOSX, cela ne fonctionne pas par objets à  redessiner, mais bien par zone de l'écran à  de nouveau composer puis rendre.

    En effet, imagine que tu dessines un rectangle rempli de vert, puis un cercle rempli d'un bleu semi-transparent à  40%, puis un autre rectangle par dessus le tout...
    Et puis tout à  coup ton controlleur ou autre qui gère les objets que tu présentes dans ta vue (j'imagine que tu as bien en tête le concept de MVC avec séparation entre les objets à  manipuler (modèle) et leur représentation visuelle (vue) ?) décide que ton cercle voit son rayon changer ou sa couleur de remplissage modifiée...

    Tu es bien obligé dans ce cas de redessiner tous les objets, au moins dans la zone correspondant au "rectangle englobant" contenant le cercle de plus grande taille (le nouveau cercle si son rayon a été agrandi, l'ancien cercle si la modification correspond à  une diminution de rayon)...
    Après tout le reste de la zone graphique qui n'aurait pas été modifiée tu peux la laissée inchangée, mais n'empêche qu'il ne suffit pas juste de redessiner le cercle (d'autant qu'il a une couleur de remplissage semi-transparente) puisque ce cercle et son remplissage impactent le rendu des autres objets dessinés en dessous ou au dessus.


    Au final c'est donc bien "setNeedsDisplayInRect:" qu'il faut utiliser. Et utiliser le CGRect passé éventuellement pour cropper ton dessin ou ne dessiner que cette zone. Après à  toi dans ton algo de dessin de déterminer pour chaque objet à  dessiner si son "rectangle englobant" a une intersection / zone commune avec la zone à  redessiner.
    Pour cela, tu peux utiliser la fonction CGRectIntersect et autres fonctions C "CGRectXXX".


    Par exemple si le CGRect passé à  "drawInRect:" va de x=150 à  x=200 et de y=180 à  y=220, alors ne dessine que les objets dont au moins une partie est contenue dans cette zone : s'il y a un cercle de centre {220,200} mais de rayon 30, il s'étale donc de x=190 à  x=250 et de y=170 à  y=230, donc même si son centre n'est pas dans le rect dessiné, le "boundingRect" (rectangle englobant) de ce cercle a une intersection avec la zone dessinée
  • MickMick Membre
    19:43 modifié #4
    Bonjour à  tous,

    Effectivement, après réflexion, il paraà®t obligatoire d'envoyer le message setNeedsDisplay.
    En fait j'avais en tête une autre structure : ma vue affiche des choses que j'aimerais considérer comme des "subViews". J'ai réussi pour des lignes verticales et horizontales (pour lecture graphique précise des coordonnées d'un point) => Je créais une NSBox utilisée en separateur. J'ajoutais à  ma vue ces NSBox en tant que subView : lorsque la souris bouge, je supprime ces subviews et j'en recrée à  la nouvelle position de la souris. Cela fonctionne bien. Comment puis-je faire de même avec des lignes obliques ? Dans l'état actuel des choses, je retrace entièrement la vue à  chaque mouseMoved. N'y a-t-il pas mieux en terme de performance ? Avez-vous une idée pour avoir un affichage le plus fluide possible ?
  • CéroceCéroce Membre, Modérateur
    19:43 modifié #5
    Il faut TOUT gérer dans une sous-classe de NSView. Utiliser des vues (style NSBox) pour composer l'affichage est inefficace au possible. Apple le déconseille dans le Cocoa Drawing Guide.

    La méthode -[setNeedsDisplayInRect:] permet de n'invalider qu'un partie de la vue. Les rectangles passés aux différents appels de cette méthode seront englobés par un rectangle passé au prochain appel de -[drawInRect:]. En pratique, cette technique est efficace si le rectangle englobant n'est pas trop grand => Bien adaptée pour des "marqueurs" horizontaux ou verticaux.

    Tu traces une courbe, non ? Le tracé (en utilisant les NSBezierPath ou leurs équivalents Core Graphics) prend beaucoup de temps.
    Tu devrais dessiner dans une bitmap en mémoire ("offscreen buffer") puis afficher cette bitmap à  l'écran. Tu as plusieurs possibilités: NSBitmapImageRep (que je te déconseille, car c'est une classe assez imprévisible), ou utiliser directement Core Graphics (CGBitmapContext). Note que tu devras quand même prévoir le dessin directement dans la vue (sans passer par la bitmap) au cas où tu veux imprimer.
    Ca a l'air difficile: ça l'est, mais pour obtenir de bonnes performances, il n'y a guère le choix.
  • MickMick Membre
    19:43 modifié #6
    Bonjour,
    Ok Céroce pour l'utilisation des subViews : c'est effectivement déconseillé.
    Aurais-tu un exemple pour utiliser CGBitmapContext ?

    En fait, j'ai vraiment besoin de performance lors d'un tracking de souris ou l'affichage doit se mettre à  jour. Lors de ce tracking, je trace des lignes qui "se déplacent" selon la position de la souris. Dans l'état actuel des choses, je retrace l'ensemble de la vue à  chaque fois (pas top).
    Comment fonctionne ce "dessin" en mémoire ? as-tu un petit bout de code exemple ?
    Je recherche parallèlement dans la doc...

  • CéroceCéroce Membre, Modérateur
    19:43 modifié #7
    Commence déjà  par implémenter correctement la méthode drawRect: en ne dessinant que ce qui se trouve dans le rectangle "sale" et en ne déclarant pas toute la vue comme sale, mais seulement l'endroit où se trouvait le marqueur à  l'instant d'avant (avec setNeedsDisplayInRect: au lieu de setNeedsDisplay:). Tu devrais voir une amélioration très notable des performances.


    Placer le dessin dans un "cache" (c'est bien à  ça que sert la bitmap), n'apportera un gain que s'il est difficile de ne dessiner que la partie sale, et si les données d'entrée ne sont pas raffraichies trop souvent (puisqu'il faut redessiner dans la bitmap). Je ne pense pas que tu trouveras grand chose sur le net à  ce propos, car cela devient très spécifique, et je n'ai pas non plus le temps de te pondre du code, mais l'idée en gros c'est:
    - on crée une bitmap de la même taille (en pixels) que le graphique
    - on dessine dans la bitmap lorsque les données d'entrée ont changé.
    - quand il faut rafraà®chir la vue parce que la souris s'est déplacée, on dessine une portion de la bitmap dans le rectangle sale.
  • MickMick Membre
    mai 2010 modifié #8
    Ok. En fait, je crois que réécrire la méthode drawRect et tester si telle ou telle chose est dans le rectangle sale risque de prendre plus de temps que de redessiner la vue. en effet, si l'utilisateur choisi un "outil" tangente, j'ai une droite (donc un path) dont le rect prend quasiment tout le rectangle de la vue (sauf si la pente de la tangente est très "raide" ou au contraire très peu raide) (vous imaginez bien l'allure de la vue : une série de points, et une tangente numérique en un point). Pas sûr que ça vaille le coup...

    Si une valeur change, le déplacement du point peut être optimisé vu la petitesse du rectangle. Je vais essayer.
    Le cas des lignes horizontales et verticales (si l'utilisateur est en mode "lecture de données") peut être optimisé aussi car les rect sales sont beaucoup plus petits.

    Merci en tout cas de vos réponses, mais j'avais osé espérer que le dessin soit "facile" avec Cocoa. En fait, une simple vue qui affiche des lignes (droites et courbes) et des rectangles nécessite une usine à  gaz si on veut optimiser. Je pensais vraiment (à  tort) que l'utilisation des subviews était plus efficace, les développeurs d'Apple s'étant déjà  chargé en écrivant le framework d'optimiser l'affichage de la vue en tenant compte de ses subviews et de la modification éventuelle de leur rect.

  • CéroceCéroce Membre, Modérateur
    19:43 modifié #9
    Tu peux peut-être quand même faire une optimisation: au lieu de reconstruire les NSBezierPaths à  chaque dessin, conserve-les en mémoire et ne les reconstruits que quand c'est nécessaire.

    Apple nous laisse effectivement beaucoup de travail lorsqu'on fait quelque chose qui n'est pas commun à  beaucoup d'applis; à  vrai dire, c'est difficile d'imaginer des systèmes vraiment universels, mais je suis d'accord que le fait que la vue gère tout (dessin et événements) a tendance à  rendre obèse le code des vues perso. Il faut bien travailler sur l'organisation des classes pour que la vue délègue son travail.

    Ceci dit, nous disposons quand même du système graphique le plus puissant que j'aie pu voir. Compare avec ce que proposent les autres frameworks pour dessiner...
Connectez-vous ou Inscrivez-vous pour répondre.