void MainPage::Draw(ISurfaceImageSourceNative* sisn, const wchar_t* mask) { HRESULT hr; ComPtr<IDXGISurface> surface; RECT rect = { 0, 0, 400, 400 }; POINT renderTargetOffset; // 可视区域在surface中的偏移量 // 可以想象成surface是一张大画板,比我们的显示区域400*400要大。每次Direct2D会选择一个区域来画,不一定是(0,0),因为可能有一些缓冲策略,使得每次画图的区域都不一样 HR(PrepareDraw(sisn, rect, &surface, &renderTargetOffset)); // 创建所有我们需要的图形 ComPtr<IWICBitmapSource> maskSrc = GetMask(LoadImageByWIC(mask).Get()); ComPtr<ID2D1Bitmap> maskBmp; ComPtr<ID2D1Bitmap> imgBmp; ComPtr<ID2D1BitmapBrush> imgBrush; ComPtr<ID2D1Bitmap1> tgrBmp; // Note ID2D1Bitmap1 HR(m_d2dDeviceContext->CreateBitmapFromWicBitmap(maskSrc.Get(), &maskBmp)); HR(m_d2dDeviceContext->CreateBitmapFromWicBitmap(m_img.Get(), &imgBmp)); HR(m_d2dDeviceContext->CreateBitmapBrush(imgBmp.Get(), &imgBrush)); HR(m_d2dDeviceContext->CreateBitmapFromDxgiSurface(surface.Get(), nullptr, &tgrBmp)); m_d2dDeviceContext->SetTarget(tgrBmp.Get()); m_d2dDeviceContext->BeginDraw(); m_d2dDeviceContext->SetTransform(D2D1::Matrix3x2F::Translation(renderTargetOffset.x, renderTargetOffset.y)); // 应用可视区域的偏移量来调整device context的位置 m_d2dDeviceContext->Clear({0, 0, 1, 1}); // 将画布填充成蓝色,让我们的改变变得明显一些 m_d2dDeviceContext->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED); // 必须先设置这个,才能调用下面的函数 m_d2dDeviceContext->FillOpacityMask(maskBmp.Get(), imgBrush.Get()); // 应用蒙版 HR(m_d2dDeviceContext->EndDraw()); m_d2dDeviceContext->SetTarget(nullptr); HR(sisn->EndDraw()); }
以下是我们的一些辅助函数:
尝试开始画图。这会确定我们需要画图的区域(在surface上)。如果这个开始调用失败了,我们检测一下原因,尝试第二次。
HRESULT MainPage::PrepareDraw(ISurfaceImageSourceNative* sisn, const RECT& updateRect, IDXGISurface** surface, POINT* offset) { HRESULT hr; hr = sisn->BeginDraw(updateRect, surface, offset); if ((hr == DXGI_ERROR_DEVICE_REMOVED) || (hr == DXGI_ERROR_DEVICE_RESET)) { CreateDevice(); PrepareDraw(sisn, updateRect, surface, offset); } else { return hr; } }
ComPtr<IWICBitmapSource> MainPage::LoadImageByWIC(const wchar_t* file)函数,通过WIC加载图片,既加载我们的原图像,也加载蒙版图像。
ComPtr<IWICBitmapSource> MainPage::LoadImageByWIC(const wchar_t* file) { ComPtr<IWICBitmapDecoder> decoder; ComPtr<IWICBitmapFrameDecode> frame; ComPtr<IWICFormatConverter> converter; HRESULT hr; HR(m_factory->CreateDecoderFromFilename(file, nullptr, GENERIC_READ, WICDecodeMetadataCacheOnDemand, &decoder)); HR(decoder->GetFrame(0, &frame)); HR(m_factory->CreateFormatConverter(&converter)); HR(converter->Initialize( frame.Get(), GUID_WICPixelFormat32bppPBGRA, // 这个预处理的BGRA,因为两个透明图层叠加时需要把RGB通道和Alpha通道相乘相加,而预处理就是预先把相乘的步骤完成了,可以增加一点效率 WICBitmapDitherTypeNone, nullptr, 0, WICBitmapPaletteTypeCustom)); return converter; }
MainPage::GetMask函数把黑白色的图片处理成用alpha通道表示透明度的bitmap。因为只有黑白色的话,颜色是通过RGB通道确定的,alpha通道一直是1.0。而direct2D提供的API呢,却是使用的alpha通道来进行蒙版应用。这样看似合理一些,但我们生成一张利用alpha通道来表现透明度的蒙版图片,肯定是要比我们用单纯的黑白色来表现蒙版要麻烦一些的。
ComPtr<IWICBitmapSource> MainPage::GetMask(IWICBitmapSource* src) { uint32_t width, height; src->GetSize(&width, &height); size_t len = width * 4 * height; unique_ptr<byte[]> pixels(new byte[len]); src->CopyPixels(nullptr, width * 4, len, pixels.get()); for (size_t i = 0; i < width * height; i++) { pixels[i * 4 + 3] = (pixels[i * 4] + pixels[i * 4 + 1] + pixels[i * 4 + 2]) / 3; } ComPtr<IWICBitmap> bmp; m_factory->CreateBitmapFromMemory(width, height, GUID_WICPixelFormat32bppPBGRA, width * 4, len, pixels.get(), &bmp); return bmp; }
用Direct2D的好处
图形应用(包括图像处理,地图),游戏,这些特殊的应用需要一个强悍的图形技术来支撑它们的运作和体验,而Direct2D无疑为我们提供了这样的可能,让我们能在XAML之中,发挥图形技术的强大威力。
扩展