Trong phát triển ứng dụng UWP bằng C++, việc quản lý tài nguyên hình ảnh đòi hỏi sự phối hợp giữa Windows Runtime (WinRT) và các API đồ họa cấp thấp như Direct2D (D2D) và Direct3D (D3D). Quy trình bắt đầu từ việc truy xuất tệp tin và chuyển đổi dữ liệu qua các lớp trung gian như Windows Imaging Component (WIC) và DXGI.
1. Khởi tạo luồng dữ liệu từ tệp tin
Để nạp một tệp hình ảnh từ gói ứng dụng, chúng ta sử dụng StorageFile để lấy đối tượng IRandomAccessStream. Đây là bước đệm đầu tiên để truy cập dữ liệu thô của tệp tin.
// Lấy tệp tin từ thư mục Assets
auto getFileTask = create_task(StorageFile::GetFileFromApplicationUriAsync(ref new Uri("ms-appx:///Assets/image_source.png")));
getFileTask.then([](StorageFile^ file) {
return file->OpenReadAsync();
}).then([](IRandomAccessStream^ imageStream) {
// Xử lý luồng dữ liệu tại đây
});
2. Chuyển đổi sang giao diện IStream của WIC
Các API của WIC không làm việc trực tiếp với IRandomAccessStream của WinRT. Do đó, cần sử dụng hàm trợ giúp để bọc luồng này vào giao diện IStream chuẩn của COM.
ComPtr<IStream> wicStream;
HRESULT hr = CreateStreamOverRandomAccessStream(imageStream, IID_PPV_ARGS(&wicStream));
if (FAILED(hr)) throw ref new FailureException();
3. Giải mã và chuẩn hóa định dạng điểm ảnh
Sử dụng IWICImagingFactory để tạo bộ giải mã (Decoder). Để đảm bảo tính tương thích cao nhất với phần cứng đồ họa, dữ liệu hình ảnh nên được chuyển đổi sang định dạng 32-bit PBGRA (B8G8R8A8).
ComPtr<IWICBitmapDecoder> imageDecoder;
hr = wicFactory->CreateDecoderFromStream(wicStream.Get(), nullptr, WICDecodeMetadataCacheOnDemand, &imageDecoder);
ComPtr<IWICBitmapFrameDecode> bitmapFrame;
imageDecoder->GetFrame(0, &bitmapFrame);
// Chuyển đổi sang định dạng điểm ảnh tương thích Direct2D/Direct3D
ComPtr<IWICFormatConverter> pixelConverter;
wicFactory->CreateFormatConverter(&pixelConverter);
pixelConverter->Initialize(
bitmapFrame.Get(),
GUID_WICPixelFormat32bppPBGRA,
WICBitmapDitherTypeNone,
nullptr,
0.0f,
WICBitmapPaletteTypeCustom
);
4. Tạo đối tượng Bitmap cho Direct2D
Nếu mục tiêu chỉ là hiển thị 2D, ta có thể tạo ngay ID2D1Bitmap1 từ bộ chuyển đổi định dạng của WIC thông qua Device Context hiện hành.
ComPtr<ID2D1Bitmap1> d2dContextBitmap;
hr = d2dContext->CreateBitmapFromWicBitmap(pixelConverter.Get(), nullptr, &d2dContextBitmap);
5. Tích hợp hình ảnh vào Texture của Direct3D
Để sử dụng hình ảnh trong môi trường 3D hoặc các shader phức tạp, chúng ta cần tạo một ID3D11Texture2D. Quá trình này đòi hỏi thiết lập các thông số kỹ thuật cho Texture phù hợp với dữ liệu đã giải mã.
D3D11_TEXTURE2D_DESC textureInfo = {};
textureInfo.Width = imgWidth;
textureInfo.Height = imgHeight;
textureInfo.MipLevels = 1;
textureInfo.ArraySize = 1;
textureInfo.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
textureInfo.SampleDesc.Count = 1;
textureInfo.Usage = D3D11_USAGE_DEFAULT;
textureInfo.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET;
ComPtr<ID3D11Texture2D> gpuTexture;
d3dDevice->CreateTexture2D(&textureInfo, nullptr, &gpuTexture);
6. Ánh xạ giữa Direct3D và Direct2D qua DXGI
Để vẽ nội dung từ WIC lên Texture của Direct3D bằng các lệnh Direct2D, ta cần sử dụng IDXGISurface làm cầu nối. Sau đó, tạo một Render Target đặc biệt từ Surface này.
ComPtr<IDXGISurface> dxgiBackingSurface;
gpuTexture.As(&dxgiBackingSurface);
D2D1_RENDER_TARGET_PROPERTIES rtProps = D2D1::RenderTargetProperties(
D2D1_RENDER_TARGET_TYPE_DEFAULT,
D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED),
96.0f, // DPI mặc định
96.0f
);
ComPtr<ID2D1RenderTarget> surfaceRenderTarget;
d2dFactory->CreateDxgiSurfaceRenderTarget(dxgiBackingSurface.Get(), &rtProps, &surfaceRenderTarget);
7. Thực hiện tác vụ vẽ nội dung
Cuối cùng, ta tạo một Bitmap cục bộ tương thích với Render Target vừa tạo và thực hiện thao tác vẽ. Lưu ý rằng tài nguyên được tạo từ surfaceRenderTarget gắn liền với ngữ cảnh của Surface đó.
ComPtr<ID2D1Bitmap> finalBitmap;
surfaceRenderTarget->CreateBitmapFromWicBitmap(pixelConverter.Get(), nullptr, &finalBitmap);
surfaceRenderTarget->BeginDraw();
surfaceRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::Black, 0.0f));
surfaceRenderTarget->DrawBitmap(finalBitmap.Get());
surfaceRenderTarget->EndDraw();
Sau khi kết thúc quá trình EndDraw, dữ liệu hình ảnh từ tệp tin ban đầu đã được nạp thành công vào Texture của Direct3D, sẵn sàng để sử dụng làm tài nguyên shader hoặc hiển thị trong quy trình render 3D.