16

ウィンドウでスクリーンショットを撮り、結果のHBITMAPをJPEGとして保存する(やや)簡単な方法を見つけようとしています。ここで注意が必要なのは、コードが CI にあるため GDI+ を使用できず、コードがより大きなプログラムのモジュールであるため、外部ライブラリ (libjpeg など) も使用できないことです。

このコードはスクリーンショットを取得し、HBITMAP を返します。そのビットマップをファイルに保存するのは簡単です。問題は、ビットマップが 2 または 3 MB であることです。

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;

これを行う方法についてのアイデアはありますか?どうもありがとう、どんな助けでも大歓迎です

EDIT:GDI +の方向に進んだので、スクリーンショットを撮り、GDI +を使用してJPEGに変換できるコードiv C ++を投稿すると思いました。FLAT GDI+ を使用してこれを達成する方法を誰かが知っている場合は、助けていただければ幸いです。コード:

    #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;

}
4

11 に答える 11

20

OK、たくさんの努力の後、ここに答えがあります:

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;
}
  • hModuleThreadとは何ですか?ここを見てください。で置き換えることができますGetModuleHandle()

  • GetEncoderClsidとは何ですか?ここを見てください。

ここで問題となるのは、エンコードされたpBitmapを(jpegとして)BYTEバッファーに保存するにはどうすればよいですか?

于 2009-06-23T19:25:03.923 に答える
6

フラットGDI+APIへの変換は、かなり簡単です。

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);
}

明らかではなかったのは、GdipCreateBitmapFromHBITMAP()によって作成されたGpBitmapのクリーンアップでした。Gdiplus :: Bitmapクラスにはデストラクタがないようであるため、内部のGpBitmapでは何もしません。また、他のGDI +オブジェクトの場合のように、GdipDeleteBitmap()はありません。したがって、リークを回避するために何をする必要があるのか​​は私にはわかりません。

編集: このコードは、Microsoftが提供するGDI+ヘッダーファイルがC++名前空間で必要なすべての関数を宣言しているという事実に対応していません。1つの解決策は、必要な宣言をコピーする(そして必要に応じてCコードに変換する)ことです。もう1つの可能性は、WineまたはMonoプロジェクトによって提供されるヘッダーを使用することです。これらは両方とも、Cコードとしてのコンパイルに対してはるかに優れた動作をするように見えます。

于 2009-06-16T00:18:16.103 に答える
4

1 つの可能性: このプログラムを変更して JPEG として保存できない場合は、C# / GDI+ / その他の優れたテクノロジを使用して保存ディレクトリを監視し、保存された BMP を jpeg に処理する 2 つ目のプログラムを作成します。

それができない場合は、Independent JPEG グループが 20 世紀後半からピュア C JPEG コードを公開しています。非常に最小限の Web ページがここから入手できます。

于 2009-06-15T16:58:59.680 に答える
3

コードの最初でこれを試してください

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

そして、GdipSaveImageToFile() は、c++ でコンパイルできると思います。

純粋な C では、おそらく最善の方法は、単一のインクルードを作成することです。#include "Gdiplusflat.h"「gdiplus.h」で関数宣言を探し、名前空間を含まない各関数に最小限のインクルードを追加します。GdipSaveImageToFile()

于 2009-11-26T14:22:20.817 に答える
0

あなたはFreeImageプロジェクトを見たことがありますか?ええ、それは外部ライブラリですが、かなり小さいです(〜1Mb)。画像を使って、あなたがやりたいことは何でもできます。このプロジェクトでなくても、知っておく価値は間違いありません。

于 2009-06-16T00:22:26.170 に答える
0

スペースが本当に心配な場合は、おそらく C ランタイム ライブラリも放棄できます。いくつかの関数しか使用していない場合は、独自のバージョンを作成してください (例: strcpy)。数 K バイトの Windows アプリケーションを作成することは可能です。これを行う小さなサンプル アプリをお送りします。

C イメージング コードを取り出して JPEG エンコーダーだけに分解すると、おそらく約 20K のコードが生成されます (グレースケール イメージをサポートする必要がない場合はそれより少なくなります)。

于 2009-07-03T20:11:33.567 に答える
0

C コードで JPEG を作成するには、おそらく外部ライブラリを使用する必要があります。これを行うための標準ライブラリ関数はありません。もちろん、ファイル形式を調べて、基準に基づいて独自のファイル形式を生成する関数を作成することもできます。ウィキペディアから始めることができます。外部ライブラリを使用できない場合は、ライブラリの関連関数を読み込んで、独自の JPEG を生成する方法を学ぶことができます。しかし、それは単にライブラリを使用するよりもはるかに多くの作業のように思えます。

また、ライブラリのサイズが実際に C に影響を与えるとは思いません。そのライブラリから呼び出す関数のサイズしか得られないと思います (とにかくそう思います)。もちろん、すべての関数が密結合されている場合は、とにかく巨大になります。

于 2009-06-15T17:47:30.700 に答える
0

私は同じ問題を抱えていて、このコードで解決しました。また、リンカーの入力に gdiplus.lib を含める必要があります。

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);
}

このコードは、jpg ファイル出力の品質値を追加することで強化できます。これを行うコードは、このスレッドの上位にあります。

于 2014-01-22T03:34:23.077 に答える
0

libjpeg は無料のオープン ソースであり、C でコーディングされています。そのコードを自分のコードにコピーできます。

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

于 2009-06-16T01:12:55.753 に答える
-2

車輪を再発明しないでください。

スクリーンショットを撮って jpeg として保存するプログラムが必要な場合は、ImageMagickimportを使用するだけです。

シェル コマンドは単純に次のようになります。

import screen.jpg

もちろん、上記を takeScreenshot.bat として保存すると、要件に一致するプログラムが作成されます。

于 2009-06-15T17:47:39.420 に答える