Direct 2D #9 О текстурах

от автора

Здравствуйте! После разбора 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);

После этого текстура находится в видеопамяти. Теперь о том, что можно сделать с битмапом:

  1. D2D1_SIZE_F GetSize() — возвращает размеры битмапа с учётом DPI.

  2. D2D1_SIZE_U GetPixelSize() — возвращает размеры без учёта DPI

  3. D2D1_PIXEL_FORMAT GetPixelFormat() — возвращает формат пикселей.

  4. void GetDpi(FLOAT* dpiX, FLOAT* dpiY) — возвращает значения DPI, с которыми был создан битмап.

  5. HRESULT CopyFromMemory(const D2D1_RECT_U* dstRect, const void* srcData, UINT32 pitch) — копирует пиксели из памяти в битмап. dstRect — область в битмапе, куда копируем (если nullptr, то во весь битмап). srcData — указатель на данные в системной памяти, pitch — ширина строки данных в байтах.

  6. HRESULT CopyFromBitmap(const D2D1_POINT_2U* destPoint, ID2D1Bitmap* bitmap, const D2D1_RECT_U* srcRect) — копирует пиксели из другого битмапа в текущий. Аргументы аналогичны CopyFromMemory.

  7. HRESULT CopyFromRenderTarget(const D2D1_POINT_2U* destPoint, ID2D1RenderTarget* renderTarget, const D2D1_RECT_U* srcRect) — копирует пиксели с рендер-таргета. Аргументы аналогичны CopyFromBitmap.

  8. HRESULT CreateBitmapBrush(ID2D1Bitmap* bitmap, const D2D1_BITMAP_BRUSH_PROPERTIES* bitmapBrushProperties, const D2D1_BRUSH_PROPERTIES* brushProperties, ID2D1BitmapBrush** bitmapBrush) — создаёт кисть из текстуры. bitmapBrushProperties — свойства кисти (можно передать nullptr для значений по умолчанию), brushProperties — общие свойства (непрозрачность, матрица трансформации и т.п.).

Отрисовку разберём, когда дойдём до этого пункта. К слову, начиная с Windows 8 существует также интерфейс ID2D1Bitmap1, в котором есть пара дополнительных функций:

  1. HRESULT Map(D2D1_MAP_OPTIONS options, D2D1_MAPPED_RECT* mappedRect) — предоставляет прямой доступ к данным битмапа в видеопамяти. Пока битмап замаплен, его нельзя использовать для рисования. options — опции: D2D1_MAP_OPTIONS_READ (только чтение), D2D1_MAP_OPTIONS_WRITE (только запись), а также их комбинация.

  2. HRESULT Unmap() — завершает доступ, начатый Map.

Теперь о отрисовке:

  1. void DrawBitmap( ID2D1Bitmap* bitmap, const D2D1_RECT_F& destinationRectangle, FLOAT opacity, D2D1_BITMAP_INTERPOLATION_MODE interpolationMode, const D2D1_RECT_F* sourceRectangle = nullptr ) — упрощённая версия на ID2D1RenderTarget:

    1. bitmap — рисуемый битмап

    2. destinationRectangle — координаты где рисовать

    3. opacity — непрозрачность от 0.0 (полностью прозрачно) до 1.0 (непрозрачно)

    4. interpolationMode — режим интерполяции: LINEAR (сглаживание) или NEAREST_NEIGHBOR (без сглаживания)

    5. sourceRectangle — область исходного битмапа (в пикселях) для рисования. nullptr — рисуется целиком .

  2. 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:

    1. bitmap — битмап

    2. destinationRectangle — координаты

    3. opacity — непрозрачность

    4. interpolationMode — D2D1_INTERPOLATION_MODE (аналог предыдущего, но включает дополнительные опции, например, ANISOTROPIC)

    5. sourceRectangle — как раньше

    6. 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/