idée pour NSScrollView infini

Bonjour,


 


Je suis en train de faire une sorte d'application de dessin et je souhaiterais avoir un canva de taille infini.


Je peux pas me permettre de redessiner toute la vue si non ca devient vraiment lent.


 


Les solutions qui me viennent à  l'esprit :


- créer ma propre vue classe MyScrollView descendant de NSView mais il me faudrait translater l'ancien dessin présent pour re-dessiner que la partie manquante (mais comment ?)


-  ou si non essayer de sous-classer NSScrollView mais je voie pas trop non plus.


 


Auriez vous une idée de méthode et peut être de sa réalisation ?

 


 


Réponses

  • L'infini, même sur une machine 64 bits, ça se traduit par NaN (Not a Number). Il s'agit d'un concept purement théorique qui échappe à  notre entendement.


     


    Le temps que ton utilisateur mettrait à  atteindre la limite de ta scroll view serait également infini... si tant est que l'on puisse scroller l'infini, car tout déplacement du scroller serait... infiniment petit.  ;)


     


    Par contre rien ne t'empêche de jouer avec des nombres très grands, et de jouer sur les réglages de ta scroll view... Je ne sais pas combien mesurerait une feuille de MINFLOAT à  MAXFLOAT pixels de côté. Quelqu'un sait-il?


  • AliGatorAliGator Membre, Modérateur

    il me faudrait translater l'ancien dessin présent pour re-dessiner que la partie manquante (mais comment ?)

    en changeant le bounds.origin, c'est fait pour.


    Quand au redessin faut utiliser le dirtyRect passé en paramètre de la méthode drawRect: pour optimiser et ne redessiner que la zone qui nécessite d'être redessinée et pas dessiner la totalité.
  • mpergandmpergand Membre
    décembre 2013 modifié #4


    L'infini, même sur une machine 64 bits, ça se traduit par NaN (Not a Number). Il s'agit d'un concept purement théorique qui échappe à  notre entendement.




     


    Not a Number et Infinity sont deux choses différentes, la preuve, je fais la différence dans mon parser d'expressions:



     


    2^5000


    ∞


     


    sqr(-2)


    Pas un nombre 



     


    Le code qui gère ça:



    EXPValue ExpressionParser::parseExpression(const wideChar* exp)
    {
    ...

    if(isnan(resultValue.number()))
    throw EXPException(EXPError_NaN);

    if(isinf(resultValue.number()))
    throw EXPException(EXPError_Infinity);

    ...

  • AliGatorAliGator Membre, Modérateur
    décembre 2013 modifié #5
    +1 mpergand. NaN et +INF ou -INF n'ont rien à  voir et n'ont pas la même signification du tout.


    INF représente un nombre trop grand pour être représenté par IEEE754 alors que NaN représente un nombre indéterminé ou une opération invalide (du moins dans |R)


  • 2^5000


    ∞




     


    Ah zut. Je voyais ça plus grand. Tout fout le camp décidément.

  • JE729JE729 Membre
    décembre 2013 modifié #7

    En effet c'est pas vraiment l'infini mais plutôt que l'utilisateur est l'air que ce soit infini.


     


    Du coup je vais essayer d'utiliser une scrollView avec une vue géante à  l'intérieur comme vous m'indiquez.


    Mais cependant quand je scroll ce n'est pas vraiment fluide. Avec un simple carré ca saccade alors avec un dessin complexe ca va être pire non ?


     


    J'ai fait un exemple pour montrer ce qu'il se passe. (pour mes besoins ca serait avec copieOnScroll et avec la plus grande taille du coup)

  • berfisberfis Membre
    décembre 2013 modifié #8

    Jeremy,


     


    En fait, si tu prétends ta "feuille" infinie, tu es en droit de le faire, mais dans ce cas tu ne dois pas mettre de scrollers, puisque ceux-ci ont par définition une valeur maximale et donc font mauvais ménage avec l'infini, car dès qu'on scrolle "jusqu'en haut", on se retrouve à  cet endroit magique où les parallèles se rejoignent... et même dessinent des bretzels.


     


    Si tu comptes créer des objets "vectoriels", alors ils peuvent très bien exister sur un fond qui n'est qu'une abstraction: il n'y a pas de scrollers... parce qu'il n'y a rien à  scroller. Tu te déplaces (avec la roue de la souris, pourquoi pas, ou avec un outil "main") au milieu d'objets qui savent quand ils doivent apparaà®tre sur l'écran, car ils ont des coordonnées connues par rapport à  l'origine (0;0). Et en fait, les objets seuls existent.


     


    Du coup, il n'existe plus de problème de vitesse, quelque soit la valeur du zoom que l'utilisateur choisit.


     


    C'est juste une idée comme ça.


  • JE729JE729 Membre
    décembre 2013 modifié #9

    Ba au début c'est exactement ce que je voulais faire dans l'idée mais je n'arrive pas à  réaliser cela correctement.


    Je ne sais pas comment garder une partie de la vue précédente dans un endroit et dessiner que la nouvelle petite bande manquante et du coup le scroll n'est plus fluide.


    Plus généralement comment optimizer le dessin dans la pratique en partant de ce principe et ne pas tout re-dessiner


     


    J'ai un peu de mal à  expliquer dsl ^^ 


  • berfisberfis Membre
    décembre 2013 modifié #10

    Ta vue a une dimension connue. Elle a une origine connue dans l'espace abstrait. Donc le rectangle de cet espace qu'elle représente t'est connu. Si le rectangle de l'objet intersecte avec le rectangle représenté par la vue, tu dessines l'objet (en entier, puisque la partie non visible sera clipée).


     


    Rappel: tu n'as rien à  dessiner ou à  redessiner puisque le "fond" n'existe pas. Si les coordonnées de la vue changent, tu effaces le contenu de la vue et tu redessines les objets concernés, c'est tout. Quel soit l'endroit représenté, cela doit se compter en microsecondes, à  moins que tu aies des milliers d'objets...


  • L'infini c'est trés long, surtout vers la fin ..
  • Si tu dessines des NSBezierPath par exemple le stroke ou le fill teste déjà  l'intersection avec le dirtyRect et ne dessine que si c'est nécessaire.


    Par expérience il faut minimiser les temps de calcul dans le drawrect. Personnellement je m'arrange pour n'avoir quasiment que des stroke et des fill sur des NSBezierPath que je crée une fois pour toute à  l'ouverture de mon fichier à  partir des éléments de mon dessin. Certes ça mange pas mal d'espace mémoire mais c'est rapide à  l'usage.


  • AliGatorAliGator Membre, Modérateur
    (Copier-coller de mon message #3)
  • La seule fois que j'ai vu un espace graphique "infini" comme celui-ci c'était pour dessiner l'ensemble de Mandelbrot. Je chauffe ?


  • (Copier-coller de mon message #3)




    Pas exactement car tu peux tout dessiner. C'est le système qui teste si l'élément est concerné et tu n'as pas à  le faire.



  • L'infini c'est trés long, surtout vers la fin ..




    Pas pour Chuck Norris, vu qu'il a déjà  compté jusqu'à  l'infini deux fois.

  • Est-ce que pour les CGPath ca détecte aussi tout seul aussi ?(je ne vais pas utiliser NSBezierPath pour porter par la suite sur iOS)


     


    La chose qui me gène mais que j'arrive pas vraiment à  expliquer est qu'avec la méthode de berfis #10, le dirtyRect serait de la taille de la vue et non de la taille de la bande manquante qui ferait seulement la taille du décalage du scroll comme NSScrolllView fait actuellement.

  • AliGatorAliGator Membre, Modérateur
    décembre 2013 modifié #18

    Pas exactement car tu peux tout dessiner. C'est le système qui teste si l'élément est concerné et tu n'as pas à  le faire.

    Ben oui c'est pour ça que je cite mon message #3 : pas la peine de faire des calculs alambiqués pour savoir quelle portion du dessin redessiner, c'est le système qui fait ce calcul et te fournit le dirtyRect en paramètre déjà  !


    Donc y'a plus qu'à  faire l'intersection de ce dirtyRect avec le truc à  dessiner (un bezierPath, une image, que sais-je il ne nous l'a pas dit) pour ne dessiner que les portions qui sont dans ce dirtyRect et pas s'embêter avec les autres.


    "


    Après pour faire cette intersection, par exemple sur des BezierPath, soit on boucle sur tous les BezierPath pour tester s'ils intersectent le dirtyRect et dessiner seulement ceux-là , mais si on a vraiment beaucoup beaucoup de BezierPath et qu'on veut optimiser d'avantage on peut aussi part exemple découper la zone de dessin en grille de NxN pixels et pré calculer pour chaque case quels BezierPath passent dedans ; comme ça au moment du parcours pour boucler sur les paths à  dessiner on peut limiter le parcours qu'à  une portion / qu'à  ceux dans les cases alentours de la grille ça fait un tableau moins conséquent à  parcourir.
  • AliGatorAliGator Membre, Modérateur

    Est-ce que pour les CGPath ca détecte aussi tout seul aussi ?(je ne vais pas utiliser NSBezierPath pour porter par la suite sur iOS)

    Documentation Apple de CGPath : méthode CGPathGetPathBoundingBox

     

    La chose qui me gène mais que j'arrive pas vraiment à  expliquer est qu'avec la méthode de berfis #10, le dirtyRect serait de la taille de la vue et non de la taille de la bande manquante qui ferait seulement la taille du décalage du scroll comme NSScrolllView fait actuellement.

    Mais pas avec ma méthode : le paramètre qui est passé par le système lorsque ce dernier appelle "drawRect:" est bien juste la zone visible à  redessiner et pas la totalité.
  • Mais Aligator, pour que ce soit automatique et que le dirtyRect soit calculer par le système toi tu me dis d'utiliser un NSScrollView avec dedans une vue géante non ?


    Mais au message #10, berfis me disait de lui faire ma propre sous classe de NSView.


     


    Je suis dsl je suis un peu perdu la :S


  • CéroceCéroce Membre, Modérateur
    Les deux approches sont possibles. Personnellement, je partirais plutôt sur une scroll view. En effet,
    1) c'est standard, donc tu profiteras des éventuelles évolutions d'OS X.
    2) elle va faire le travail de défilement à  ta place.
  • Mais en regardant bien, est-ce normal que ce ne soit pas parfaitement fluide. Ca tremble ...


  • AliGatorAliGator Membre, Modérateur
    Non j'ai toujours dit sous classer NSView moi. Et changer la valeur de bounds.origin pour faire translater son contenu.
  • CéroceCéroce Membre, Modérateur
    C'est fluide chez moi :-)

    Sous Mavericks, Apple utilise maintenant un cache mémoire pour les scroll views. En théorie, c'est plus rapide, en pratique, c'est très lent dans certains cas. Par exemple, faire défiler un PDF dans Aperçu est très très lent chez moi. C'est peut-être ce que tu observes.

    Par ailleurs, il n'est pas nécessaire de sauvegarder et restaurer le CGContext: quand -drawRect: est appelée, ton contexte est toujours dans un état "propre".

    Enfin, il n'est plus nécessaire d'utiliser les fonctions NSRectToCGRect() et cie. On peut remplacer l'un par l'autre indifféremment. Préfère les CGRect, les noms des fonctions sont plus logiques.
  • Merci Céroce pour tes conseils !  :)


     


    @AliGator Je viens de faire comme tu dis. Le système rafraichit bien toute la vue et non la bande manquante. J'ai lié un exemple.


  • J'ai vérifié avec Quartz Debug. C'est toute la vue qui est rafraà®chie, mais elle n'est pas bien grande et c'est fluide (en tout cas chez moi). Et puis, tu fais des NSLog, ça n'accélère pas les choses.


     


    C'est quand même mieux sans les scrollers, non? ça donnerait presque le vertige de songer à  cette infinité qui entoure le havre de l'origine. Joli boulot ! 


     


    Bon, ben il ne reste plus qu'à  implémenter l'outil main, tu sais, celui qui fait se refermer les doigts quand tu drag la vue...  ;)


  • Pour rappel voir la définition du drawRect de la NSView et notamment :



    Discussion

    Use this method to draw the specified portion of your view's content. Your implementation of this method should be as fast as possible and do as little work as possible. The dirtyRect parameter helps you achieve better performance by specifying the portion of the view that needs to be drawn. You should always limit drawing to the content inside this rectangle. For even better performance, you can call the getRectsBeingDrawn:count: method and use the list of rectangles returned by that method to limit drawing even further. You can also use the needsToDrawRect: method test whether objects in a particular rectangle need to be drawn.

  • Extrait de la doc très explicite


    Listing 6-2  Simplified intersection testing using needsToDrawRect:



    - (void) drawRect:(NSRect)aRect {
    id thing;
    NSEnumerator *thingEnumerator = [[self arrayOfAllThingsIDraw] objectEnumerator];
    while (thing = [thingEnumerator nextObject]) {
    if ([self needsToDrawRect:[thing bounds]]) {
    [self drawThing:thing];
    }
    }

    The needsToDrawRect: method is optimized to efficiently reject objects that lie entirely outside the bounds of the area being drawn by employing the same “trivial rejection” test as that used in Listing 6-1.


  • En effet -needsToDrawRect: est une super-méthode que je viens de voir 1 min avant ton message ^^


     


    Merci pour les conseils!


    (si il y a des connaisseurs dans le domaine qui ont des conseils pour améliorer la rapidité des dessins je suis toujours preneur. :) )




  • (si il y a des connaisseurs dans le domaine qui ont des conseils pour améliorer la rapidité des dessins je suis toujours preneur. :) )




    Bah... à  moins d'avoir des dessins incroyablement complexes... Quartz fait un aussi bon boulot que QuickDraw à  l'époque, et les compilateurs optimisent ton code.


     


    J'avais une carte constituée de 4096 éléments de dessin, sur laquelle je déplaçais des objets. J'ai commencé à  me triturer la cervelle pour voir comment optimiser tout ça, en calculant quels étaient les éléments "survolés" pour ne redessiner qu'eux, avant de m'apercevoir que Quartz redessinait toute la carte quasi-instantanément. Le temps que j'aurais passé à  calculer une "optimisation" aurait été pure perte dans ce cas-là ...


     


    Commence par avancer dans le design de l'application avant d'optimiser. Sur l'optimisation, les avis sont partagés. Selon Donald Knuth, "Premature optimization is the root of all evil".


     


    En tout cas, ça prend tournure cette application!

  • J'aime bien ta réponse !! :D


     


    Je vais donc avancer dans ce cas et on verra par la suite.


Connectez-vous ou Inscrivez-vous pour répondre.