2009-06-15 15 views
12

Estoy tratando de encontrar una forma (algo) fácil de tomar una captura de pantalla en la ventana y guardar el HBITMAP resultante como JPEG. La parte difícil aquí es que, como el código está en C, no puedo usar GDI + y dado que el código es un módulo para un programa más grande, no puedo usar una lib externa (como libjpeg).¿Cómo puedo tomar una captura de pantalla y guardarla como JPEG en Windows?

Este código toma una captura de pantalla y devuelve un HBITMAP. Guardar ese mapa de bits en un archivo es fácil. el problema es que el mapa de bits es 2 o 3mb.

HDC hDCMem = CreateCompatibleDC(NULL); 
HBITMAP hBmp; 
RECT rect; 
HDC hDC; 
HGDIOBJ hOld;  

GetWindowRect(hWnd, & rect); 

hBmp = NULL; 

{ 
    hDC = GetDC(hWnd); 
    hBmp = CreateCompatibleBitmap(hDC, rect.right - rect.left, rect.bottom - rect.top); 
    ReleaseDC(hWnd, hDC); 
} 

hOld = SelectObject(hDCMem, hBmp); 
SendMessage(hWnd, WM_PRINT, (WPARAM) hDCMem, PRF_CHILDREN | PRF_CLIENT | PRF_ERASEBKGND | PRF_NONCLIENT | PRF_OWNED); 

SelectObject(hDCMem, hOld); 
DeleteObject(hDCMem); 

return hBmp; 

¿Alguna idea de cómo hacer esto? gracias tanto, cualquier ayuda se agradece

EDIT: Desde que fuimos en la dirección de GDI + I pensé que había puesto el código IV C++ que puede tomar la captura de pantalla y convertirlo en un archivo JPEG utilizando GDI +. Si alguien sabe cómo lograr esto usando el FLAT GDI + agradecería la ayuda. Código:

#include <windows.h> 
#include <stdio.h> 
#include <gdiplus.h> 

using namespace Gdiplus; 


int GetEncoderClsid(WCHAR *format, CLSID *pClsid) 
{ 
    unsigned int num = 0, size = 0; 
    GetImageEncodersSize(&num, &size); 
    if(size == 0) return -1; 
    ImageCodecInfo *pImageCodecInfo = (ImageCodecInfo *)(malloc(size)); 
    if(pImageCodecInfo == NULL) return -1; 
    GetImageEncoders(num, size, pImageCodecInfo); 
    for(unsigned int j = 0; j < num; ++j) 
    { 
     if(wcscmp(pImageCodecInfo[j].MimeType, format) == 0){ 
      *pClsid = pImageCodecInfo[j].Clsid; 
      free(pImageCodecInfo); 
      return j; 
     }  
    } 
    free(pImageCodecInfo); 
    return -1; 
} 

int GetScreeny(LPWSTR lpszFilename, ULONG uQuality) // by Napalm 
{ 
    ULONG_PTR gdiplusToken; 
    GdiplusStartupInput gdiplusStartupInput; 
    GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL); 
    HWND hMyWnd = GetForegroundWindow(); // get my own window 
    RECT r;        // the area we are going to capture 
    int w, h;       // the width and height of the area 
    HDC dc;        // the container for the area 
    int nBPP; 
    HDC hdcCapture; 
    LPBYTE lpCapture; 
    int nCapture; 
    int iRes; 
    CLSID imageCLSID; 
    Bitmap *pScreenShot; 
    HGLOBAL hMem; 
    int result; 

    // get the area of my application's window 
    //GetClientRect(hMyWnd, &r); 
    GetWindowRect(hMyWnd, &r); 
    dc = GetWindowDC(hMyWnd);// GetDC(hMyWnd) ; 
    w = r.right - r.left; 
    h = r.bottom - r.top; 
    nBPP = GetDeviceCaps(dc, BITSPIXEL); 
    hdcCapture = CreateCompatibleDC(dc); 


    // create the buffer for the screenshot 
    BITMAPINFO bmiCapture = { 
      sizeof(BITMAPINFOHEADER), w, -h, 1, nBPP, BI_RGB, 0, 0, 0, 0, 0, 
    }; 

    // create a container and take the screenshot 
    HBITMAP hbmCapture = CreateDIBSection(dc, &bmiCapture, 
     DIB_PAL_COLORS, (LPVOID *)&lpCapture, NULL, 0); 

    // failed to take it 
    if(!hbmCapture) 
    { 
     DeleteDC(hdcCapture); 
     DeleteDC(dc); 
     GdiplusShutdown(gdiplusToken); 
     printf("failed to take the screenshot. err: %d\n", GetLastError()); 
     return 0; 
    } 

    // copy the screenshot buffer 
    nCapture = SaveDC(hdcCapture); 
    SelectObject(hdcCapture, hbmCapture); 
    BitBlt(hdcCapture, 0, 0, w, h, dc, 0, 0, SRCCOPY); 
    RestoreDC(hdcCapture, nCapture); 
    DeleteDC(hdcCapture); 
    DeleteDC(dc); 

    // save the buffer to a file  
    pScreenShot = new Bitmap(hbmCapture, (HPALETTE)NULL); 
    EncoderParameters encoderParams; 
    encoderParams.Count = 1; 
    encoderParams.Parameter[0].NumberOfValues = 1; 
    encoderParams.Parameter[0].Guid = EncoderQuality; 
    encoderParams.Parameter[0].Type = EncoderParameterValueTypeLong; 
    encoderParams.Parameter[0].Value = &uQuality; 
    GetEncoderClsid(L"image/jpeg", &imageCLSID); 
    iRes = (pScreenShot->Save(lpszFilename, &imageCLSID, &encoderParams) == Ok); 
    delete pScreenShot; 
    DeleteObject(hbmCapture); 
    GdiplusShutdown(gdiplusToken); 
    return iRes; 

} 
+0

¿realmente tiene que ser un JPG? si un PNG está bien, puede usar libpng .. –

+0

Sí, tiene que ser JPG. PNG no es lo suficientemente pequeño para lo que necesito. Además, no puedo usar libs externos o de terceros. libpng es grande y agrega mucho código adicional que no necesito (ya lo intenté) Gracias por la respuesta. – wonderer

+0

JPG no es una buena opción para capturas de pantalla. Es mucho mejor para fotos y similares, pero para capturas de pantalla, PNG ofrece resultados mucho mejores. Puede reducirlo reduciendo o reduciendo la profundidad de bits de color. –

Respuesta

15

bien, después de mucho esfuerzo aquí está la respuesta:

int SaveJpeg(HBITMAP hBmp, LPCWSTR lpszFilename, ULONG uQuality) 
{ 
    ULONG *pBitmap = NULL; 
    CLSID imageCLSID; 
    EncoderParameters encoderParams; 
    int iRes = 0; 

    typedef Status (WINAPI *pGdipCreateBitmapFromHBITMAP)(HBITMAP, HPALETTE, ULONG**); 
    pGdipCreateBitmapFromHBITMAP lGdipCreateBitmapFromHBITMAP; 

    typedef Status (WINAPI *pGdipSaveImageToFile)(ULONG *, const WCHAR*, const CLSID*, const EncoderParameters*); 
    pGdipSaveImageToFile lGdipSaveImageToFile; 

    // load GdipCreateBitmapFromHBITMAP 
    lGdipCreateBitmapFromHBITMAP = (pGdipCreateBitmapFromHBITMAP)GetProcAddress(hModuleThread, "GdipCreateBitmapFromHBITMAP"); 
    if(lGdipCreateBitmapFromHBITMAP == NULL) 
    { 
     // error 
     return 0; 
    } 

    // load GdipSaveImageToFile 
    lGdipSaveImageToFile = (pGdipSaveImageToFile)GetProcAddress(hModuleThread, "GdipSaveImageToFile"); 
    if(lGdipSaveImageToFile == NULL) 
    { 
     // error 
     return 0; 
    } 

     lGdipCreateBitmapFromHBITMAP(hBmp, NULL, &pBitmap); 

     iRes = GetEncoderClsid(L"image/jpeg", &imageCLSID); 
     if(iRes == -1) 
    { 
     // error 
     return 0; 
    } 
    encoderParams.Count = 1; 
    encoderParams.Parameter[0].NumberOfValues = 1; 
    encoderParams.Parameter[0].Guid = EncoderQuality; 
    encoderParams.Parameter[0].Type = EncoderParameterValueTypeLong; 
    encoderParams.Parameter[0].Value = &uQuality; 

    lGdipSaveImageToFile(pBitmap, lpszFilename, &imageCLSID, &encoderParams); 


    return 1; 
} 
  • lo que es hModuleThread? Mire en here. Puede reemplazar con GetModuleHandle()

  • ¿Qué es GetEncoderClsid? Mire here.

Ahora la pregunta es, ¿cómo puedo guardar el pBitmap codificado (como un jpeg) en un búfer BYTE?

4

Una posibilidad: Si no puede modificar este programa para guardar como JPEG, escribir un segundo programa, usando C#/+/otras tecnologías de fantasía GDI para supervisar el directorio de almacenamiento y proceso de guardado BMP en archivos JPEG .

Si no puede hacer eso, el grupo Independent JPEG ha hecho puro código C-JPEG disponible desde finales del siglo 20: A very minimal web page is available here.

+0

Gracias. El problema con esa lib es que es ENORME. En cualquier caso, no puedo usar una lib externa. – wonderer

0

Usted probablemente tendrá que utilizar una biblioteca externa para crear un archivo JPEG en C código - no habrá una función de biblioteca estándar para hacerlo. Por supuesto, también puede estudiar el formato de archivo y escribir una función para generar la suya, según los criterios. Puede comenzar at wikipedia. Si realmente no puede usar una biblioteca externa, puede leer las funciones relevantes de la biblioteca para aprender cómo generar sus propios archivos JPEG. Pero eso parece mucho más trabajo que solo usar la biblioteca.

Además, no creo que el tamaño de una biblioteca realmente entre en juego con C: creo que solo obtiene el tamaño de las funciones que llama desde esa biblioteca (creo, de todos modos). Por supuesto, si todas las funciones están estrechamente conectadas de lo que sería enorme de todos modos.

+0

Lo primero que probé fue el uso de libjpg y otras librerías de terceros. No importa lo que use de esas libs. Importa todo. Hace que mi código sea ENORME. – wonderer

+0

Estaba leyendo acerca de una versión C de GDI + (llamada plana GDI +), ¿alguien tiene experiencia con eso? Si es un sí, tengo todo el código para tomar la captura de pantalla, codificarla como JPEG y guardarla en un archivo o en un buffer – wonderer

-1

No reinventar la rueda.

Si lo que necesita es un programa que toma una captura de pantalla y la guarda como un jpeg, puede simplemente usar ImageMagick 's import.

El comando shell sería simplemente:

import screen.jpg 

Por supuesto, usted podría ahorrar lo anterior como takeScreenshot.bat y que tendríamos un programa que se adapte a sus necesidades.

+1

Gracias. No estoy tratando de reinventar la rueda. Este es un módulo que forma parte de un programa que necesita tomar una captura de pantalla cada vez que finaliza una transacción. Esa captura de pantalla luego se guarda en una base de datos de tipo. Es por eso que debe ser pequeño, un jpg y no puedo usar libs de terceros porque necesito mantener mi código tan pequeño como sea posible – wonderer

+0

La importación de ImageMagick es lenta. –

0

La buena compresión de la imagen es difícil. Existe una razón para que exista código de biblioteca para los formatos comunes. Requiere mucho código para hacer. Sus limitaciones sobre el problema no hacen que parezca que hay una solución práctica.

Hay un par de cosas que probar.

  1. Utilice un BMP de 8 bits por píxel en lugar de un BMP de color verdadero. Esto supone que la pérdida de información en el mapeo de colores es aceptable, por supuesto. Le comprará un factor de tres en tamaño de archivo sobre un BMP de 24 bits.

  2. Intenta usar la codificación de longitud de ejecución en el BMP que escribes. RLE es un formato documentado para BMP, y debería haber suficiente código de muestra para hacerlo. Funciona mejor en imágenes que tienen muchas tiradas largas de color idéntico. Una pantalla típica sin demasiados degradados y rellenos se ajusta a esa descripción.

  3. Si no son suficientes, puede que necesite recurrir a una compresión más fuerte. El código fuente de libjpeg está disponible, y es posible vincularlo estáticamente en lugar de usar el DLL. Se requerirá un poco de esfuerzo para configurar solo los bits que necesita, pero el código de compresión y descompresión es casi completamente independiente el uno del otro y eso divide la biblioteca casi a la mitad.

+0

gracias. Voy a probar el RLE. Estaba leyendo acerca de una versión C de GDI + (llamada plana GDI +), ¿alguien tiene experiencia con eso? Si es un sí, tengo todo el código para tomar la captura de pantalla, codificarla como JPEG y guardarla en un archivo o en un búfer. – wonderer

+0

El sitio gdi + plano es http://msdn.microsoft.com/en-us/library/ms533969(VS.85).aspx – wonderer

3

Traduciendo a la GDI plana + API es bastante sencillo:

void SaveJpeg(HBITMAP hBmp, LPCWSTR lpszFilename, ULONG uQuality) 
{ 
    GpBitmap* pBitmap; 
    GdipCreateBitmapFromHBITMAP(hBmp, NULL, &pBitmap); 

    CLSID imageCLSID; 
    GetEncoderClsid(L"image/jpeg", &imageCLSID); 

    EncoderParameters encoderParams; 
    encoderParams.Count = 1; 
    encoderParams.Parameter[0].NumberOfValues = 1; 
    encoderParams.Parameter[0].Guid = EncoderQuality; 
    encoderParams.Parameter[0].Type = EncoderParameterValueTypeLong; 
    encoderParams.Parameter[0].Value = &uQuality; 

    GdipSaveImageToFile(pBitmap, lpszFilename, &imageCLSID, &encoderParams); 
} 

La única cosa que no era evidente era la limpieza de la GpBitmap creado por GdipCreateBitmapFromHBITMAP(). La clase Gdiplus :: Bitmap no parece tener un destructor, por lo que no hace nada con su GpBitmap interno. Además, no hay GdipDeleteBitmap(), como hay para otros objetos GDI +. Por lo tanto, no está claro qué se debe hacer para evitar fugas.

Editar: Este código no se refiere al hecho de que el Microsoft suministra archivos de GDI + de cabecera declaran todas las funciones necesarias en los espacios de nombres de C++. Una solución es copiar las declaraciones necesarias (y convertir según sea necesario el código C) a su propio encabezado. Otra posibilidad es usar los encabezados suministrados por los proyectos Wine o Mono. Ambos parecen comportarse mucho mejor para la compilación como código C.

+0

Gracias, déjame intentarlo. ¿Qué encabezados incluye para poder compilar esto? – wonderer

+0

Sí, no puedo compilar esto ya que no se puede encontrar toda la función basada en GDI. por ejemplo: GpBitmap. Agregar #include empeorará las cosas. ¿Qué me estoy perdiendo? – wonderer

+0

La compilación es bastante extraña. Tuve que #include , luego #include . Para colmo, los tipos y funciones de Gdiplus están espaciados, por lo que necesitaba agregar instrucciones de uso para los espacios de nombres Gdiplus y Gdiplus :: DllExports. Obviamente, esto te obligará a compilar el código como C++, a pesar de que en realidad es solo código C directo. Si esto es un problema, probablemente puedas simplemente sacar las declaraciones necesarias a tu propio encabezado. –

0

¿Has echado un vistazo al FreeImage Project? Sí, es una biblioteca externa, pero es bastante pequeña (~ 1Mb). Hace casi cualquier cosa que desee, con imágenes, también. Definitivamente vale la pena saberlo, incluso si no fuera por este proyecto.

+0

Disculpe, como dije, no puedo usar libs externas. Mi módulo es 200k, agregando que lib será enorme. gracias – wonderer

+0

Entendido. Vi ese comentario, pero pensé que también vi un poco de apertura más tarde. Ah, bueno, tal vez otro proyecto ... –

0

libjpeg es gratuito, de código abierto, codificado en C. Puede copiar su código en el suyo.

http://sourceforge.net/project/showfiles.php?group_id=159521

+0

Gracias. desafortunadamente no puedo usar el código GPL en el mío porque el mío no es GPL. Dicho eso, eché un vistazo a su código. Sería virtualmente imposible no incluir toda la lib ya que es tan complicado que no se puede obtener una función sin tener que traer un montón de código. gracias por el comentario, realmente lo aprecio. – wonderer

0

Si realmente le preocupa el espacio, probablemente también pueda echar por la borda las bibliotecas de tiempo de ejecución de C. Si solo está utilizando algunas funciones, solo escriba su propia versión (por ejemplo, strcpy). Es posible escribir aplicaciones de Windows que son solo unos pocos K bytes. Puedo enviarte una pequeña aplicación de muestra que hace esto.

Si tomo mi código de imagen C y lo elimino solo para el codificador JPEG, probablemente produzca unos 20K de código (menos si no necesita admitir imágenes en escala de grises).

+0

Gracias, pero como pueden ver, yo ya contesté la pregunta yo mismo. – wonderer

+0

No veo cómo respondió su pregunta usted mismo. tienes una solución? ¿Tienes un presupuesto? La respuesta a esas 2 preguntas determina si obtiene una solución óptima. – BitBank

+0

Quizás entendiste mal y pensaste que me refería al código GPL. – BitBank

0

intenta esto en el comienzo de su código

#include "windows.h" 
#include "gdiplus.h" 
using namespace Gdiplus; 
using namespace Gdiplus::DllExports; 

Y GdipSaveImageToFile() puede compilar en C++ creo.

En C puro, probablemente lo mejor es tratar de hacer inclusiones individuales. Busque las declaraciones de funciones en "gdiplus.h" y agregue mínimos incluye para cada una de las funciones que no incluyen espacios de nombres, como #include "Gdiplusflat.h" para GdipSaveImageToFile()

0

Tuve el mismo problema y lo resolví con este código. También necesita incluir gdiplus.lib en las Entradas para el Enlazador.

struct GdiplusStartupInput 
{ 
    UINT32 GdiplusVersion;    // Must be 1 (or 2 for the Ex version) 
    UINT_PTR DebugEventCallback;   // Ignored on free builds 
    BOOL SuppressBackgroundThread;  // FALSE unless you're prepared to call the hook/unhook functions properly 
    BOOL SuppressExternalCodecs;  // FALSE unless you want GDI+ only to use its internal image codecs. 
}; 
int __stdcall GdiplusStartup(ULONG_PTR *token, struct GdiplusStartupInput *gstart, struct GdiplusStartupInput *gstartout); 
int __stdcall GdiplusShutdown(ULONG_PTR token); 
int __stdcall GdipCreateBitmapFromFile(WCHAR *filename, void **GpBitmap); 
int __stdcall GdipSaveImageToFile(void *GpBitmap, WCHAR *filename, CLSID *encoder, void *params); 
int __stdcall GdipCreateBitmapFromStream(IStream *pstm, void **GpBitmap); 
int __stdcall GdipCreateHBITMAPFromBitmap(void *GpBitmap, HBITMAP *bm, COLORREF col); 
int __stdcall GdipCreateBitmapFromHBITMAP(HBITMAP bm, HPALETTE hpal, void *GpBitmap); 
int __stdcall GdipDisposeImage(void *GpBitmap); 
int __stdcall GdipImageGetFrameDimensionsCount(void *GpBitmap, int *count); 
int __stdcall GdipImageGetFrameDimensionsList(void *GpBitmap, GUID *guid, int count); 
int __stdcall GdipImageGetFrameCount(void *GpBitmap, GUID *guid, int *count); 
int __stdcall GdipImageSelectActiveFrame(void *GpBitmap, GUID *guid, int num); 
int __stdcall GdipImageRotateFlip(void *GpBitmap, int num); 

/* 
    Save the image memory bitmap to a file (.bmp, .jpg, .png or .tif) 
*/ 
int save_bitmap_to_file(char *filename, HBITMAP hbitmap) 
{ 
    wchar_t wstr[MAX_FILENAME]; 
    int  sts; 
    size_t len; 
    void *imageptr;   /* actually an 'image' object pointer */ 
    CLSID encoder; 

    sts = FAILURE; 
    _strlwr(filename); 
    if(strstr(filename, ".png")) 
     sts = CLSIDFromString(L"{557cf406-1a04-11d3-9a73-0000f81ef32e}", &encoder);  /* png encoder classid */ 
    else if(strstr(filename, ".jpg")) 
     sts = CLSIDFromString(L"{557cf401-1a04-11d3-9a73-0000f81ef32e}", &encoder);  /* jpg encoder classid */ 
    else if(strstr(filename, ".jpeg")) 
     sts = CLSIDFromString(L"{557cf401-1a04-11d3-9a73-0000f81ef32e}", &encoder);  /* jpg encoder classid */ 
    else if(strstr(filename, ".tif")) 
     sts = CLSIDFromString(L"{557cf405-1a04-11d3-9a73-0000f81ef32e}", &encoder);  /* tif encoder classid */ 
    else if(strstr(filename, ".bmp")) 
     sts = CLSIDFromString(L"{557cf400-1a04-11d3-9a73-0000f81ef32e}", &encoder);  /* bmp encoder classid */ 
    else if(strstr(filename, ".gif")) 
     sts = CLSIDFromString(L"{557cf402-1a04-11d3-9a73-0000f81ef32e}", &encoder);  /* gif encoder classid */ 
    if(sts != 0) return(FAILURE); 

    mbstowcs_s(&len, wstr, MAX_FILENAME, filename, MAX_FILENAME-1);  /* get filename in multi-byte format */ 
    sts = GdipCreateBitmapFromHBITMAP(hbitmap, NULL, &imageptr); 
    if(sts != 0) return(FAILURE); 

    sts = GdipSaveImageToFile(imageptr, wstr, &encoder, NULL); 

    GdipDisposeImage(imageptr);     /* destroy the 'Image' object */ 
    return(sts); 
} 

Este código se podría mejorar mediante la adición de un valor de calidad para la salida de archivos jpg, el código para hacer que sea más alta en este hilo.

Cuestiones relacionadas