Le forum (ô combien francophone) des utilisateurs de Powerbuilder.
Bonjour les gens!
Voilà, je reviens avec une petite colle qui, je suis sur, titillera vos neurones...
J'aimerais tout simplement reprendre, en powerscript, l'image bitmap qui se trouve dans le clipboard et l'afficher dans un picture control (et pourquoi pas, la sauver par la suite en jpeg ou bmp).
Tout ce que j'arrive à faire pour l'instant, c'est obtenir un handle du clipboard contenant le bitmap... (en passant par l'api user32.dll).
Après... je ne sais pas comment rapatrier les données de la mémoire, et encore moins comment afficher cela en image.
Des idées? des solutions?
Merci!
Dernière modification par Shed (21-11-2012 09:03:40)
Hors ligne
Pas de solution, mais pour t'aider à jouer avec le presse-papiers, tu peux utiliser l'espion dispo ici et qui te permettra de déterminer quels sont les formats disponibles dans le clipboard après avoir copié l'image, et quel format demander via l'api.
Après je suppose que ça va se jouer avec EnumClipboardFormats(), GetClipboardData() et une fois les données récupérées il est probable qu'on ne peut pas les affecter directement au picture (rien vu dans les fonctions) mais qu'il faille le dessiner sur le rectangle du picture... L'exemple microsoft donne quelques exemple de manipulation du clipboard.
Bon courage
Hors ligne
Dans quoi est-ce que je me suis encore embarqué moi...
Hors ligne
Et en passant par un OLE object (par exemple bitmap picture), pas d'idée? Le problème avec les OLE, c est que c'est toujours trés nébuleux pour en obtenir les propriétés et fonctions...
Hors ligne
Voici un petit essai qui semblait prometteur... mais ça ne fonctionne pas. Une idée?
Les api externes:
Function ulong GetDC(long hWnd) Library "user32.dll" Function ulong CreateCompatibleDC(ulong hdc) Library "gdi32.dll" Function ulong CreateCompatibleBitmap( ulong hdc, long nWidth, long nHeight) Library "gdi32.dll" Function boolean BitBlt(ulong hdcDest, long nXDest, long nYDest, long nWidth, long nHeight, ulong hdcSrc, long nXSrc, long nYSrc, long dwRop) Library "gdi32.dll" Function boolean StretchBlt(ulong hdcDest, long nXOriginDest, long nYOriginDest, long nWidthDest, long nHeightDest, ulong hdcSrc, long nXOriginSrc, long nYOriginSrc, long nWidthSrc, long nHeightSrc, long dwRop) Library "gdi32.dll" Function ulong SelectObject(ulong hdc, ulong hgdiobj) Library "gdi32.dll" Function long GetDIBits (ulong hdc, ulong hbmp, uint uStartScan, uint cScanLines, Ref blob lpvBits, Ref bitmapinfo lpbi, uint uUsage) Library "gdi32.dll" Function long GetDIBits (ulong hdc, ulong hbmp, uint uStartScan, uint cScanLines, ulong lpvBits, ref bitmapinfo lpbi, uint uUsage) Library "gdi32.dll" Subroutine CopyBitmapFileHeader (Ref blob Destination, bitmapfileheader Source, long Length) Library "kernel32.dll" Alias For "RtlMoveMemory" Subroutine CopyBitmapInfo ( Ref blob Destination, bitmapinfo Source, long Length) Library "kernel32.dll" Alias For "RtlMoveMemory"
Le bout de code pour tenter l'affaire:
BitmapInfo lstr_Info BitmapFileHeader lstr_Header Blob lblb_header, lblb_info, lblb_bitmap ULong lul_hdc, lul_hdcMem, lul_hBitmap UInt DIB_RGB_COLORS Integer li_pixels Boolean lb_result long ll_hWnd, al_width, ll_height ll_hWnd = Handle(w_test2) openclipboard(ll_hWnd) // Get the device context of window and allocate memory lul_hdc = GetDC(ll_hWnd) lul_hdcMem = CreateCompatibleDC(lul_hdc) lul_hBitmap = getclipboarddata(2) // Select an object into the specified device context SelectObject(lul_hdcMem, lul_hBitmap) // try to store the bitmap into a blob so we can save it lstr_Info.bmiHeader.biSize = 40 ll_height = 480 DIB_RGB_COLORS = 0 // Get the bitmapinfo If GetDIBits(lul_hdcMem, lul_hBitmap, 0, ll_height, 0, lstr_Info, DIB_RGB_COLORS) > 0 Then li_pixels = lstr_Info.bmiHeader.biBitCount lstr_Info.bmiColors[li_pixels] = 0 lblb_bitmap = Blob(Space(lstr_Info.bmiHeader.biSizeImage/2)) // get the actual bits GetDIBits(lul_hdcMem, lul_hBitmap, 0, ll_height, lblb_bitmap, lstr_Info, DIB_RGB_COLORS) // create a bitmap header lstr_Header.bfType = BITMAPTYPE lstr_Header.bfSize = lstr_Info.bmiHeader.biSizeImage lstr_Header.bfOffBits = 54 + (li_pixels * 4) // copy the header structure to a blob lblb_header = Blob(Space(14/2)) CopyBitmapFileHeader(lblb_header, lstr_Header, 14) // copy the info structure to a blob lblb_Info = Blob(Space((40 + li_pixels * 4)/2)) CopyBitmapInfo(lblb_Info, lstr_Info, 40 + li_pixels * 4) // add all together and we have a window bitmap in a blob lblb_bitmap = lblb_header + lblb_info + lblb_bitmap End If CloseClipboard() picframe.SetPicture(lblb_bitmap)
BitmapInfo, BitmapFileHeader sont des structures définies ici:
http://msdn.microsoft.com/en-us/library … 85%29.aspx
http://msdn.microsoft.com/en-us/library … 85%29.aspx
Dernière modification par Shed (16-11-2012 15:12:32)
Hors ligne
Shed a écrit:
BitmapInfo, BitmapFileHeader sont des structures définies ici:
http://msdn.microsoft.com/en-us/library … 85%29.aspx
http://msdn.microsoft.com/en-us/library … 85%29.aspx
Comment les as-tu traduites en PB ?
Hors ligne
http://infocenter.sybase.com/help/index … GEGBDD.htm
+ encore d'autres sources, la conversion n'est pas compliquée.
Il faut juste faire attention aux pointeurs (variable à passer en référence). Mais il n'y en a pas ici.
Hors ligne
J'ai réussi à le faire tomber en marche
Mes modifs :
- pas de SelectObject()
- la taille du blob du bitmap : je passe par un blob(space(), encodingansi!) au lieu lieu de blob(space( /2) ) des fois que la taille du bitmap ne soit pas paire
- j'utilise un blob final au lieu de concaténer les 3 blobs dans le 3ème : ce n'est pas ça qui faisait merdoyer ? j'ai trouvé ça à la fin en visualisant le blob en hexadécimal
- j'ai ajouté un DeleteDC() à la fin pour être propre
J'ai remarqué que ça ne fonctionne pas pour toutes les images, parfois on a une taille d'image correcte (en visualisant la bordure du picture, mais le picture est vide... bug pb ? à moins que ça ne vienne du "magic number" 54 dans le code : je n'ai pas cherché sa provenance. Enjoy!
BitmapInfo lstr_Info BitmapFileHeader lstr_Header Blob lblb_header, lblb_info, lblb_bitmap, lbl_final ULong lul_hdc, lul_hdcMem, lul_hBitmap, lul_hWnd, lul_blpos UInt DIB_RGB_COLORS Integer li_pixels Boolean lb_result long al_width, ll_height, ll_ret constant integer BITMAPTYPE = 19778 //4D42 = BM constant integer CF_BITMAP = 2 constant integer CF_DIB = 8 lul_hWnd = Handle(w_test) lb_result = OpenClipboard(lul_hWnd) // Get the device context of window and allocate memory lul_hdc = GetDC(lul_hWnd) lul_hdcMem = CreateCompatibleDC(lul_hdc) lul_hBitmap = getclipboarddata(CF_BITMAP) // try to store the bitmap into a blob so we can save it lstr_Info.bmiHeader.biSize = 40 ll_height = 480 // ... 480 devrait suffire pour tout le monde ? ;) DIB_RGB_COLORS = 0 // Get the bitmapinfo If GetDIBits(lul_hdcMem, lul_hBitmap, 0, ll_height, 0, lstr_Info, DIB_RGB_COLORS) > 0 Then li_pixels = lstr_Info.bmiHeader.biBitCount lstr_Info.bmiColors[li_pixels] = 0 lblb_bitmap = Blob(Space(lstr_Info.bmiHeader.biSizeImage), EncodingANSI!) // get the actual bits ll_ret = GetDIBits(lul_hdcMem, lul_hBitmap, 0, ll_height, lblb_bitmap, lstr_Info, DIB_RGB_COLORS) //ls_msg = hexdump_blob(lblb_bitmap, false) // create a bitmap header lstr_Header.bfType = BITMAPTYPE lstr_Header.bfSize = lstr_Info.bmiHeader.biSizeImage lstr_Header.bfOffBits = 54 + (li_pixels * 4) // copy the header structure to a blob lblb_header = Blob(Space(7))//14/2 CopyBitmapFileHeader(lblb_header, lstr_Header, 14) // copy the info structure to a blob lblb_Info = Blob(Space((40 + li_pixels * 4)/2)) CopyBitmapInfo(lblb_Info, lstr_Info, len(lblb_Info)) // add all together and we have a window bitmap in a blob lbl_final = lblb_header + lblb_info + lblb_bitmap End If CloseClipboard() DeleteDC(lul_hdc) picframe.SetPicture(lbl_final)
Pour les archives, les prototypes et les structures sont :
type bitmapinfo from structure bitmapinfoheader bmiheader unsignedlong bmicolors[] end type type bitmapfileheader from structure integer bftype long bfsize integer bfreserved1 integer bfreserved2 long bfoffbits end type type bitmapinfoheader from structure long bisize long biwidth long biheight integer biplanes integer bibitcount long bicompression unsignedlong bisizeimage long bixpelspermeter long biypelspermeter long biclrused long biclrimportant end type Function ulong GetDC(ulong hWnd) Library "user32.dll" Function ulong CreateCompatibleDC(ulong hdc) Library "gdi32.dll" Function ulong CreateCompatibleBitmap( ulong hdc, ulong nWidth, ulong nHeight) Library "gdi32.dll" Function boolean DeleteDC(ulong hdc) Library "gdi32.dll" Function boolean BitBlt(ulong hdcDest, long nXDest, long nYDest, long nWidth, long nHeight, ulong hdcSrc, long nXSrc, long nYSrc, long dwRop) Library "gdi32.dll" Function boolean StretchBlt(ulong hdcDest, long nXOriginDest, long nYOriginDest, long nWidthDest, long nHeightDest, ulong hdcSrc, long nXOriginSrc, long nYOriginSrc, long nWidthSrc, long nHeightSrc, long dwRop) Library "gdi32.dll" Function ulong SelectObject(ulong hdc, ulong hgdiobj) Library "gdi32.dll" Function long GetDIBits (ulong hdc, ulong hbmp, uint uStartScan, uint cScanLines, Ref blob lpvBits, Ref bitmapinfo lpbi, uint uUsage) Library "gdi32.dll" alias for "GetDIBits" Function long GetDIBits (ulong hdc, ulong hbmp, uint uStartScan, uint cScanLines, ulong lpvBits, ref bitmapinfo lpbi, uint uUsage) Library "gdi32.dll" alias for "GetDIBits" Subroutine CopyBitmapFileHeader (Ref blob Destination, bitmapfileheader Source, long Length) Library "kernel32.dll" Alias For "RtlMoveMemory" Subroutine CopyBitmapInfo (Ref blob Destination, bitmapinfo Source, long Length) Library "kernel32.dll" Alias For "RtlMoveMemory" Function boolean OpenClipboard(ulong hWndNewOwner) Library "user32.dll" Function boolean CloseClipboard() Library "user32.dll" Function boolean EmptyClipboard() Library "user32.dll" Function ulong GetClipboardData(ulong uFormat) Library "user32.dll"
Hors ligne
Hummm... Ca ne marche pas chez moi.
Je suis en PB12.1, windows 7 64 bits.
J'ai essayé de sauver le blob en fichier.bmp pour voir, le fichier est bien créé avec un certain poids, mais pas d'image dedans...
Hors ligne
Je viens de retester avec Win7 ce qui fonctionnait vendredi sous XP. Effectivement des images qui étaient prises en compte ne le sont plus. Mais j'avais déjà remarqué que ça ne fonctionnait pas dans tous les cas.
Disons que ça fonctionne "partiellement" : lors de la récupération de l'image dans le presse-papiers, lors du SetPicture dans un contrôle bitmap de la fenêtre pour lequel la bordure est visible, je remarque que la bordure a la taille correcte de l'image que j'ai copié. Mais le cadre reste vide. Il doit y avoir un sac avec la fabrication d'un bitmap à partir des 3 parties header + info + bitmap.
J'essaie de creuser plus bas quand je peux.
Hors ligne
Shed a écrit:
J'ai essayé de sauver le blob en fichier.bmp pour voir, le fichier est bien créé avec un certain poids, mais pas d'image dedans...
En regardant avec un éditeur hexa : tu as un entête qui semble plausible, suivi d'un paquet d'espaces à la place des pixels ? (ce que je vois ici).
Hors ligne
Bon...
Après investigations, voici ce qu'il se passe:
Si le nombre de lignes lues, ici ll_height, est plus petit que le nombre de lignes réelles de l'image: L'image ne présente que ses ll_height dernière lignes.
Si le nombre de lignes lues est plus grand que le nombre de lignes réelles de l'image: Ca ne marche pas, on ne voit rien.
Le problème, c'est que techniquement, on ne devrait pas spécifier ll_height à la main, mais le déduire.
Donc:
ll_height = lstr_Info.bmiHeader.biheight
juste avant le 2ème GetDIBits.
Une petite ligne de code pour l'homme, un grand pas pour PB!
Merci à tous!
Hors ligne
Me suis un peu avancé...
La sauvegarde en fichier bmp à partir du blob fonctionne. Pas l'affichage dans le picture control.
Donc:
integer li_FileNum li_FileNum = FileOpen("D:\develop\photopass\test.bmp", StreamMode!, Write!, Shared!, Replace!) FileWriteEx(li_FileNum, lbl_final)
--> Ok
picframe.SetPicture(lbl_final)
--> Ko
Dernière modification par Shed (19-11-2012 14:53:11)
Hors ligne
Shed a écrit:
Code:
picframe.SetPicture(lbl_final)--> Ko
Marrant, "Chez moi ça marche ©" (en corrigeant le nombre de scanlines correct)
Par contre la sauvegarde -> bmp foire...
Hors ligne
Bon...
De base, l'appli se sert de avicap32.dll pour acquérir des images depuis une webcam. Des snapshots peuvent être pris et l'api stocke l'image prise dans le clipboard en utilisant nottement
FUNCTION Boolean SendMessage(Long hwnd, Long Msg, Long wParam, Long lParam) LIBRARY "user32.dll" ALIAS FOR "SendMessageA;ansi" CONSTANT Long WM_CAP_EDIT_COPY = 1054 lb_res = SendMessage(hHwnd, WM_CAP_EDIT_COPY, 0, 0)
L'image est bien dans le clipboard. Si je la copie colle dans une autre appli, ça fonctionne nickel.
Puis après, comme dit au dessus, je construis mon blob à partir de l'image dans le clipboard, puis je fais un .setpicture. Ca ne marche pas. Par contre, si je sauvegarde en .bmp, puis que je fais un .picturename = test.bmp, ça fonctionne bien, mais c'est moins propre car je dois passer par un fichier temporaire...
Mais mais mais... Si je n'utilise pas la première partie de l'appli pour mettre l'image dans le clipboard, mais que je fais simplement un ctrl+c d'une image bmp quelconque dans une autre appli, puis que j'essaie de faire un .setpicture, eh bien... ça fonctionne (mais bon... ça me sert à rien ça dans mon appli webcam :-))
je perds mes tartinnes...
Dernière modification par Shed (19-11-2012 16:41:27)
Hors ligne
Shed a écrit:
Mais mais mais... Si je n'utilise pas la première partie de l'appli pour mettre l'image dans le clipboard, mais que je fais simplement un ctrl+c d'une image bmp quelconque dans une autre appli, puis que j'essaie de faire un .setpicture, eh bien... ça fonctionne (mais bon... ça me sert à rien ça dans mon appli webcam :-))
je perds mes tartinnes...
Ce serait lié à la manière / l'application pour mettre les infos dans le presse-papiers ?
Pour info quand je teste ici et que ça fonctionne (XP et Win7), l'image est copiée depuis Chrome avec un clic-droit / "copier l'image", et le SetPicture fonctionne (beaucoup mieux avec le bon nombre de lignes demandées).
Avec l'espion de presse-papier (il y avait aussi clipbrd.exe, natif sous XP mais ça semble ne plus exister avec win7) ce sont les mêmes types de données depuis Ctrl-c et la webcam ?
Dernière modification par seki (19-11-2012 17:13:27)
Hors ligne
J'ai utilisé clipbrd.exe que j'ai rapatrié d'un XP vers un 7 (ça fonctionne). Rien de particulier à noter, les deux images sont bien présentes dans le clipboard, que ça soit par acquisition de la webcam ou par une autre appli.
Maintenant, il existe apparemment 7 formats de fichiers bmp différents (tous normalement reconnus par windows).
Peut-être que le .SetPicture ne fonctionne pas avec l'un (ou certains) d'entre eux, ce qui expliquerait pourquoi j'arrive à affichier des images venant d'autres appli et pas celles de la webcam.
La sauvegarde correcte du fichier en .bmp dans les deux cas s'expliquerait par le fait que je soupçonne FileWriteEx de faire du réencodage à l'écriture (J'ai copié un bmp avec une profondeur de couleur de 24, et après la sauvegarde, celle-ci est devenue 32...)... Ou alors FileWriteEx ne subit pas les mêmes limitations en bmp que .SetPicture.
Comme je disais, j'ai contourné le problème en sauvegardant d'abord le fichier en temp.bmp sur le disque, puis en faisant un .picturename="temp.bmp". Mais je trouve ça pas propre... Ca m'énerve
Hors ligne
Nouvelle info...
Si je mets un InkPicture.LoadPicture(lbl_blob) à la place d'un Picture.SetPicture(lbl_blob), eh beh vous savez quoi...
Hors ligne
Shed a écrit:
Maintenant, il existe apparemment 7 formats de fichiers bmp différents (tous normalement reconnus par windows).
Peut-être que le .SetPicture ne fonctionne pas avec l'un (ou certains) d'entre eux, ce qui expliquerait pourquoi j'arrive à affichier des images venant d'autres appli et pas celles de la webcam.
La sauvegarde correcte du fichier en .bmp dans les deux cas s'expliquerait par le fait que je soupçonne FileWriteEx de faire du réencodage à l'écriture (J'ai copié un bmp avec une profondeur de couleur de 24, et après la sauvegarde, celle-ci est devenue 32...)... Ou alors FileWriteEx ne subit pas les mêmes limitations en bmp que .SetPicture.
Le réencodage automagique du bitmap par FileWriteEx, comment dire... ça semble un peu fumeux comme théorie
En revanche, le presse-papier effectue bien des conversions en fonction de ce qui est dedans et ce qui est demandé par l'application : si un format n'existe pas mais qu'on peut l'obtenir depuis un autre, il y a conversion (source krosoft - paragraphe "Synthesized Clipboard Formats") C'est peut-être là qu'il il faut creuser.
Voici une fonction capable de faire un dump hexa d'un blob (version pb "vanilla", c'est super lent - ici j'utilise une fonction fastreplaceall() codée en C qui accélère le fonctionnement - il vaut mieux éviter de traiter trop de données, ne traite que des portions du blob final), et les dépendances de code qui vont avec si ça peut t'aider à examiner les données avant qu'elles n'aillent dans un fichier...
global type iif from function_object end type forward prototypes global function any iif (boolean ab_cond, any aa_true, any aa_false) end prototypes global function any iif (boolean ab_cond, any aa_true, any aa_false); // simulates the VB iif or C ternary operator if ab_cond then return aa_true else return aa_false end if end function
global type replaceall from function_object end type forward prototypes global function string replaceall (string as_source, string as_pattern, string as_replace) end prototypes global function string replaceall (string as_source, string as_pattern, string as_replace);//remplace toute les occurences de as_pattern de as_source par as_replace string ls_target long i, j ls_target="" i = 1 j = 1 do i = pos( as_source, as_pattern, j ) if i>0 then ls_target += mid( as_source, j, i - j ) ls_target += as_replace j = i + len( as_pattern ) else ls_target += mid( as_source, j ) end if loop while i>0 return ls_target end function
global type hexdump_blob from function_object end type forward prototypes global function string hexdump_blob (blob abl_data, boolean ab_fill_lastline) end prototypes global function string hexdump_blob (blob abl_data, boolean ab_fill_lastline);//hexify a blob content string ls_tohex = "0123456789ABCDEF" string ls_msg = "", ls_line, ls_binary long i, j, length byte b string ls_fill if isnull( abl_data ) then return "" if ab_fill_lastline then ls_fill = " __" else ls_fill = " " end if length = len( abl_data ) for i = 1 to length GetByte( abl_data, i, b ) ls_line += mid( ls_tohex, 1+ mod(int(b/16),16), 1) ls_line += mid( ls_tohex, 1+ mod(b,16), 1) ls_line += " " ls_binary += string( iif(b>31 and b<128,char(b)," ")) if mod(i,16) = 0 and i > 0 then ls_binary = replaceall( ls_binary, "~r", "·") //no cr/lf ls_binary = replaceall( ls_binary, "~n", "·") ls_binary = replaceall( ls_binary, "~t", "·") ls_msg += "[" + string( i - 16, "0000") + "] " + ls_line + "~t" + ls_binary + "~r~n" ls_line = "" ls_binary = "" end if next i -- // i - 1 due to the last loop in for ls_line += fill(ls_fill, 3 * ( 16 - mod(i, 16) ) ) ls_msg += "[" + string( i - mod(i,16), "0000") + "] " + ls_line + "~t" + ls_binary return ls_msg end function
Shed a écrit:
Comme je disais, j'ai contourné le problème en sauvegardant d'abord le fichier en temp.bmp sur le disque, puis en faisant un .picturename="temp.bmp". Mais je trouve ça pas propre... Ca m'énerve
Moi aussi ça m'intrigue. En temps masqué, je vais creuser un peu les différents formats bitmap.
Edit: ah ben je vois qu'entre temps tu as trouvé une solution propre. Je crois que je vais proposer ma fonction hexdump_blob() dans la section partage de code...
Dernière modification par seki (20-11-2012 10:02:14)
Hors ligne
Bon... on va mettre le topic comme résolu :-)
Une petite dernière question, je cherche à définir un event sur mon control inkedit, qui a lieu lorsque l'image contenue dans le control est rafraichie.
(Typiquement, comme c'est un flux vidéo, ça devrait avoir lieu à la fréquence du taux de raffraichissement de l'image, que je peux moi-même fixer. Je l'ai actuellement mis à 2 sec pour tester.)
Une idée? La liste est longue et pas bien documentée... Et apparemment, je peux aussi utiliser les events windows en utilisant les custom events.
Hors ligne
En plus des custom events, tu peux aussi créer ton propre event non mappé sur un pbm_xxx et l'appeler comme tu veux. L'ID d'event est optionnel.
Hors ligne
hummm... je ne vois pas très bien comment je vais définir le moment où l'event se déclenche si il n'est mappé sur rien du tout...
Hors ligne
Parce que je suis en train de jouer avec des HBITMAP et HICON avec Xlat, je viens de remarquer un (léger) problème dans le code qui a été posté :
- à un GetDC() doit correspondre un ReleaseDC()
- et à un CreateCompatibleDC() doit correspondre un DeleteDC()
Donc le code doit finir avec
ReleaseDC(lul_hwnd, lul_hdc) DeleteDC(lul_hdcMem)
Ça fonctionnait sans planter, mais ça doit occasionner des fuites de ressources gdi...
Hors ligne
Yep, ça a été corrigé chez moi y'a quelques jours :-)
Hors ligne