Здравствуйте! После разбора WIC начинается работа с данными, для которой он как раз и пригодится.
Сразу скажу, что спрайты будут в следующей статье, так как понадобится немного залезть в Direct3D, рассказать про DXGI-цепочки и всё что будет важным для понимания этих вещей. Поэтому эта статья не очень длинная, но касается текстур — а текстуры, к слову, будут использоваться спрайтами.
Кстати, о разнице между спрайтами и текстурой:
Текстура — после того как вы декодировали файл с изображением и конвертировали его в нужный пиксельный формат, вы можете передать данные в видеопамять (такие данные в видеопамяти — это текстура).
Спрайт — ссылается на текстуру и содержит описание того, как её отрисовать, и т.п. То есть если нужно отрисовать текстуру по-разному, вы прибегаете к спрайтам.
Просто напоминаю, что пиксельные форматы описывают хранение одного пикселя. Вот весь их список в Direct2D.
Ну а теперь — процесс получения текстуры. Как я говорил ранее, необходимо декодировать файл с изображением и конвертировать его в один из пиксельных форматов. Так как это было в статье #8, здесь будет не полный код:
1. Получаем доступ к фабрике WICIWICImagingFactory* pWicFactory;CoCreateInstance(CLSID_WICImagingFactory, ...);2. Декодируем файлIWICBitmapDecoder* pDecoder;pWicFactory->CreateDecoderFromFilename(L"image.png", ..., &pDecoder);3. Извлекаем кадрIWICBitmapFrameDecode* pFrame;pDecoder->GetFrame(0, &pFrame);4. Конвертируем в понятный Direct2D форматIWICFormatConverter* pConverter;pWicFactory->CreateFormatConverter(&pConverter);pConverter->Initialize(pFrame, GUID_WICPixelFormat32bppPBGRA, ...);
Теперь создание текстуры:
ID2D1Bitmap* pBitmap;pRenderTarget->CreateBitmapFromWicBitmap(pConverter, nullptr, &pBitmap);
После этого текстура находится в видеопамяти. Теперь о том, что можно сделать с битмапом:
-
D2D1_SIZE_F GetSize() — возвращает размеры битмапа с учётом DPI.
-
D2D1_SIZE_U GetPixelSize() — возвращает размеры без учёта DPI
-
D2D1_PIXEL_FORMAT GetPixelFormat() — возвращает формат пикселей.
-
void GetDpi(FLOAT* dpiX, FLOAT* dpiY) — возвращает значения DPI, с которыми был создан битмап.
-
HRESULT CopyFromMemory(const D2D1_RECT_U* dstRect, const void* srcData, UINT32 pitch) — копирует пиксели из памяти в битмап. dstRect — область в битмапе, куда копируем (если nullptr, то во весь битмап). srcData — указатель на данные в системной памяти, pitch — ширина строки данных в байтах.
-
HRESULT CopyFromBitmap(const D2D1_POINT_2U* destPoint, ID2D1Bitmap* bitmap, const D2D1_RECT_U* srcRect) — копирует пиксели из другого битмапа в текущий. Аргументы аналогичны CopyFromMemory.
-
HRESULT CopyFromRenderTarget(const D2D1_POINT_2U* destPoint, ID2D1RenderTarget* renderTarget, const D2D1_RECT_U* srcRect) — копирует пиксели с рендер-таргета. Аргументы аналогичны CopyFromBitmap.
-
HRESULT CreateBitmapBrush(ID2D1Bitmap* bitmap, const D2D1_BITMAP_BRUSH_PROPERTIES* bitmapBrushProperties, const D2D1_BRUSH_PROPERTIES* brushProperties, ID2D1BitmapBrush** bitmapBrush) — создаёт кисть из текстуры. bitmapBrushProperties — свойства кисти (можно передать nullptr для значений по умолчанию), brushProperties — общие свойства (непрозрачность, матрица трансформации и т.п.).
Отрисовку разберём, когда дойдём до этого пункта. К слову, начиная с Windows 8 существует также интерфейс ID2D1Bitmap1, в котором есть пара дополнительных функций:
-
HRESULT Map(D2D1_MAP_OPTIONS options, D2D1_MAPPED_RECT* mappedRect) — предоставляет прямой доступ к данным битмапа в видеопамяти. Пока битмап замаплен, его нельзя использовать для рисования. options — опции: D2D1_MAP_OPTIONS_READ (только чтение), D2D1_MAP_OPTIONS_WRITE (только запись), а также их комбинация.
-
HRESULT Unmap() — завершает доступ, начатый Map.
Теперь о отрисовке:
-
void DrawBitmap( ID2D1Bitmap* bitmap, const D2D1_RECT_F& destinationRectangle, FLOAT opacity, D2D1_BITMAP_INTERPOLATION_MODE interpolationMode, const D2D1_RECT_F* sourceRectangle = nullptr ) — упрощённая версия на ID2D1RenderTarget:
-
bitmap — рисуемый битмап
-
destinationRectangle — координаты где рисовать
-
opacity — непрозрачность от 0.0 (полностью прозрачно) до 1.0 (непрозрачно)
-
interpolationMode — режим интерполяции: LINEAR (сглаживание) или NEAREST_NEIGHBOR (без сглаживания)
-
sourceRectangle — область исходного битмапа (в пикселях) для рисования. nullptr — рисуется целиком .
-
-
void DrawBitmap( ID2D1Bitmap* bitmap, const D2D1_RECT_F* destinationRectangle, FLOAT opacity, D2D1_INTERPOLATION_MODE interpolationMode, const D2D1_RECT_F* sourceRectangle, const D2D1_MATRIX_4X4_F* perspectiveTransform ) — расширенная версия на ID2D1DeviceContext:
-
bitmap — битмап
-
destinationRectangle — координаты
-
opacity — непрозрачность
-
interpolationMode — D2D1_INTERPOLATION_MODE (аналог предыдущего, но включает дополнительные опции, например, ANISOTROPIC)
-
sourceRectangle — как раньше
-
perspectiveTransform — матрица 4×4 для перспективного преобразования. Позволяет рисовать битмап с эффектом наклона или трёхмерного поворота
-
Собственно, код, который использует все эти методы (с комментариями):
Информация о изображении: image.png (RGBA, 200×200). Содержимое:

При запуске кода появится сообщение с информацией о файле:

После нажатия «OK» будет такое содержимое окна:

Код
#define WIN32_LEAN_AND_MEAN#include <windows.h>#include <d2d1.h>#include <d2d1_1.h> // ID2D1Bitmap1#include <wincodec.h>#include <cstdio>#include <comdef.h> #pragma comment(lib, "d2d1.lib")#pragma comment(lib, "windowscodecs.lib")#pragma comment(lib, "ole32.lib")ID2D1Factory* g_pD2DFactory = nullptr;ID2D1HwndRenderTarget* g_pRT = nullptr;ID2D1Bitmap* g_pBitmap = nullptr; ID2D1Bitmap* g_pCopyBitmap = nullptr; ID2D1BitmapBrush* g_pBrush = nullptr; IWICImagingFactory* g_pWICFactory = nullptr;HWND g_hwnd;// Загрузка битмапа через WICHRESULT LoadBitmapFromFile(ID2D1RenderTarget* pRT, const wchar_t* filename, ID2D1Bitmap** ppBitmap){ IWICBitmapDecoder* pDecoder = nullptr; HRESULT hr = g_pWICFactory->CreateDecoderFromFilename( filename, nullptr, GENERIC_READ, WICDecodeMetadataCacheOnLoad, &pDecoder); if (FAILED(hr)) return hr; IWICBitmapFrameDecode* pFrame = nullptr; hr = pDecoder->GetFrame(0, &pFrame); pDecoder->Release(); if (FAILED(hr)) return hr; IWICFormatConverter* pConverter = nullptr; hr = g_pWICFactory->CreateFormatConverter(&pConverter); if (SUCCEEDED(hr)) { hr = pConverter->Initialize( pFrame, GUID_WICPixelFormat32bppPBGRA, WICBitmapDitherTypeNone, nullptr, 0.f, WICBitmapPaletteTypeMedianCut); } pFrame->Release(); if (FAILED(hr)) return hr; hr = pRT->CreateBitmapFromWicBitmap(pConverter, nullptr, ppBitmap); pConverter->Release(); return hr;}// Инициализация Direct2D и демонстрация методовHRESULT InitD2D(HWND hWnd){ HRESULT hr; // 1. Фабрика Direct2D hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &g_pD2DFactory); if (FAILED(hr)) { MessageBox(hWnd, L"Failed to create D2D factory", L"Error", MB_ICONERROR); return hr; } // 2. WIC-фабрика hr = CoCreateInstance( CLSID_WICImagingFactory, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&g_pWICFactory)); if (FAILED(hr)) { MessageBox(hWnd, L"Failed to create WIC factory", L"Error", MB_ICONERROR); return hr; } // 3. Создание HWND render target RECT rc; GetClientRect(hWnd, &rc); D2D1_SIZE_U size = D2D1::SizeU(rc.right - rc.left, rc.bottom - rc.top); hr = g_pD2DFactory->CreateHwndRenderTarget( D2D1::RenderTargetProperties(), D2D1::HwndRenderTargetProperties(hWnd, size), &g_pRT); if (FAILED(hr)) { MessageBox(hWnd, L"Failed to create render target", L"Error", MB_ICONERROR); return hr; } // 4. Загрузка текстуры hr = LoadBitmapFromFile(g_pRT, L"image.png", &g_pBitmap); if (FAILED(hr)) { wchar_t buf[256]; wsprintf(buf, L"Failed to load image.png\nHRESULT: 0x%08X", hr); MessageBox(hWnd, buf, L"Error", MB_ICONERROR); return hr; } // ------------------- GetSize / GetPixelSize / GetPixelFormat / GetDpi ------------------ D2D1_SIZE_F dipSize = g_pBitmap->GetSize(); D2D1_SIZE_U pxSize = g_pBitmap->GetPixelSize(); D2D1_PIXEL_FORMAT fmt = g_pBitmap->GetPixelFormat(); FLOAT dpiX, dpiY; g_pBitmap->GetDpi(&dpiX, &dpiY); char msg[256]; sprintf_s(msg, "Size (DIP): %.1f x %.1f\nPixel size: %u x %u\nFormat: %u, Alpha: %u\nDPI: %.0f x %.0f", dipSize.width, dipSize.height, pxSize.width, pxSize.height, fmt.format, fmt.alphaMode, dpiX, dpiY); MessageBoxA(hWnd, msg, "Bitmap Info", MB_OK); // ------------------- CopyFromMemory ------------------ UINT w = pxSize.width, h = pxSize.height; // Полупрозрачный красный квадрат 100x100 BYTE* redData = new BYTE[w * h * 4]; for (UINT y = 0; y < h; ++y) for (UINT x = 0; x < w; ++x) { UINT idx = (y * w + x) * 4; redData[idx + 0] = 0; // B redData[idx + 1] = 0; // G redData[idx + 2] = 255; // R redData[idx + 3] = 128; // A } D2D1_RECT_U dstRect = D2D1::RectU(0, 0, 100, 100); g_pBitmap->CopyFromMemory(&dstRect, redData, w * 4); delete[] redData; // ------------------- CopyFromBitmap ------------------ D2D1_BITMAP_PROPERTIES bmpProps = D2D1::BitmapProperties(g_pBitmap->GetPixelFormat()); g_pRT->CreateBitmap(pxSize, bmpProps, &g_pCopyBitmap); g_pCopyBitmap->CopyFromBitmap(nullptr, g_pBitmap, nullptr); // ------------------- CopyFromRenderTarget ------------------ ID2D1BitmapRenderTarget* pBmpRT = nullptr; g_pRT->CreateCompatibleRenderTarget(D2D1::SizeF((FLOAT)w, (FLOAT)h), &pBmpRT); pBmpRT->BeginDraw(); pBmpRT->Clear(D2D1::ColorF(D2D1::ColorF::LimeGreen)); pBmpRT->EndDraw(); D2D1_POINT_2U destPoint = { 0, 100 }; D2D1_RECT_U srcRect = D2D1::RectU(0, 0, 100, 100); g_pBitmap->CopyFromRenderTarget(&destPoint, pBmpRT, &srcRect); pBmpRT->Release(); // ------------------- Map / Unmap (ID2D1Bitmap1) ------------------ ID2D1Bitmap1* pBitmap1 = nullptr; hr = g_pBitmap->QueryInterface(IID_PPV_ARGS(&pBitmap1)); if (SUCCEEDED(hr)) { D2D1_MAPPED_RECT mapped; hr = pBitmap1->Map(D2D1_MAP_OPTIONS_WRITE, &mapped); if (SUCCEEDED(hr)) { // Заливаем верхний правый квадрат (100,0)-(200,100) for (UINT y = 0; y < 100; ++y) { BYTE* pixel = mapped.bits + y * mapped.pitch + 100 * 4; for (UINT x = 100; x < 200; ++x) { pixel[0] = 255; // B pixel[1] = 0; // G pixel[2] = 0; // R pixel[3] = 255; // A pixel += 4; } } pBitmap1->Unmap(); } pBitmap1->Release(); } // ------------------- CreateBitmapBrush ------------------ D2D1_BITMAP_BRUSH_PROPERTIES brushProps = D2D1::BitmapBrushProperties( D2D1_EXTEND_MODE_WRAP, D2D1_EXTEND_MODE_WRAP, D2D1_BITMAP_INTERPOLATION_MODE_LINEAR); g_pRT->CreateBitmapBrush(g_pCopyBitmap, brushProps, &g_pBrush); D2D1_MATRIX_3X2_F translate = D2D1::Matrix3x2F::Translation(50.0f, 50.0f); g_pBrush->SetTransform(translate); return S_OK;}void Render(){ g_pRT->BeginDraw(); g_pRT->Clear(D2D1::ColorF(D2D1::ColorF::White)); // Модифицированный битмап (с тремя цветными квадратами) g_pRT->DrawBitmap(g_pBitmap, D2D1::RectF(10, 10, 210, 210)); // Копия g_pRT->DrawBitmap(g_pCopyBitmap, D2D1::RectF(220, 10, 420, 210)); // Эллипс, заполненный текстурной кистью D2D1_ELLIPSE ellipse = D2D1::Ellipse(D2D1::Point2F(350.0f, 350.0f), 150.0f, 100.0f); g_pRT->FillEllipse(ellipse, g_pBrush); g_pRT->EndDraw();}void Cleanup(){ if (g_pBrush) g_pBrush->Release(); if (g_pCopyBitmap) g_pCopyBitmap->Release(); if (g_pBitmap) g_pBitmap->Release(); if (g_pRT) g_pRT->Release(); if (g_pWICFactory) g_pWICFactory->Release(); if (g_pD2DFactory) g_pD2DFactory->Release();}LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam){ switch (msg) { case WM_PAINT: Render(); ValidateRect(hWnd, nullptr); return 0; case WM_DESTROY: Cleanup(); PostQuitMessage(0); return 0; } return DefWindowProc(hWnd, msg, wParam, lParam);}int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR, int nCmdShow){ // Инициализация COM для WIC CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); WNDCLASS wc = {}; wc.lpfnWndProc = WndProc; wc.hInstance = hInstance; wc.lpszClassName = L"BitmapDemo"; RegisterClass(&wc); g_hwnd = CreateWindow( wc.lpszClassName, L"ID2D1Bitmap Methods Demo", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 600, 500, nullptr, nullptr, hInstance, nullptr); HRESULT hr = InitD2D(g_hwnd); if (FAILED(hr)) { wchar_t buf[128]; wsprintf(buf, L"Initialization failed! HRESULT: 0x%08X", hr); MessageBox(g_hwnd, buf, L"Fatal Error", MB_ICONERROR); CoUninitialize(); return 1; } ShowWindow(g_hwnd, nCmdShow); UpdateWindow(g_hwnd); MSG msg; while (GetMessage(&msg, nullptr, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } CoUninitialize(); return (int)msg.wParam;}
На этом всё 😀 Да, статья получилась небольшой, но дальше будут спрайты — там будет о чём рассказать. К слову, когда требуется обработать много объектов и наложить на них текстуры, спрайты оказываются в разы производительнее.
На этом всё 😀 Да, статья получилась небольшой, но дальше будут спрайты — там будет о чём рассказать. К слову, когда требуется обработать много объектов и наложить на них текстуры, спрайты оказываются в разы производительнее.
При желании материально подержать перевод и структурирование информации — средства можете отправить через сбор в ЮМани .
ссылка на оригинал статьи https://habr.com/ru/articles/1030942/