Почему я не могу поместить раздел DIB в буфер обмена? - PullRequest
0 голосов
/ 13 мая 2018

Я пытаюсь захватить содержимое экрана, изменить биты захваченного изображения напрямую, а затем поместить результат в буфер обмена. (На самом деле я не заинтересован в буфере обмена, но использую его в качестве шага тестирования.)

Я начал с примера с одного из ответов на на этот вопрос . Однако он использует CreateCompatibleBitmap, и, насколько я понимаю, нет никакого способа прямого доступа к битам растровых изображений, созданных с помощью этой функции, поэтому я пытаюсь использовать CreateDIBSection вместо этого. Вот что у меня есть:

void GetScreenShot(void)
{
    int x1, y1, w, h;

    // get screen dimensions
    x1  = GetSystemMetrics(SM_XVIRTUALSCREEN);
    y1  = GetSystemMetrics(SM_YVIRTUALSCREEN);
    w  = GetSystemMetrics(SM_CXVIRTUALSCREEN);
    h  = GetSystemMetrics(SM_CYVIRTUALSCREEN);

    // copy screen to bitmap

    HDC hScreen = GetDC(NULL);

    HDC hDC = CreateCompatibleDC(hScreen);
    if( !hDC )
        throw 0;

    // This works:
    //HBITMAP hBitmap = CreateCompatibleBitmap(hScreen, w, h);

    BITMAPINFO BitmapInfo;
    BitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    BitmapInfo.bmiHeader.biWidth = w;
    BitmapInfo.bmiHeader.biHeight = h;
    BitmapInfo.bmiHeader.biPlanes = 1;
    BitmapInfo.bmiHeader.biBitCount = 24;   // assumption; ok for our use case
    BitmapInfo.bmiHeader.biCompression = BI_RGB;
    BitmapInfo.bmiHeader.biSizeImage = ((w * 3 + 3) & ~3) * h;
    BitmapInfo.bmiHeader.biXPelsPerMeter = (int)(GetDeviceCaps( hScreen, LOGPIXELSX ) * 39.3701 + 0.5);
    BitmapInfo.bmiHeader.biYPelsPerMeter = (int)(GetDeviceCaps( hScreen, LOGPIXELSY ) * 39.3701 + 0.5);
    BitmapInfo.bmiHeader.biClrUsed = 0;
    BitmapInfo.bmiHeader.biClrImportant = 0;
    BitmapInfo.bmiColors[0].rgbBlue = 0;
    BitmapInfo.bmiColors[0].rgbGreen = 0;
    BitmapInfo.bmiColors[0].rgbRed = 0;
    BitmapInfo.bmiColors[0].rgbReserved = 0;

    void *pBits;
    // This does not work:
    HBITMAP hBitmap = CreateDIBSection( hScreen, &BitmapInfo, DIB_RGB_COLORS, &pBits, NULL, 0 );
    if( !hBitmap )
        throw 0;

    HGDIOBJ old_obj = SelectObject(hDC, hBitmap);
    if( !old_obj )
        throw 0;

    if( !BitBlt(hDC, 0, 0, w, h, hScreen, x1, y1, SRCCOPY) )
        throw 0;

    if( !SelectObject(hDC, old_obj) )
        throw 0;

    if( !GdiFlush() )
        throw 0;

    // this is where we would modify the image

    // save bitmap to clipboard

    if( !OpenClipboard(NULL) )
        throw 0;

    if( !EmptyClipboard() )
        throw 0;

    if( !SetClipboardData( CF_BITMAP, hBitmap ) )   // CF_DIB causes the throw
        throw 0;

    if( !CloseClipboard() )
        throw 0;

    // clean up
    DeleteDC(hDC);
    ReleaseDC(NULL, hScreen);
    DeleteObject(hBitmap);
}

Однако это не работает. Все звонки сообщают об успехе, но изображение не попадает в буфер обмена.

Когда я запускаю это в отладчике, я могу видеть то, что выглядит как данные изображения, в pBits после вызова BitBlt, хотя это немного подозрительно, поскольку первая группа значений имеет R, G, B все то же самое, но левый нижний угол моего экрана на самом деле имеет голубоватый цвет. В любом случае, даже если фактические биты неверны, я должен получить что-то изображения в буфере обмена, но я не получаю.

Я пытался использовать CF_DIB в качестве первого аргумента для SetClipboardData вместо CF_BITMAP, но затем вызов не удался.

Если я закомментирую вызов CreateDIBSection и раскомментирую вызов CreateCompatibleBitmap, он будет работать, но у меня нет возможности напрямую изменять биты изображения.

Полагаю, я мог бы сначала захватить мой раздел DIB, изменить его, затем вызвать CreateCompatibleBitmap и перевести из раздела DIB в «совместимое растровое изображение», но кажется, что это просто глупо копировать биты снова без видимой причины.

Почему я не могу передать раздел DIB на SetClipboardData?

(Должен сказать, я ненавижу работать с GDI и т. Д. Обычно это ясно, как грязь.)

1 Ответ

0 голосов
/ 13 мая 2018

Разобрался, наконец, когда нашел это .Документация по API на MSDN довольно расплывчата по этому поводу, возможно, потому что сама она восходит к тому времени, но похоже, что все функции буфера обмена используют систему выделения памяти в стиле Windows 3.x (GlobalAlloc и т. Д.).

Для системного буфера обмена имеет смысл выставлять разделяемую память приложению напрямую, а не ОС, вынужденной копировать данные во внутренние буферы.Но функциональность буфера обмена восходит достаточно далеко, так что более новых схем на основе файла подкачки для общей памяти не существовало, поэтому они должны были использовать GlobalAlloc память.Когда появилась 32-битная Windows, имело больше смысла просто эмулировать этот механизм, а не нарушать существующий код приложения.

Я сильно подозреваю, что по аналогичным причинам большинство дескрипторов GDI на самом деле также являются дескрипторами GlobalAlloc, ивот почему вы можете передать возврат из CreateCompatibleBitmap в буфер обмена.В отличие от этого, CreateDIBSection действительно не полностью использует распределение в старом стиле, что очевидно из того факта, что вы можете сказать ему хранить биты в отображении файла.(Я подозреваю, что дескриптор, который он возвращает, все еще от GlobalAlloc, но что блок, выделенный таким образом, содержит прямой указатель на виртуальную память для данных изображения, и SetClipboardData проверяет это, потому что это очевидная «ошибка».)

Так что я все исправил, просто позволив CreateDIBSection выделять, где он хочет, потому что так или иначе невозможно передать это на SetClipboardData в любом случае, а затем делать это, когда я хочу отправитьв буфер обмена:

void CScreenshot::SendToClipboard( void )
{
    HGLOBAL hClipboardDib = GlobalAlloc( GMEM_MOVEABLE | GMEM_SHARE, cbDib );
    if( !hClipboardDib )
        throw 0;

    void *pClipboardDib = GlobalLock( hClipboardDib );
    memcpy( pClipboardDib, &BitmapInfo, sizeof(BITMAPINFOHEADER) );
    memcpy( (BITMAPINFOHEADER*)pClipboardDib+1, pBits, BitmapInfo.bmiHeader.biSizeImage );
    GlobalUnlock( hClipboardDib );

    if( !OpenClipboard( NULL ) )
    {
        GlobalFree( hClipboardDib );
        throw 0;
    }

    EmptyClipboard();
    SetClipboardData( CF_DIB, hClipboardDib );
    CloseClipboard();
}

К сожалению, я должен сделать избыточную копию здесь, но с другой стороны, я сильно подозреваю, что приложение, читающее буфер обмена, увидит ту же копию, а не Windowsлюбое дальнейшее внутреннее копирование.

Если я хочу быть наркоманом общей эффективности, я подозреваю , что дескриптор, возвращенный из CreateCompatibleBitmap, может быть использован при вызове GlobalLock, а затем выможет получить биты напрямую, не вызывая копию в CScreenshot::SendToClipboard, потому что вы можете просто передать ее прямо в SetClipboardData.Тем не менее, я также сильно подозреваю, что это будет недокументированное поведение (но поправьте меня, если я ошибаюсь!), Так что это довольно плохая идея.Вам также придется отслеживать, передали ли вы это в буфер обмена или нет, и если вы это сделали, не вызывайте DeleteObject для него.Но я не уверен.Я также подозреваю, что SetClipboardData в любом случае придется скопировать его, потому что он, вероятно, не выделен с GMEM_SHARE.

Спасибо комментаторам за то, что я немного приблизился к его выяснению.

...