IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

2D Java Discussion :

Probleme de performance avec objet Graphics d'une image.


Sujet :

2D Java

  1. #1
    Nouveau membre du Club
    Profil pro
    Inscrit en
    Juin 2005
    Messages
    68
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2005
    Messages : 68
    Points : 32
    Points
    32
    Par défaut Probleme de performance avec objet Graphics d'une image.
    Bonjour à tous,

    Je développe une petite application de manipulation d'image en Java.
    Dans un certain cas j'ai besoin de définir un "masque" par dessus une image. Le masque se dessine à la souris avec un gros pinceau (grossièrement) et s'affiche en transparence par dessus l'image.
    On doit pouvoir modifier la couleur du masque si on en a envie (rouge par défaut).

    Pour faire ça j'utilise une BufferedImage de type INT_ARGB que j'affiche par dessus l'image de base, j'appelle cette image "mask".
    Pour dessiner dans mon masque j'ai le code suivant :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     
                // get image graphics object
                final Graphics2D g = (Graphics2D) mask.getGraphics();
     
                // set no alpha
                g.setComposite(AlphaComposite.Src);
                // set color depending "remove" or "add" to mask
                if (remove)
                    g.setColor(new Color(0x00000000, true));
                else
                    g.setColor(new Color(0x80000000, true));
                // draw cursor in the mask
                g.fill(cursor);
    L'objet Cursor est un "Shape" que j'utilise pour déterminer la form du pinceau.

    Ensuite pour l'affichage en temps que tel (image de base + le masque) j'utilise le code suivant :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
     
            public void paint(Graphics g)
            {
                // get graphics object of mask
                final Graphics2D g2m = (Graphics2D) mask.getGraphics();
     
                // we want to use source alpha information to mix color
                g2m.setComposite(AlphaComposite.SrcIn);
                // set mask color
                g2m.setColor(getMaskColor());
                // THIS IS SLOW !
                g2m.fillRect(0, 0, mask.getWidth(), mask.getHeight());
     
                final Graphics2D g2 = (Graphics2D) g;
     
                // draw base image
                g2.drawImage(image, null, 0, 0);
                // draw mask
                g2.drawImage(mask, null, 0, 0);
             }
    Mon problème étant, comme vous l'avez peut être vu dans les commentaires du code, que la méthode "g2m.fillRect(...)" n'est pas assez rapide, elle devient même carrément lente sur de grosses images (5000 x 5000).
    Je n'arrive pas à comprendre pourquoi puisque si j'essaie de faire la même opération sur l'objet "g2" alors ça reste très rapide même sur de très large surface (10000 x 10000) ! Je sais que l'objet "g2" représente le contexte graphique actuel mais je pense que Java 6 est capable de délivrer des "BufferedImage" accélérés dans la plupart des cas (et surtout pour une image de type INT_ARGB). J'ai essayé les différents xxxGraphicsDevice.createCompatibleImage(...) ou même createVolatileImage(...) mais sans succés

    Honnêtement je suis sûr qu'il y des bien meilleurs méthodes pour afficher un masque coloré en transparence sur une image mais pour le moment je n'ai pas trouvé J'ai essayé d'utiliser une image de type BYTE_GRAY qui contenait 0x00 (transparent) et 0xFF (non transparent) pour le masque afin de réduire la charge mémoire du masque (après tout 1 bit devrait suffire) mais avec ce type d'image je n'ai pas été capable d'afficher le masque correctement puisque la méthode "g2m.setComposite(AlphaComposite.SrcIn)" n'a de sens qu'avec une information de type alpha ! De plus impossible d'afficher le masque autrement qu'en niveau de gris

    Mais je me dis que j'ai du louper quelque chose, il n'est pas normal de devoir déclarer une image INT_ARGB pour afficher un pauvre masque mono couleur avec un degré de transparence fixé ! théoriquement 1 bit suffit ... pourtant je ne vois pas comment m'en sortir sans passer par une fichue image ARGB à un moment donné.

    Merci à tout ceux qui pourront me venir en aide. Le plus important ça serait déjà de pouvoir corriger mon problème de performance sur le "g2m.fillRect(...)", ensuite la méthode, c'est secondaire... même si la méthode actuelle ne me convient pas vraiment

  2. #2
    Expert éminent sénior
    Avatar de sinok
    Profil pro
    Inscrit en
    Août 2004
    Messages
    8 765
    Détails du profil
    Informations personnelles :
    Âge : 43
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Août 2004
    Messages : 8 765
    Points : 12 977
    Points
    12 977
    Par défaut
    Y-a-t'il une raison quelconque pour que tu redessines sur le masque à chaque repaint? Ne suffirait-il pas de faire ça simplement à chaque changement de couleur? Car les composites ont une certaines tendance à être relativement lourd niveau traitement.

    Secondement assures toi vraiment que tu as bien rendu tom image compatible, le gain en traitement est visible.

    Les VolatileImage sont des bestioles un tant soit peu récalcitrantes, cf JavaDoc, donc à éviter autant que peut se faire:

    Citation Envoyé par javadoc
    The drawing surface of an image (the memory where the image contents actually reside) can be lost or invalidated, causing the contents of that memory to go away. The drawing surface thus needs to be restored or recreated and the contents of that surface need to be re-rendered. VolatileImage provides an interface for allowing the user to detect these problems and fix them when they occur.
    Hey, this is mine. That's mine. All this is mine. I'm claiming all this as mine. Except that bit. I don't want that bit. But all the rest of this is mine. Hey, this has been a really good day. I've eaten five times, I've slept six times, and I've made a lot of things mine. Tomorrow, I'm gonna see if I can't have sex with something.

  3. #3
    Nouveau membre du Club
    Profil pro
    Inscrit en
    Juin 2005
    Messages
    68
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2005
    Messages : 68
    Points : 32
    Points
    32
    Par défaut
    Citation Envoyé par sinok Voir le message
    Y-a-t'il une raison quelconque pour que tu redessines sur le masque à chaque repaint? Ne suffirait-il pas de faire ça simplement à chaque changement de couleur? Car les composites ont une certaines tendance à être relativement lourd niveau traitement.

    Secondement assures toi vraiment que tu as bien rendu tom image compatible, le gain en traitement est visible.

    Les VolatileImage sont des bestioles un tant soit peu récalcitrantes, cf JavaDoc, donc à éviter autant que peut se faire:
    Merci pour ta réponse
    Disons que redessiner à chaque repaint est plus simple car mon getMaskColor() utilise "une formule" pour être calculé, l'état peut changer souvent, au final le problème de lenteur se posera à un autre moment mais sera tout aussi gênant... et puis naïvement je pensais que le rectFill() était très rapide, ce que je ne comprends pas c'est qu'il l'est quand je le fais sur l'objet Graphics renvoyé par la méthode paint()...

    Pour la compatibilité de mon image, j'ai essayé de passer par :
    GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration().createCompatibleImage(w, h, alpha);

    Ca me retourne une image INT_ARGB ce qui me parait normal puisque ça semble être le type "optimal" quand tu veux une composante alpha. Malgré tout ça reste aussi lent... j'ai essayé de modifier le AccelerationPriority mais ça ne change rien.

  4. #4
    Nouveau membre du Club
    Profil pro
    Inscrit en
    Juin 2005
    Messages
    68
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2005
    Messages : 68
    Points : 32
    Points
    32
    Par défaut
    J'ai fait quelque tests supplémentaire, si mon masque est une image INT_RGB (sans transparence donc) ou si je n'utilise pas la méthode setComposite alors effectivement l'affichage est plus rapide, MAIS ça reste bien plus lent que de dessiner dans l'objet Graphics global, un bête fillRect(0,0, 5000, 3000) prend environ 50ms (et j'ai une grosse config) alors que sur l'objet Graphics global ça prend environ 1ms Il y a donc bien un problème sur mon image qui n'est décidément pas accélérée...

  5. #5
    Nouveau membre du Club
    Profil pro
    Inscrit en
    Juin 2005
    Messages
    68
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2005
    Messages : 68
    Points : 32
    Points
    32
    Par défaut
    Je me fais une raison, je crois que excepté via un VolatileImage (que je ne peux pas utiliser dans mon cas), les "offscreen buffers" sont lents quoiqu'il arrive en Java

    J'aimerai bien trouver une méthode plus intelligente pour rendre ce fichu masque

  6. #6
    Expert éminent sénior
    Avatar de sinok
    Profil pro
    Inscrit en
    Août 2004
    Messages
    8 765
    Détails du profil
    Informations personnelles :
    Âge : 43
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Août 2004
    Messages : 8 765
    Points : 12 977
    Points
    12 977
    Par défaut
    Préparer ton masque dans un Thread et demander le repaint une fois la préparation finie (ie à la fin de l'exécution du Thread).

    En effet toute opération lente dans un paint est un gros problème puisque bloquant l'EDT, et de fait toute l'application.
    Hey, this is mine. That's mine. All this is mine. I'm claiming all this as mine. Except that bit. I don't want that bit. But all the rest of this is mine. Hey, this has been a really good day. I've eaten five times, I've slept six times, and I've made a lot of things mine. Tomorrow, I'm gonna see if I can't have sex with something.

  7. #7
    Nouveau membre du Club
    Profil pro
    Inscrit en
    Juin 2005
    Messages
    68
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2005
    Messages : 68
    Points : 32
    Points
    32
    Par défaut
    Citation Envoyé par sinok Voir le message
    Préparer ton masque dans un Thread et demander le repaint une fois la préparation finie (ie à la fin de l'exécution du Thread).

    En effet toute opération lente dans un paint est un gros problème puisque bloquant l'EDT, et de fait toute l'application.
    Oui oui bien sur je peux passer par un thread pour ne pas bloquer l'EDT mais pour moi le problème c'est simplement de ne pas être capable de rafraichir mon masque à pleine vitesse (même de grande taille). C'est tellement lent qu'une implémentation pure software sur le changement de couleur serait encore la meilleur solution en tout cas merci pour ton aide, je vais continuer un peu à chercher des solutions et voir ce que ça donne en soft !

  8. #8
    Expert éminent sénior
    Avatar de sinok
    Profil pro
    Inscrit en
    Août 2004
    Messages
    8 765
    Détails du profil
    Informations personnelles :
    Âge : 43
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Août 2004
    Messages : 8 765
    Points : 12 977
    Points
    12 977
    Par défaut
    En même temps un offscreen buffer qui se recalcule à chaque repaint, ça ne sert grosso modo à rien. Autant faire directement l'opération sur les Graphics du composant, ça t'évitera un drawImage et tu profiteras du pipeline accéléré (D3D/OpenGL) de Swing...

    Le but de l'offscreen buffer étant d'être calculé en parallèle et de déclencher l'affichage une fois prêt.
    Hey, this is mine. That's mine. All this is mine. I'm claiming all this as mine. Except that bit. I don't want that bit. But all the rest of this is mine. Hey, this has been a really good day. I've eaten five times, I've slept six times, and I've made a lot of things mine. Tomorrow, I'm gonna see if I can't have sex with something.

  9. #9
    Nouveau membre du Club
    Profil pro
    Inscrit en
    Juin 2005
    Messages
    68
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2005
    Messages : 68
    Points : 32
    Points
    32
    Par défaut
    Citation Envoyé par sinok Voir le message
    En même temps un offscreen buffer qui se recalcule à chaque repaint, ça ne sert grosso modo à rien. Autant faire directement l'opération sur les Graphics du composant, ça t'évitera un drawImage et tu profiteras du pipeline accéléré (D3D/OpenGL) de Swing...

    Le but de l'offscreen buffer étant d'être calculé en parallèle et de déclencher l'affichage une fois prêt.
    Je suis tout à fait d'accord mais justement c'est là où je dis que mon implémentation est foireuse et malheureusement je n'ai pas trouvé de meilleur méthode pour faire ça : afficher un masque en transparence par dessus une image avec comme contrainte un changement "rapide" de couleur du masque (la couleur dépend de la position de la souris, d'ou la nécessité d'un refresh rapide).

    Je viens de changer mon code, j'ai toujours mon masque sous forme d'une image INT_ARGB mais maintenant je modifie la couleur du masque à la main lorsque la couleur change, sur une image de 10000x5000 c'est pas hyper rapide mais c'est toujours bien plus rapide que de passer par le setComposite(...) et le fillRect(...)

    Honnêtement si quelqu'un me trouve une meilleur façon de faire ça uniquement avec des opérations sur le Graphics du composant je suis preneur j'ai essayé pas mal de choses mais rien ne me permet d'arriver au résultat voulu pourtant je suis certain qu'il y a moyen.

  10. #10
    Expert éminent sénior
    Avatar de sinok
    Profil pro
    Inscrit en
    Août 2004
    Messages
    8 765
    Détails du profil
    Informations personnelles :
    Âge : 43
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Août 2004
    Messages : 8 765
    Points : 12 977
    Points
    12 977
    Par défaut
    Tu pourrais commencer faire faire un setClip sur les graphics de l'image afin de limiter la taille des modifs à un rectangle donné. (setClip prend au choix un rectangle ou une Shape) En effet faire des traitements sur une image de cette taille ce n'est de loin pas anodin.
    Hey, this is mine. That's mine. All this is mine. I'm claiming all this as mine. Except that bit. I don't want that bit. But all the rest of this is mine. Hey, this has been a really good day. I've eaten five times, I've slept six times, and I've made a lot of things mine. Tomorrow, I'm gonna see if I can't have sex with something.

  11. #11
    Nouveau membre du Club
    Profil pro
    Inscrit en
    Juin 2005
    Messages
    68
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2005
    Messages : 68
    Points : 32
    Points
    32
    Par défaut
    Citation Envoyé par sinok Voir le message
    Tu pourrais commencer faire faire un setClip sur les graphics de l'image afin de limiter la taille des modifs à un rectangle donné. (setClip prend au choix un rectangle ou une Shape) En effet faire des traitements sur une image de cette taille ce n'est de loin pas anodin.
    En fait la taille de l'image du masque est dynamique et dépend de son "bounds" : je tiens un Rectangle qui contient toujours la taille "optimal" pour contenir tout les pixels du masque et l'image du masque est dynamiquement modifié en fonction de la taille du rectangle, au final quand je dois changer la couleur du masque, je dois toujours travailler sur l'intégralité de l'image puisque sa taille est adapté aux pixels contenus. Bien sur c'est uniquement quand la taille du masque devient vraiment importante que le vitesse devient un problème mais ça nous arrive de manipuler de grandes images (on a déjà eu des 50000x15000 mais c'est exceptionnel). Le fait est que, même avec de grandes images j'aimerai que cela reste "relativement" rapide. Ma solution actuelle ne s'en sort pas si mal mais je pense qu'on peut faire mieux (en code comme en performance)

  12. #12
    Rédacteur/Modérateur

    Avatar de bouye
    Homme Profil pro
    Information Technologies Specialist (Scientific Computing)
    Inscrit en
    Août 2005
    Messages
    6 840
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : Nouvelle-Calédonie

    Informations professionnelles :
    Activité : Information Technologies Specialist (Scientific Computing)
    Secteur : Agroalimentaire - Agriculture

    Informations forums :
    Inscription : Août 2005
    Messages : 6 840
    Points : 22 854
    Points
    22 854
    Billets dans le blog
    51
    Par défaut
    Utilises-tu une image compatible ?
    Merci de penser au tag quand une réponse a été apportée à votre question. Aucune réponse ne sera donnée à des messages privés portant sur des questions d'ordre technique. Les forums sont là pour que vous y postiez publiquement vos problèmes.

    suivez mon blog sur Développez.

    Programming today is a race between software engineers striving to build bigger and better idiot-proof programs, and the universe trying to produce bigger and better idiots. So far, the universe is winning. ~ Rich Cook

  13. #13
    Nouveau membre du Club
    Profil pro
    Inscrit en
    Juin 2005
    Messages
    68
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2005
    Messages : 68
    Points : 32
    Points
    32
    Par défaut
    Citation Envoyé par bouye Voir le message
    Utilises-tu une image compatible ?
    Oui oui, j'ai tout essayé à ce niveau mais ça ne change rien...
    et en cherchant dans la doc java, j'ai vu que sur un offscreen buffer (comprendre pas là que tu utilises le Graphics d'une image plutot que celui du Paint() pour dessiner) seules les VolatileImage peuvent être accélérés mais dans mon cas j'ai vraiment besoin d'un BufferedImage.

    Sinon j'ai essayé d'obtenir le rendu voulu en utilisant que le Graphics du paint() mais les différentes possibilités du setComposite() ne me permettent pas d'obtenir ce que je veux. Au mieux j'ai mon masque qui s'affiche mais en gris

  14. #14
    Rédacteur/Modérateur

    Avatar de bouye
    Homme Profil pro
    Information Technologies Specialist (Scientific Computing)
    Inscrit en
    Août 2005
    Messages
    6 840
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : Nouvelle-Calédonie

    Informations professionnelles :
    Activité : Information Technologies Specialist (Scientific Computing)
    Secteur : Agroalimentaire - Agriculture

    Informations forums :
    Inscription : Août 2005
    Messages : 6 840
    Points : 22 854
    Points
    22 854
    Billets dans le blog
    51
    Par défaut
    L'ennui c'est que je ne vois pas du tout dans le code que tu as posté en haut une raison quelconque pourquoi tu ne peux pas te restreindre à travailler sur une sous-zone du graphics/buffer offscreen telle que définie par repaint(x, y, w, h) et graphics.getClip().getBounds2D() d'une part et ensuite les tailles d'images que tu nous donnes sont tout simplement énooooooooooooooooooooooooormes, et ça me semble tout à fait normal que ce soit super-lent pour une telle taille, en plus à moins d'une transformation affine ultérieure, elle ne devrait pas être affichée intégralement à l'écran. Bref pour moi c'est clair tu dois revoir ta manière de procéder et travailler "par morceaux"*.

    Une (buffered) image compatible est dans le format interne de la carte vidéo ce qui signifie qu'il n'y a pas de conversion (d'un modèle de couleur à un autre) lors de son affichage à l'écran ce qui accélère déjà énormément l'affichage. De plus les buffered images sont accélérées autant que possible tant que tu n'accèdes pas directement à leur raster (l'accélération est perdue dès le premier accès au raster), d'où le fait qu'il vaut mieux faire set/getRBG que getRaster().<whatever>....

    *Mais bon c'est vite dit comme ça sans savoir si c'est critique ou pas.
    T'est-il possible de nous fournir un code un peu plus complet et fonctionnel qu'on puisse tester ça ?

    Il existe d'autres types d'images dont nottament les PlanarImage utilisées par le JAI (Java Advanced Imaging) qui permettent de travailler sur des images constituées de tiles (on découpe l'image en morceaux justement, pas forcément tous chargés en mémoire du 1er coup). La plupart des algos du JAI sont optimisés pour fonctionner de cette manière (par morceaux mais aussi avec des variantes natives des algorithmes même le JAI Java pur m'a toujours suffit).
    Merci de penser au tag quand une réponse a été apportée à votre question. Aucune réponse ne sera donnée à des messages privés portant sur des questions d'ordre technique. Les forums sont là pour que vous y postiez publiquement vos problèmes.

    suivez mon blog sur Développez.

    Programming today is a race between software engineers striving to build bigger and better idiot-proof programs, and the universe trying to produce bigger and better idiots. So far, the universe is winning. ~ Rich Cook

  15. #15
    Nouveau membre du Club
    Profil pro
    Inscrit en
    Juin 2005
    Messages
    68
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2005
    Messages : 68
    Points : 32
    Points
    32
    Par défaut
    Merci de te pencher sur mon problème =)

    Effectivement je n'ai peut-être pas donné assez de détails sur ce que je fais.
    Les images qu'on manipule sont assez grandes car proviennent souvent de microscope. On peut avoir du 1024x1024 mais ça arrive également à des résolutions très importantes comme 10000x5000 même si heureusement ces dernières sont moins fréquentes. Effectivement pour afficher l'image dans sa totalité j'utilise une transformation affine qui gère un facteur zoom. L'image et le masque sont donc tout les 2 transformés selon ce coefficient de zoom.

    Voici un exemple de ce que je peux avoir :



    La résolution de l'image est de 10236 x 3939, la taille du masque (en bleu clair ici) est environ la même (on voit qu'il déborde un peu sur les côtés). Le masque est donc modifiable simplement comme avec un pinceau (le rouge rouge sur la gauche), le rectangle bleu désigne le contour du masque, en passant la souris dessus tout le masque devient "focused" ce qui permet de faire une opération de "drag" du masque sur l'image. L'état focused est indiqué par un changement de couleur du masque, dans le cas présent je suis bien obligé de modifier l'intégralité du masque. Si le coefficient du zoom est tel que seule une partie de l'image est affichée alors d'accord, je pourrais clipper mais disons que je fais mes tests sur les "worst cases".

    Ce que tu dis à propos de l'accélération perdue à partir du premier accès à getRaster() est très interessant ! Je ne le savais pas et en effet je fais directement un accès au raster dés la création de l'image. Je vais faire des tests de ce côté, malheureusement l'accés à getRaster() me permet certaines manipulations de bas niveau plus rapidement (comme recalculer le "bounds" lorsque j'efface une partie du masque) et je ne sais pas si je peux m'en passer...

    Je connais JAI mais je ne suis pas sur que ça puisse m'aider pour ce que je veux faire ici, puisque toute l'image doit de toute manière être rafraichie.

    Pour un code plus complet, excepté la transformation affine que je n'ai pas fait apparaitre je ne sais pas ce qu'il pourrait apporter de plus, surtout que vu comment c'est dispatché en plusieurs classes ça va m'être assez difficile.

    Je suis d'accord qu'il s'agit de grande images, malgré tout je pense qu'il est possible avec la puissance des machines d'aujourd'hui de rafraichir un "bête masque" rapidement, d'ailleurs en le faisant à la main c'est "acceptable" mais la solution n'est pas clean et en exploitant l'accélération graphique y'a moyen d'aller beaucoup plus vite

  16. #16
    Nouveau membre du Club
    Profil pro
    Inscrit en
    Juin 2005
    Messages
    68
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2005
    Messages : 68
    Points : 32
    Points
    32
    Par défaut
    A tout hasard j'ai essayé avec un BufferedImage auquel je n'accède pas au getRaster(), aucun changement... j'ai même essayé avec un VolatileImage (créé avec createCompatibleVolatileImage(...)) et pareil, c'est toujours lent

  17. #17
    Rédacteur/Modérateur

    Avatar de bouye
    Homme Profil pro
    Information Technologies Specialist (Scientific Computing)
    Inscrit en
    Août 2005
    Messages
    6 840
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : Nouvelle-Calédonie

    Informations professionnelles :
    Activité : Information Technologies Specialist (Scientific Computing)
    Secteur : Agroalimentaire - Agriculture

    Informations forums :
    Inscription : Août 2005
    Messages : 6 840
    Points : 22 854
    Points
    22 854
    Billets dans le blog
    51
    Par défaut
    Et paf insomnie... autant réfléchir au problème.

    Personnellement je pense que plutot que d'essayer de régler des problèmes d'optimisation de rendu d'images de grandes tailles, il faudra plutot revoir complètement la conception de l'affichage.

    Déjà il me parait complètement abérant que tu manipules des images de cette taille et plus encore un masque de taille identique (ce qui, non-obstant les variations dues au modèle de stockage des couleurs, rajoute de toute manière une seconde image énorme en mémoire et te rapproche d'autant plus de la OutOfMemoryException).

    Ne serait-ce que que parce que de toute manière tu fais une transformation affine à la fin pour afficher le tout, autant prendre le problème à l'envers ; il faut apprendre à faire ce que fait tout bon logiciel ammené à manipuler des grandes images et/ou en grande quantité fait : il faut apprendre à jouer avec les niveaux de détails.
    • Ton buffer de travail offscreen, il fait la taille de ton écran ou de ta surface affichable (si tu utilises une sous-partie de l'écran). Pas plus, il ne fait certainement pas la taille de l'image source.
    • Tu implémentes le concept de viewport + vue ce qui permet à ton utilisateur de zoomer sur toute ou une partie de l'image source en mappant un niveau d'échelle et le ratio w/h de l'image source vers le buffer.
      Nom : Render.png
Affichages : 261
Taille : 143,0 Ko
      Note ici dans ce dessin je dessine directement la vue et le masque à l'écran en étape 3 mais si tu veux tu peux rajouter une autre niveau de buffer offscreen. Idem ce dernier buffer ne fait que la taille approprée (taille de l'écran ou de la surface affichable).
    • Une fois que tu en as extrait ce dont tu avait besoin pour l'affichage de ton buffer, ton image source, tu la mets de coté, soit en la gardant en mémoire (pas très bonne idée surtout si l'utilisateur change rarement sa vue) soit en la stockant dans un fichier (c'est ici que PlanarImage peut être utile au fait).
      Si le viewport ne change pas tu n'as aucune raison de regénérer la vue et donc comme tu as déjà une image pré-générée à la bonne taille, tu saute toute cette étape qui est semble-t-il très lente.
    • Le masque, s'il est simplement constitué de formes géométriques déssinées à la souris, c'est une très mauvaise idée que le stocker dans une image bitmap. Autant utiliser le package géom et construire une Area dans laquelle on ajoute des Ellipse2D. Ca bouffe nettement moins de mémoire qu'une bitmap de grande taille et le dessin en est plus rapide. Récupérer la boite englobante d'une Shape est instantannée également.
      Même plus besoin d'utiliser une composite spéciale pour le rendu, une simple couleur transparente suffit.
      Par contre il te faudra travailler dans le repère de ton image source puisque tu peux un masque correct et probablement réutilisable plus tard (pour une export bitmap ou un travail postérieur -reconnaissance de formes ou autre-). Il te faudra donc transformer les coord de la souris en celle de l'image source pour modifier le masque et appliquer une transformation affine sur le masque pour l'afficher (là encore si on veut on peut tricher en ayant un masque à chaque échelle, mais bon encore une fois il s'agit de formes et donc je pense que c'est peu utile -sauf découverte de bugs ultérieurs imprévus-).
    • Même ainsi cela t'exempt pas plus tard de tenter d'optimiser encore plus le rendu des buffers et des shapes en utilisant repaint(x, y, w, h) et la boite englobante de la zone de clip du graphics recut par paintComponent(). Ces optimisations-là sont de toute manière la clé de tout affichage rapide et fluide dans Swing.
    Merci de penser au tag quand une réponse a été apportée à votre question. Aucune réponse ne sera donnée à des messages privés portant sur des questions d'ordre technique. Les forums sont là pour que vous y postiez publiquement vos problèmes.

    suivez mon blog sur Développez.

    Programming today is a race between software engineers striving to build bigger and better idiot-proof programs, and the universe trying to produce bigger and better idiots. So far, the universe is winning. ~ Rich Cook

  18. #18
    Nouveau membre du Club
    Profil pro
    Inscrit en
    Juin 2005
    Messages
    68
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2005
    Messages : 68
    Points : 32
    Points
    32
    Par défaut
    Citation Envoyé par bouye Voir le message
    Et paf insomnie... autant réfléchir au problème
    ....
    Tu es bien courageux ! Et merci d'avoir pris le temps de faire une réponse aussi complète

    Alors je vois très bien ce que tu veux dire quand à la taille de "viewport" qui théoriquement représente la taille maximum de l'image à afficher, en utilisant un système de niveau de détail etc... en fait lorsqu'on a démarré l'application, la question s'est posée sur la manière dont on allait procéder pour ça :
    - soit on choisissait la solution plus complexe d'avoir un offscreen de la taille du viewport dans lequel on se débrouille pour faire le rendu, quelque soit la taille de l'image source et l'échelle courante.
    - soit on posait comme contrainte d'avoir toujours l'image source en mémoire (quelque soit sa taille) et la rend directement dans le viewport selon l'échelle courante via une transformation affine.

    On a choisi la 2ème solution car déjà c'est plus simple à implémenter (ok mauvaise raison ) mais surtout c'est très rapide (oui encore mon obsession de la performance). La transformation affine sur l'objet Graphics du paint() est très rapide et nous avons voulu permettre une utilisation intensive et rapide du zoom (c'est un zoom très smooth qui se controle avec la molette de la souris), pour justement manipuler facilement les images dont la résolution est importante.

    Ce qui me gène surtout sur la première méthode c'est que faire un masque adapté à la taille de l'offscreen (et donc pas à la taille de l'image source) va engendrer des calculs supplémentaire sur la méthode "mask.contains(int x, int y)" et il n'y aura pas de précision au pixel selon l'échelle (si je reste avec une implémentation par image).
    Quand à passer par un Area, j'ai essayé... c'est simplement inutilisable dans ce cadre : plus tu ajoutes de Shape dans le Area plus le "compute" pour la mise à jour interne est longue. Bref, plus tu vas dessiner dedans, plus ça va devenir lent et ça devient vite lent ! C'est dommage car effectivement ça me semblait la solution idéale. Dommage qu'il n'y a pas une méthode .compact() ou dans le genre sur le Area qui lui permettrait d'optimiser la représentation interne de la surface (je ne sais pas si je me fais bien comprendre ^^). Enfin quoiqu'il en soit j'ai du trouver une méthode alternative.

    Je suis conscient que peut-être le choix initial n'a pas été des plus judicieux, surtout par rapport à nos besoins maintenant. Nos priorités était d'avoir une application performante / réactive et intuitive, quitte à augmenter les ressources nécessaires en mémoire. On a de grosses config ici et ça évolue vite, on a parié la dessus ^^

  19. #19
    Rédacteur/Modérateur

    Avatar de bouye
    Homme Profil pro
    Information Technologies Specialist (Scientific Computing)
    Inscrit en
    Août 2005
    Messages
    6 840
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : Nouvelle-Calédonie

    Informations professionnelles :
    Activité : Information Technologies Specialist (Scientific Computing)
    Secteur : Agroalimentaire - Agriculture

    Informations forums :
    Inscription : Août 2005
    Messages : 6 840
    Points : 22 854
    Points
    22 854
    Billets dans le blog
    51
    Par défaut
    Interressant cette perte de performance avec Area d'autant plus que le PathIterator retourné par une Area est optimisé contrairement à celui retourné par Path2D (puisqu'on peut aussi composer des formes entre elles avec Path2D.append() ) ; voir code ci-dessous, la variante avec Path3D conserve intacte toutes ses sous-formes.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
     
     
    package test;
     
    import java.awt.Rectangle;
    import java.awt.Shape;
    import java.awt.geom.Area;
    import java.awt.geom.Path2D;
    import java.awt.geom.PathIterator;
    import java.util.LinkedList;
    import java.util.List;
     
    enum Type {
     
        PATH,
        AREA;
    }
     
    /**
     *
     * @author Fabrice
     */
    public class Main {
     
        static void walkPath(Shape shape) {
            float[] buffer = new float[6];
            for (PathIterator it = shape.getPathIterator(null); !it.isDone(); it.next()) {
                int seg = it.currentSegment(buffer);
                switch (seg) {
                    case PathIterator.SEG_QUADTO:
                        System.out.println("Quad");
                        break;
                    case PathIterator.SEG_MOVETO:
                        System.out.println("Move");
                        break;
                    case PathIterator.SEG_LINETO:
                        System.out.println("Line");
                        break;
                    case PathIterator.SEG_CUBICTO:
                        System.out.println("Cubic");
                        break;
                    case PathIterator.SEG_CLOSE:
                    default:
                        System.out.println("Close");
                }
            }
        }
     
        /**
         * @param args the command line arguments
         */
        public static void main(String[] args) {
            List<Shape> shapes = new LinkedList<Shape>();
            for (int i = 0; i < 20; i++) {
                Rectangle rect = new Rectangle(i * 100, i * 100, 200, 200);
                shapes.add(rect);
            }
            for (Type type : Type.values()) {
                Shape mask = null;
                for (Shape shape : shapes) {
                    switch (type) {
                        case PATH:
                            if (mask == null) {
                                mask = new Path2D.Float(shape);
                            } else {
                                Path2D path = (Path2D) mask;
                                path.append(shape, false);
                            }
                            break;
                        case AREA:
                        default:
                            if (mask == null) {
                                mask = new Area(shape);
                            } else {
                                Area area = (Area) mask;
                                area.add(new Area(shape));
                            }
                    }
                }
                System.out.println(type);
                walkPath(mask);
                System.out.println();
            }
        }
    }
    Cependant justement, il est possible d'optimiser l'aire en re-écricant la méthode walkPath() pour générer un nouveau Path2D à partir de l'Area.
    Merci de penser au tag quand une réponse a été apportée à votre question. Aucune réponse ne sera donnée à des messages privés portant sur des questions d'ordre technique. Les forums sont là pour que vous y postiez publiquement vos problèmes.

    suivez mon blog sur Développez.

    Programming today is a race between software engineers striving to build bigger and better idiot-proof programs, and the universe trying to produce bigger and better idiots. So far, the universe is winning. ~ Rich Cook

  20. #20
    Nouveau membre du Club
    Profil pro
    Inscrit en
    Juin 2005
    Messages
    68
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2005
    Messages : 68
    Points : 32
    Points
    32
    Par défaut
    Citation Envoyé par bouye Voir le message
    Interressant cette perte de performance avec Area d'autant plus que le PathIterator retourné par une Area est optimisé contrairement à celui retourné par Path2D (puisqu'on peut aussi composer des formes entre elles avec Path2D.append() ) ; voir code ci-dessous, la variante avec Path3D conserve intacte toutes ses sous-formes.
    Ca me semble bizarre que le PathIterator retourné par Area soit optimisé et qu'il ne soit pas utilisé en interne pour optimisé l'Area, malgré tout je vais essayer la méthode de "reconstruire le Area à partir de son PathIterator" voir ce que cela donne
    Sinon je ne connais pas Path3D ? ça vient de quelle librairie ?

+ Répondre à la discussion
Cette discussion est résolue.
Page 1 sur 2 12 DernièreDernière

Discussions similaires

  1. probleme de performance avec curseur
    Par donny dans le forum PL/SQL
    Réponses: 6
    Dernier message: 06/08/2009, 14h44
  2. [C#] objet graphics dans une bitmap
    Par lancer83 dans le forum Windows Forms
    Réponses: 2
    Dernier message: 04/08/2006, 14h30
  3. Réponses: 14
    Dernier message: 09/08/2004, 13h42
  4. problemes de performances avec les requetes select
    Par berry dans le forum Requêtes
    Réponses: 3
    Dernier message: 10/07/2003, 13h39
  5. problemes de performances avec les requetes select
    Par berry dans le forum PostgreSQL
    Réponses: 3
    Dernier message: 10/07/2003, 13h39

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo