Obtenir le contenu d'une vue sous forme d'image

laurrislaurris Membre
février 2007 modifié dans API AppKit #1
Comme l'indique le titre, je souhaiterais obtenir le contenu d'une vue sous forme d'image.

J'ai bien trouvé ceci http://www.cocoadev.com/index.pl?GettingViewContentAsImage sur Cocoadev sans être convaincu toutefois (trop compliqué).
Connaissez-vous un moyen plus simple ?

Mes respects,
Laurris.

Edit: je précise que j'ai lu ce topic de Bru: http://www.objective-cocoa.org/forum/index.php?topic=1556.0. Si possible, j'aimerais ne pas utiliser des fonctions non-documetées et si possible en Objective-C. Si pas possible j'utiliserai le code de Bru qui m'a l'air très efficace.

Réponses

  • ChachaChacha Membre
    23:27 modifié #2
    dans 1171486710:

    Comme l'indique le titre, je souhaiterais obtenir le contenu d'une vue sous forme d'image.


    Oui, en fait il y a une fonction toute faite pour cela. Le code de Bru est utile pour les fenêtres, mais pour les vues, tu peux regarder du côté de
    <br />- (NSData *)dataWithPDFInsideRect:(NSRect)aRect<br />
    

    Cette fonction te renvoie le contenu de la vue sous forme de données PDF.
    Exemple d'utilisation :
    <br />NSData* pdfData = [laVue dataWithPDFInsideRect:[laVue frame]];<br />NSImage* pdfImage = [[NSImage alloc] initWithData:pdfData];<br />//et si tu n&#39;aimes pas le PDF<br />NSData* tiffData = [pdfImage TIFFRepresentation];<br />NSImage* tiffImage = [[NSImage alloc] initWithData:tiffData];<br />...<br />[pdfImage release];<br />[tiffImage release];<br />
    


    +
    Chacha
  • laurrislaurris Membre
    23:27 modifié #3
    Merci Chacha,
    J'essaie de faire du drag'n drop de NSView et tu viens de me rendre un fier service !
  • schlumschlum Membre
    février 2007 modifié #4
    J'espère qu'on m'en voudra pas trop de linker un autre forum, mais y avait un sujet pas mal là  (difficile à  résumer rapidement...)

    Ce sujet linkait d'ailleurs un autre ici : http://www.objective-cocoa.org/forum/index.php?topic=1654.msg16730#msg16730

    En gros, si ta vue n'a pas de sous-vue, la bonne solution est de faire :
    [mon_image lockFocus];<br />[ma_vue drawRect:[ma_vue:bounds]];<br />[mon_image unlockFocus];
    


    Sinon, la méthode de Chacha, mais qui semble "lente" d'après les témoignages. ("la conversion en data qui a l'air un peu gourmande")
  • 23:27 modifié #5
    La solution proposée par Chacha peut être un peu plus lente, mais elle a au moins le mérite de fonctionner dans tous les cas: j'avais essayé il y a quelque temps un truc similaire à  ce que tu proposes (sauf qu'à  la place d'une image tu avais une CGLayer) et pour certaines classes qui utilisaient des NSCell, l'image générée était incomplète. Mais pour les classes perso, ça ne devrait effectivement pas causer de problèmes.
  • schlumschlum Membre
    23:27 modifié #6
    dans 1171524547:

    La solution proposée par Chacha peut être un peu plus lente, mais elle a au moins le mérite de fonctionner dans tous les cas: j'avais essayé il y a quelque temps un truc similaire à  ce que tu proposes (sauf qu'à  la place d'une image tu avais une CGLayer) et pour certaines classes qui utilisaient des NSCell, l'image générée était incomplète. Mais pour les classes perso, ça ne devrait effectivement pas causer de problèmes.


    Voui, en général c'est foireux quand il y a des sous-vues (subviews)...
    Comme une NSCell est une sous-vue d'un contrôle, ça ne passe pas (ça dessine le contenu de la vue principale, mais pas des sous-vues)...

    J'avais tenté d'écrire une fonction qui dessinait récursivement les vues et sous-vues, mais manquait les focus et autres effets pour une obscure raison.
  • 23:27 modifié #7
    Une NSCell n'est pas une vue: c'est un objet qu'on pourrait plus assimiler à  un cachet: tu la règles pour l'élément, tu appliques le cachet, tu modifies pour le suivant, tu appliques,... Il y a aussi une gestion assez rudimentaire des événéments, mais ça n'en fait pas une vue pour autant.
  • schlumschlum Membre
    février 2007 modifié #8
    dans 1171528150:

    Une NSCell n'est pas une vue: c'est un objet qu'on pourrait plus assimiler à  un cachet: tu la règles pour l'élément, tu appliques le cachet, tu modifies pour le suivant, tu appliques,... Il y a aussi une gestion assez rudimentaire des événéments, mais ça n'en fait pas une vue pour autant.


    Oui c'est vrai... C'était les NSCell qui étaient mal dessinées dans ma fonction récursive en fait...

    Ceci dit, elles ont tout de même une fonction :

    - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
    


    Donc il doit y avoir moyen d'arranger ça.
  • 23:27 modifié #9
    Comme d'habitude, le problème n'est pas ce qu'on fait soi-même, mais ce qu'on nous propose de tout fait. Certains sous-classes de NSActionCell fournies avec l'AppKit causent ont des problèmes lorsqu'elles ne sont pas dessinées dans une vue.
  • schlumschlum Membre
    février 2007 modifié #10
    dans 1171536848:

    Comme d'habitude, le problème n'est pas ce qu'on fait soi-même, mais ce qu'on nous propose de tout fait. Certains sous-classes de NSActionCell fournies avec l'AppKit causent ont des problèmes lorsqu'elles ne sont pas dessinées dans une vue.


    Tu veux dire qu'il n'y a pas moyen de dessiner correctement une NSCell dans une image ?

    Il y a peut-être quelques complications avec NSTableView qui a une seule NSCell par colonne re-dessinée pour chaque ligne.
  • février 2007 modifié #11
    Non non, je veux dire que l'implémentation de certaines cells fournies avec l'appkit est telle qu'il faut qu'elles soient dessinées directement dans une vue pour qu'elles soient correctement dessinées*. Maintenant que la dite cell soit répétée ou non ne change pas grand chose.

    Donc tout ça pour dire que la méthode que tu as proposé ne cause pas de problèmes si on l'applique dans une vue où on contrôle absolument tout, mais qu'il faut la considérer comme aléatoire lorsqu'il y a un élément qu'on a pas codé soi-même (ou pour lesquels on a pas accès aux sources).

    *bon, si tu veux les détails:
    Les cells en questions sont (notamment) les sous classes de NSActionCell, autrement dit, les contrôles. Ces classes utilisent l'Appearance Manager pour le dessin dans leur implémentation, ce sont donc des fonctions Carbon, qui doivent prendre comme argument un CGContextRef. Pour obtenir ce contexte dans l'implémentation de ces cells, elles prennent de NSGraphicsContext associé à  la fenêtre de la vue passé en argument de [tt]drawWithFrame:inView:[/tt], et dessinent directement dedans. Donc avec la méthode que tu donnes, ça ne peut pas marcher, car les éléments qui dépendent de l'Appearance Manager sont dessiné dans le contexte de la fenêtre, et ce qui n'en dépend pas est dessiné dans le contexte courant, qui est alors l'image.

    Mais ce cas pourrait aussi se présenter pour certains cells qui utilisent dans leur implémentation des appels directs à  CoreGraphics. Et dans la mesure où Cocoa ne permet pas de faire tout ce que CoreGraphics permet (bête exemple: les dégradés), des appels à  CoreGraphics ne sont pas à  exclure dans l'implémentation de certaines sous-classes de NSCell et donc le fonctionnement de la méthode que tu donnes n'est pas garanti. ça peut fonctionner si le contexte est obtenu avec [tt][NSGraphicsContext currentContext][/tt], mais pas si le contexte est obtenu en prenant celui de la fenêtre de la vue.
  • schlumschlum Membre
    23:27 modifié #12
    dans 1171541822:

    *bon, si tu veux les détails:
    Les cells en questions sont (notamment) les sous classes de NSActionCell, autrement dit, les contrôles. Ces classes utilisent l'Appearance Manager pour le dessin dans leur implémentation, ce sont donc des fonctions Carbon, qui doivent prendre comme argument un CGContextRef. Pour obtenir ce contexte dans l'implémentation de ces cells, elles prennent de NSGraphicsContext associé à  la fenêtre de la vue passé en argument de [tt]drawWithFrame:inView:[/tt], et dessinent directement dedans. Donc avec la méthode que tu donnes, ça ne peut pas marcher, car les éléments qui dépendent de l'Appearance Manager sont dessiné dans le contexte de la fenêtre, et ce qui n'en dépend pas est dessiné dans le contexte courant, qui est alors l'image.

    Mais ce cas pourrait aussi se présenter pour certains cells qui utilisent dans leur implémentation des appels directs à  CoreGraphics. Et dans la mesure où Cocoa ne permet pas de faire tout ce que CoreGraphics permet (bête exemple: les dégradés), des appels à  CoreGraphics ne sont pas à  exclure dans l'implémentation de certaines sous-classes de NSCell et donc le fonctionnement de la méthode que tu donnes n'est pas garanti. ça peut fonctionner si le contexte est obtenu avec [tt][NSGraphicsContext currentContext][/tt], mais pas si le contexte est obtenu en prenant celui de la fenêtre de la vue.


    Okay, je comprends mieux !
    Il faudrait en fait une méthode "- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView context:(CGContextRef)context" (qui n'existe pas, et donc c'est râpé...)
  • laurrislaurris Membre
    23:27 modifié #13
    Moi j'ai utilisé la methode de Chacha - (NSData *)dataWithPDFInsideRect:(NSRect)aRect pour déplacer l'image de la vue pendant un drag et je n'ai pas été choqué par un manque de rapidité. Selon la doc, cette methode est appropriée pour un remplissage de pasteboard.

    Par contre, j'obtiens ceci dans la console. On dirait que des méthodes CG dépréciées sont appelées à  travers cette méthode alors que la doc ne n'indique pas la méthode mère comme elle-même dépréciée. Et CGPDFPageGetBoxRect, est-ce qu'il serait possible de l'appeler directement pour contourner le problème ...
    <br />The function `CGPDFDocumentGetMediaBox&#39; is obsolete and will be removed in an upcoming update. Unfortunately, this application, or a library it uses, is using this obsolete function, and is thereby contributing to an overall degradation of system performance. Please use `CGPDFPageGetBoxRect&#39; instead.<br />
    
  • ChachaChacha Membre
    23:27 modifié #14
    dans 1171570554:

    Par contre, j'obtiens ceci dans la console.
    <br />The function `CGPDFDocumentGetMediaBox&#39; is obsolete and will be removed in an upcoming update.<br />
    



    Selon toute logique, lors d'un appel à  dataWithPDFInsideRect, la vue est redessinée (drawRect est appelé). Es-tu sûr que ce n'est pas dans ce code de dessin, que CGPDFDocumentGetMediaBox est appelé ?

    +
    Chacha
  • schlumschlum Membre
    février 2007 modifié #15
    La CallStack donne :
    CGPDFDocumentGetMediaBox<br />-[_NSPDFDocument setCurrentPage:]<br />-[_NSPDFDocument initWithData:]<br />-[NSPDFImageRep initWithData:]<br />+[NSPDFImageRep imageRepWithData:]<br />-[NSImage initWithData:]
    


    Donc c'est le initWithData avec des données PDF qui provoque cet appel et il est loin d'être direct, donc pour remplacer...

    Par contre, chez moi, rien n'indique que cette fonction est "deprecated"... T'as activé une option ou une macro spéciale pour voir ça ? (j'ai bien "Warn About Deprecated Functions" de coché dans les "Build settings")

    (car effectivement, elle est "deprecated" depuis 10.3)
Connectez-vous ou Inscrivez-vous pour répondre.