Как нарисовать 32-битные растровые изображения альфа-канала?
-
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.