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

StackOverflow https://stackoverflow.com/questions/307348

  •  08-07-2019
  •  | 
  •  

Вопрос

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

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

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

Любая помощь будет оценена по достоинству!

Спасибо.

Отредактировано (JDePedro):Как некоторые из вас предположили, я пытался использовать alpha blend для раскрашивания растрового изображения с помощью альфа-канала.Это всего лишь реализованный мной тест, в котором я загружаю 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).

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

Какая-нибудь помощь???

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

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

Это было полезно?

Решение

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

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

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

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

Другие советы

Для будущих пользователей 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);
}

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

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

Покрасить его очень легко с помощью Алфавитная функция.

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

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

При правильной настройке этого массива вы можете вычислить нужное вам значение следующим образом:

R = multiplicationLookup[alpha][R];
G = multiplicationLookup[alpha][G];
B = multiplicationLookup[alpha][B];

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top