Как нарисовать 32-битные растровые изображения с альфа-каналом?

Мне нужно создать настраиваемый элемент управления для отображения изображений BMP с альфа-каналом. Фон может быть окрашен в разные цвета, а изображения имеют тени, поэтому мне нужно по-настоящему «раскрасить» альфа-канал.

Кто-нибудь знает, как это сделать?

Я также хочу, если возможно, создать маску, используя информацию об альфа-канале, чтобы знать, была ли мышь нажата на изображении или на прозрачной области.

Мы будем благодарны за любую помощь!

Спасибо.

Отредактировано (JDePedro): как некоторые из вас предположили, я пытался использовать альфа-смешение, чтобы раскрасить растровое изображение с альфа-каналом. Это всего лишь тест, который я реализовал, когда я загружаю 32-битное растровое изображение из ресурсов и пытаюсь раскрасить его с помощью функции AlphaBlend:

void CAlphaDlg::OnPaint()
{
    CClientDC dc(this);
    CDC  dcMem;
    dcMem.CreateCompatibleDC(&dc);

    CBitmap bitmap;
    bitmap.LoadBitmap(IDB_BITMAP);

    BITMAP BitMap;
    bitmap.GetBitmap(&BitMap);
    int nWidth = BitMap.bmWidth;
    int nHeight = BitMap.bmHeight;
    CBitmap *pOldBitmap = dcMem.SelectObject(&bitmap);

    BLENDFUNCTION m_bf;
    m_bf.BlendOp = AC_SRC_OVER;
    m_bf.BlendFlags = 0;
    m_bf.SourceConstantAlpha = 255;
    m_bf.AlphaFormat = AC_SRC_ALPHA;
    AlphaBlend(dc.GetSafeHdc(), 100, 100, nWidth, nHeight, dcMem.GetSafeHdc(), 0, 0,nWidth, nHeight,m_bf); 

    dcMem.SelectObject(pOldBitmap);

    CDialog::OnPaint();
}

Это просто тест, поэтому я помещаю код в OnPaint диалогового окна (я также пробовал функцию AlphaBlend объекта CDC).

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

Любая помощь???

Это снимок экрана ... его непросто увидеть, но вокруг синего круга есть белый прямоугольник: http://img385.imageshack.us/img385/7965/alphamh8.png

В порядке. Я понял! Мне нужно предварительно умножить каждый пиксель на значение альфа. Кто-нибудь может предложить оптимизированный способ сделать это?


person Javier De Pedro    schedule 20.11.2008    source источник


Ответы (6)


Обычно я это делаю с помощью DIBSection - независимого от устройства растрового изображения, пиксели которого можно изменять напрямую. К сожалению, MFC не поддерживает DIBSections: для ее использования необходимо использовать функцию Win32 CreateDIBSection ().

Начните с загрузки растрового изображения как 32-битного RGBA (то есть четыре байта на пиксель: один красный, один зеленый, один синий и один для альфа-канала). В элементе управления создайте DIBSection подходящего размера. Затем в рутине рисования

  • Скопируйте данные растрового изображения в данные растрового изображения DIBSection, используя байт альфа-канала, чтобы смешать растровое изображение с цветом фона.
  • Создайте контекст устройства и выберите в нем DIBSection.
  • Используйте BitBlt () для копирования из контекста нового устройства в контекст устройства рисования.

Вы можете создать маску на основе необработанных данных растрового изображения, просто посмотрев на значения альфа-канала - я не уверен, о чем вы здесь спрашиваете.

person DavidK    schedule 21.11.2008

Для будущих пользователей Google вот рабочая функция предварительного умножения. Обратите внимание, что это было взято из http://www.viksoe.dk/code/alphatut1.htm.

inline void PremultiplyBitmapAlpha(HDC hDC, HBITMAP hBmp)
{
   BITMAP bm = { 0 };
   GetObject(hBmp, sizeof(bm), &bm);
   BITMAPINFO* bmi = (BITMAPINFO*) _alloca(sizeof(BITMAPINFOHEADER) + (256 * sizeof(RGBQUAD)));
   ::ZeroMemory(bmi, sizeof(BITMAPINFOHEADER) + (256 * sizeof(RGBQUAD)));
   bmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
   BOOL bRes = ::GetDIBits(hDC, hBmp, 0, bm.bmHeight, NULL, bmi, DIB_RGB_COLORS);
   if( !bRes || bmi->bmiHeader.biBitCount != 32 ) return;
   LPBYTE pBitData = (LPBYTE) ::LocalAlloc(LPTR, bm.bmWidth * bm.bmHeight * sizeof(DWORD));
   if( pBitData == NULL ) return;
   LPBYTE pData = pBitData;
   ::GetDIBits(hDC, hBmp, 0, bm.bmHeight, pData, bmi, DIB_RGB_COLORS);
   for( int y = 0; y < bm.bmHeight; y++ ) {
      for( int x = 0; x < bm.bmWidth; x++ ) {
         pData[0] = (BYTE)((DWORD)pData[0] * pData[3] / 255);
         pData[1] = (BYTE)((DWORD)pData[1] * pData[3] / 255);
         pData[2] = (BYTE)((DWORD)pData[2] * pData[3] / 255);
         pData += 4;
      }
   }
   ::SetDIBits(hDC, hBmp, 0, bm.bmHeight, pBitData, bmi, DIB_RGB_COLORS);
   ::LocalFree(pBitData);
}

Итак, ваш OnPaint становится:

void MyButton::OnPaint()
{
    CPaintDC dc(this);

    CRect rect(0, 0, 16, 16);

    static bool pmdone = false;
    if (!pmdone) {
        PremultiplyBitmapAlpha(dc, m_Image);
        pmdone = true;
    }

    BLENDFUNCTION bf;
    bf.BlendOp = AC_SRC_OVER;
    bf.BlendFlags = 0;
    bf.SourceConstantAlpha = 255;
    bf.AlphaFormat = AC_SRC_ALPHA;

    HDC src_dc = m_Image.GetDC();
    ::AlphaBlend(dc, rect.left, rect.top, 16, 16, src_dc, 0, 0, 16, 16, bf);
    m_Image.ReleaseDC();
}

И загрузка изображения (в конструкторе вашего элемента управления):

if ((HBITMAP)m_Image == NULL) {
    m_Image.LoadFromResource(::AfxGetResourceHandle(), IDB_RESOURCE_OF_32_BPP_BITMAP);
}
person Roel    schedule 19.06.2013
comment
Что, если я хочу, чтобы прозрачное изображение отображалось на статической этикетке? pStatic- ›ModifyStyle (0xF, SS_BITMAP); pStatic- ›SetBitmap (растровое изображение); - person Michael Haephrati; 15.01.2017
comment
Статическая переменная в OnPaint (), безусловно, очень плохая идея. Если у вас более одной кнопки, это не сработает. pmdone должен быть переменной-членом каждого MyButton. Однако функция BLENDFUNCTION может быть статической. Или лучше: вы можете вызвать PremultiplyBitmapAlpha () сразу после LoadFromResource () и использовать DC любого окна с GetWindowDC (). Тогда вам больше не нужна переменная pmdone. - person Elmue; 05.08.2020
comment
Очевидно, m_Image тоже должен быть статическим. Нет необходимости загружать одно и то же растровое изображение для каждой кнопки. И нет, вы не можете вызвать его после LoadFromResource () (по крайней мере, не так, как он представлен здесь), потому что вы можете создавать экземпляры этих объектов до того, как у вас будут созданы какие-либо окна. - person Roel; 11.08.2020

Вам нужно выполнить альфа-смешение с цветом фона, а затем вынуть альфа-канал для рисования это к контролю.

Альфа-канал должен занимать каждый 4-й байт вашего изображения. Вы можете использовать это непосредственно для своей маски, или вы можете просто скопировать каждый 4-й байт в новое изображение маски.

person Mark Ransom    schedule 21.11.2008
comment
Привет, Марк, посмотрим, понимаю ли я твое предложение. Представьте, что я получаю управление от CStatic / CWnd и добавляю CBitmap в качестве члена. Затем каждый раз, когда элемент управления будет раскрашиваться (в методе OnPaint), мне нужно получить фон и сделать его смешивание с моим изображением? - person Javier De Pedro; 21.11.2008
comment
(продолжить) Я прав? Я должен все делать сам? Похоже, что это может иметь высокую стоимость ... не могли бы вы (кто-нибудь) сказать мне, как лучше (более оптимально) это сделать? Может быть, какие-нибудь чертовы статьи или даже кодовый сниппет? Спасибо. - person Javier De Pedro; 21.11.2008

Нарисовать это очень просто с помощью функции AlphaBlend .

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

person P Daddy    schedule 21.11.2008

Оптимизированный способ предварительного умножения каналов RGB на альфа-канал - это создание массива [256] [256], содержащего вычисленные результаты умножения. Первое измерение - это альфа-значение, второе - это значение R / G / B, значения в массиве - это предварительно умноженные значения, которые вам нужны.

Если этот массив настроен правильно, вы можете рассчитать необходимое значение следующим образом:

R = multiplicationLookup[alpha][R];
G = multiplicationLookup[alpha][G];
B = multiplicationLookup[alpha][B];
person mackenir    schedule 02.12.2008
comment
Сомнительно, будет ли случайный доступ к большому массиву размером 64 КБ быстрее, чем выполнение вычислений. Особенно, когда операцию можно так идеально распределить по векторному блоку. Это, безусловно, создает чрезмерную нагрузку на кеш-память ЦП за очень небольшую (если вообще есть) взамен. - person IInspectable; 31.03.2018

Вы на правильном пути, но вам нужно исправить две вещи.

Сначала используйте :: LoadImage (.. LR_CREATEDIBSECTION ..) вместо CBitmap :: LoadBitmap. Во-вторых, вы должны «предварительно умножить» значения RGB каждого пикселя в растровом изображении на их соответствующее значение A. Это требование функции AlphaBlend, см. Описание AlphaFormat на этой странице MSDN. Т

lpng содержит рабочий код, который выполняет предварительное умножение данных DIB.

person Community    schedule 19.01.2009