Ir al contenido



Foto

[MULTILENGUAJE] Enviar la imagen de pantalla por un socket usando GDI+ flat API


  • Por favor identifícate para responder
3 respuestas en este tema

#1 escafandra

escafandra

    Advanced Member

  • Moderadores
  • PipPipPip
  • 3.840 mensajes
  • LocationMadrid - España

Escrito 07 febrero 2012 - 05:46

Con motivo de una sugerencia que hice sobre una duda en este tema, expongo un ejemplo de enviar el contenido de la pantalla por un socket.

Se trata de enviar un bloque de memoria que contiene la imagen de la pantalla comprimida en png usando GDI plus flat API con lo que va a funcionar en cualquier versión de delphi.


delphi
  1. uses
  2.   Windows, ActiveX, WinSock, Forms, Classes, Controls, StdCtrls;
  3.  
  4. type
  5. TCLSID = TGUID;
  6. PCLSID = ^TCLSID;
  7.  
  8. TImageCodecInfo = packed record
  9.   Clsid:            TCLSID;
  10.   FormatID:          TGUID;
  11.   CodecName:        PWCHAR;
  12.   DllName:          PWCHAR;
  13.   FormatDescription: PWCHAR;
  14.   FilenameExtension: PWCHAR;
  15.   MimeType:          PWCHAR;
  16.   Flags:            DWORD;
  17.   Version:          DWORD;
  18.   SigCount:          DWORD;
  19.   SigSize:          DWORD;
  20.   SigPattern:        PBYTE;
  21.   SigMask:          PBYTE;
  22. end;
  23. PImageCodecInfo = ^TImageCodecInfo;
  24.  
  25.  
  26. function  wcscmp(wstr1, wstr2: PWCHAR): Integer; cdecl external 'crtdll';
  27.  
  28. // GDI+ Flat API...
  29. function  GdiplusStartup(var GdiToken: DWORD; Startup, Output: PBYTE): Cardinal; stdcall external 'gdiplus';
  30. procedure GdiplusShutdown(GdiToken: DWORD); stdcall external 'gdiplus';
  31. function  GdipCreateBitmapFromHBITMAP(hbm: HBITMAP; hpal: HPALETTE; var GBitmap: THANDLE): Cardinal; stdcall external 'gdiplus';
  32. function  GdipGetImageEncodersSize(var numEncoders: DWORD; var size: DWORD): Cardinal; stdcall external 'gdiplus';
  33. function  GdipGetImageEncoders(numEncoders, size: DWORD; encoders: PImageCodecInfo): Cardinal; stdcall external 'gdiplus';
  34. function  GdipDisposeImage(image: THANDLE): Cardinal; stdcall external 'gdiplus';
  35. function  GdipSaveImageToStream(image: THANDLE; stream: ISTREAM; var clsidEncoder: TCLSID; encoderParams: Pointer): Cardinal; stdcall external 'gdiplus';
  36.  
  37.  
  38. implementation
  39.  
  40. // Obtener el CLSID para la codificación de un formato gráfico
  41. function GetEncoderClsid(Format: PWCHAR; var Clsid: TCLSID): boolean;
  42. var
  43.   i, N, Size: Cardinal;
  44.   ICInfo: array of TImageCodecInfo;
  45. begin
  46.   Result:= false;
  47.   i:= 0; N:= 0; Size:= 0;
  48.   GdipGetImageEncodersSize(N, Size);
  49.   if Size > 0 then
  50.   begin
  51.     SetLength(ICInfo, Size);
  52.     GdipGetImageEncoders(N, Size, @ICInfo[0]);
  53.     while (i< N) and (wcscmp(ICInfo[i].MimeType, Format)<>0) do inc(i);
  54.     if i<N then Clsid:= ICInfo[i].Clsid;
  55.   end;
  56.   Result:= boolean(i<N);
  57. end;
  58.  
  59. procedure SendScreen(hSocket: TSOCKET);
  60. var
  61.   gdiplusToken: DWORD;
  62.   GdiPlusStartupInput: array[0..2] of int64;
  63.   CursorInf: TCURSORINFO;
  64.   IconInf: ICONINFO;
  65.   hScreen, hCanvas: HDC;
  66.   Bitmap: HBITMAP;
  67.   GBitmap: THANDLE;
  68.   Stream: IStream;
  69.   stat: STATSTG;
  70.   Clsid: TCLSID;
  71.   hMem: HGLOBAL;
  72.   Memory: Pointer;
  73.  
  74. begin
  75.   // Inicializamos GDI+.
  76.   GdiPlusStartupInput[0]:= 1; GdiPlusStartupInput[1]:= 0;
  77.   if GdiplusStartup(gdiplusToken, @GdiPlusStartupInput, nil) <> 0 then exit;
  78.  
  79.   // Capturo la pantalla
  80.   hScreen:= GetDC(0);
  81.   hCanvas:= CreateCompatibleDC(0);
  82.   Bitmap:= CreateCompatibleBitmap(hScreen,GetDeviceCaps(hScreen, HORZRES), GetDeviceCaps(hScreen, VERTRES));
  83.   SelectObject(hCanvas, Bitmap);
  84.   BitBlt(hCanvas, 0, 0, GetDeviceCaps(hScreen, HORZRES), GetDeviceCaps(hScreen, VERTRES), hScreen, 0, 0, SRCCOPY);
  85.  
  86.   // Capturo el cursor
  87.   ZeroMemory(@CursorInf, sizeof(TCURSORINFO));
  88.   CursorInf.cbSize:= sizeof(TCURSORINFO);
  89.   if GetCursorInfo(CursorInf) and (CursorInf.flags = CURSOR_SHOWING) then
  90.   begin
  91.     GetIconInfo(CursorInf.hCursor, IconInf);
  92.     DrawIcon(hCanvas, CursorInf.ptScreenPos.x - IconInf.xHotspot, CursorInf.ptScreenPos.y - IconInf.yHotspot, CursorInf.hCursor);
  93.     DeleteObject(IconInf.hbmColor);
  94.     DeleteObject(IconInf.hbmMask);
  95.   end;
  96.  
  97.   // Procedo a enviar un bloque de memoria con la imagen comprimida en png
  98.   CreateStreamOnHGlobal(0, true, stream);
  99.   GdipCreateBitmapFromHBITMAP(Bitmap, 0, GBitmap);
  100.   GetEncoderCLSID('image/png', Clsid);
  101.   GdipSaveImageToStream(GBitmap, stream, Clsid, nil);
  102.   // Obtengo el tamaño del bloque de memoria
  103.   stream.Stat(stat, STATFLAG_NONAME);
  104.   // Obtengo el puntero del bloque de memoria (stat.cbSize)
  105.   GetHGlobalFromStream(stream, hMem);
  106.   Memory:= GlobalLock(hMem);
  107.  
  108.   // Envío el bloque de memoria
  109.   send(hSocket, Memory^, stat.cbSize, 0);
  110.  
  111.   // libero los Objetos usados del GDI+ bloques y Handles
  112.   GdipDisposeImage(GBitmap);
  113.   GlobalUnlock(hMem);
  114.   DeleteObject(Bitmap);
  115.   DeleteDC(hCanvas);
  116.   ReleaseDC(0, hScreen);
  117.  
  118.   // Shutdown GDI+
  119.   GdiplusShutdown(gdiplusToken);
  120. end;


Espero que el ejemplo sea de utilidad para aquellos que se interesen por la API de GDI+  y a algunos otros curiosos  *-).


Saludos.
  • 0

#2 felipe

felipe

    Advanced Member

  • Administrador
  • 3.283 mensajes
  • LocationColombia

Escrito 07 febrero 2012 - 09:57

Interesante... falta es el how to use :)


Saludos!
  • 0

#3 escafandra

escafandra

    Advanced Member

  • Moderadores
  • PipPipPip
  • 3.840 mensajes
  • LocationMadrid - España

Escrito 07 febrero 2012 - 11:40

Interesante... falta es el how to use :)


El "how to use" es sencillo y complejo, según....

Debe realizarse una aplicación clliente-servidor. El código expuesto envía el contenido de la pantalla básicamente en la línea


delphi
  1. send(hSocket, Memory^, stat.cbSize, 0);

Esa línea es una simplificación del envío, pues en realidad debería realizarse un sistema un poco mas complejo de envío del bloque de memoria en fragmentos y comprobar que se reciben del otro lado. También dependerá del protocolo que se elija. Tal como está puede funcionar pero la otra parte lo recibirá fragmentado. En definitiva se debe establecer en esa línea la función concreta que enviará el bloque de memoria.

En el PC remoto, una vez recibido el bloque, lo cargaremos en otro istream para reconstruir un GpBitmap con la API GdipCreateBitmapFromStream. De éste obtendremos un HBITMAP con GdipCreateHBITMAPFromBitmap. Ese HBITMAP puede ser asignado como Handle de un TBitmap de un TImage, por ejemplo, donde visualizaremos la imagen en el PC remoto. También se podría usar un TStream puesto que lo que estamos enviando es un bloque de memoria con la imagen de un fichero png. Nada nos impide elegir un formato de imagen distinto como jpg...

El ejemplo trata de mostrar una posible solución, a bajo nivel, del envío de imágenes a través de la red, aunque el código es funcional, debe completarse el código para un PC remoto y dar mas robustez al envío de grandes bloques de memoria.

¿Y porqué no complicar un poco el asunto y enviar, no imágenes completas de la pantalla, sino únicamente los cambios acaecidos  entre cada envío?. En este tema, resta de imágenes, mostré una forma para encontrar las diferencias entre dos imágenes. Enviar la diferencia ahorra tráfico de red, en ese caso enviaríamos una máscara y la diferencia entre imágenes, complicando un poco el sistema pero ganando en velocidad y eficacia.

En fin son unas cuantas ideas. Como dije al principio el "how to use" es sencillo y complejo, según lo queramos ver. Es por ese motivo que me lo reservé, para dejar libre la imaginación.


Saludos.


  • 0

#4 escafandra

escafandra

    Advanced Member

  • Moderadores
  • PipPipPip
  • 3.840 mensajes
  • LocationMadrid - España

Escrito 16 febrero 2012 - 04:57

Como sabéis, suelo publicar el código en C/C++ y delphi cuando se trata de trucos, así que aprovecho un rato libre para colocar el código equivalente del truco en C/C++ con GDI plus flat API


cpp
  1. //---------------------------------------------------------------------------
  2. #define STRICT
  3. #include <windows.h>
  4. #include <algorithm>
  5. using std::min;
  6. using std::max;
  7. #include "Gdiplus.h"
  8. #include "WinSock.h"
  9.  
  10. #pragma comment(lib, "Gdiplus.lib") // poner la ruta dentro de las libreriras
  11.  
  12. //---------------------------------------------------------------------------
  13. Gdiplus::GdiplusStartupInput gdiplusStartupInput;
  14. ULONG_PTR  gdiplusToken;
  15.  
  16. bool GetEncoderCLSID(const WCHAR* Format, CLSID* Clsid)
  17. {
  18.   UINT i=0, N=0, Size=0;
  19.   Gdiplus::GetImageEncodersSize(&N, &Size);
  20.   if(!Size) return false;
  21.   Gdiplus::ImageCodecInfo* ICInfo = new Gdiplus::ImageCodecInfo[Size];
  22.   Gdiplus::GetImageEncoders(N, Size, ICInfo);
  23.   while (i<N && wcscmp(ICInfo[i].MimeType, Format)) i++;
  24.   if(i<N) *Clsid = ICInfo[i].Clsid;
  25.   delete [] ICInfo;
  26.   return (i<N);
  27. }
  28.  
  29. //---------------------------------------------------------------------------
  30. void ScreenDownload(SOCKET hSocket)
  31. {
  32.   DWORD gdiplusToken;
  33.   Gdiplus::GdiplusStartupInput ddiPlusStartupInput;
  34.   BITMAPINFO BitmapInf;
  35.   CURSORINFO CursorInf = {sizeof(CURSORINFO)};
  36.   ICONINFO IconInf;
  37.   HDC hScreen, hCanvas;
  38.   HBITMAP Bitmap;
  39.   Gdiplus::GpBitmap* GBitmap;
  40.   IStream* Stream;
  41.   STATSTG stat;
  42.   TCLSID Clsid;
  43.   HGLOBAL hMem;
  44.   void* Memory;
  45.  
  46.   // Inicializamos GDI+.
  47.   if(GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL)) return;
  48.  
  49.   // Capturo la pantalla
  50.   hScreen = GetDC(0);
  51.   hCanvas = CreateCompatibleDC(0);
  52.   Bitmap = CreateCompatibleBitmap(hScreen, GetDeviceCaps(hScreen, HORZRES), GetDeviceCaps(hScreen, VERTRES));
  53.   SelectObject(hCanvas, Bitmap);
  54.   BitBlt(hCanvas, 0, 0, GetDeviceCaps(hScreen, HORZRES), GetDeviceCaps(hScreen, VERTRES), hScreen, 0, 0, SRCCOPY);
  55.  
  56.   // Capturo el cursor
  57.   if(GetCursorInfo(&CursorInf) && CursorInf.flags == CURSOR_SHOWING){
  58.     GetIconInfo(CursorInf.hCursor, &IconInf);
  59.     DrawIcon(hCanvas, CursorInf.ptScreenPos.x - IconInf.xHotspot, CursorInf.ptScreenPos.y - IconInf.yHotspot, CursorInf.hCursor);
  60.     DeleteObject(IconInf.hbmColor);
  61.     DeleteObject(IconInf.hbmMask);
  62.   }
  63.  
  64.   // Procedo a enviar un bloque de memoria con la imagen comprimida en png
  65.   CreateStreamOnHGlobal(0, true, &Stream);
  66.   Gdiplus::DllExports::GdipCreateBitmapFromHBITMAP(Bitmap, 0, &GBitmap);
  67.   GetEncoderCLSID(L"image/png", &Clsid);
  68.   Gdiplus::DllExports::GdipSaveImageToStream(GBitmap, Stream, &Clsid, 0);
  69.   // Obtengo el tamaño del bloque de memoria
  70.   Stream->Stat(&stat, STATFLAG_NONAME);
  71.   // Obtengo el puntero del bloque de memoria (stat.cbSize)
  72.   GetHGlobalFromStream(Stream, &hMem);
  73.   Memory = GlobalLock(hMem);
  74.  
  75.   // Envío el bloque de memoria
  76.   send(hSocket, (BYTE*)Memory, stat.cbSize.LowPart, 0);
  77.  
  78.   // libero los Objetos usados del GDI+
  79.   Gdiplus::DllExports::GdipDisposeImage(GBitmap);
  80.   GlobalUnlock(hMem);
  81.   DeleteObject(Bitmap);
  82.   DeleteDC(hCanvas);
  83.   ReleaseDC(0, hScreen);
  84.   Stream->Release();
  85.  
  86.   // Shutdown GDI+
  87.   Gdiplus::GdiplusShutdown(gdiplusToken);
  88. }


El código es una traducción del escrito en delphi por lo que no requiere mas comentarios que los expuestos mas arriba.
Espero que sea de utilidad.


Saludos.
  • 0