Здравствуйте! После разбора WIC начинается работа с данными, для которой он как раз и пригодится.

Сразу скажу, что спрайты будут в следующей статье, так как понадобится немного залезть в Direct3D, рассказать про DXGI-цепочки и всё что будет важным для понимания этих вещей. Поэтому эта статья не очень длинная, но касается текстур - а текстуры, к слову, будут использоваться спрайтами.

Кстати, о разнице между спрайтами и текстурой:

Текстура - после того как вы декодировали файл с изображением и конвертировали его в нужный пиксельный формат, вы можете передать данные в видеопамять (такие данные в видеопамяти - это текстура).

Спрайт - ссылается на текстуру и содержит описание того, как её отрисовать, и т.п. То есть если нужно отрисовать текстуру по-разному, вы прибегаете к спрайтам.

Просто напоминаю, что пиксельные форматы описывают хранение одного пикселя. Вот весь их список в Direct2D.

Ну а теперь - процесс получения текстуры. Как я говорил ранее, необходимо декодировать файл с изображением и конвертировать его в один из пиксельных форматов. Так как это было в статье #8, здесь будет не полный код:

1. Получаем доступ к фабрике WIC

IWICImagingFactory* 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;

// Загрузка битмапа через WIC
HRESULT 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;
}

На этом всё :D Да, статья получилась небольшой, но дальше будут спрайты — там будет о чём рассказать. К слову, когда требуется обработать много объектов и наложить на них текстуры, спрайты оказываются в разы производительнее.

На этом всё :D Да, статья получилась небольшой, но дальше будут спрайты - там будет о чём рассказать. К слову, когда требуется обработать много объектов и наложить на них текстуры, спрайты оказываются в разы производительнее.

При желании материально поддержать перевод и структурирование информации — средства можете отправить через сбор в ЮМани .

Комментарии (0)