Как рисовать векторную графику в Windows API?

177
06 января 2019, 00:40

Чем можно рисовать векторное изображение в Windows API? Особенно интересует SVG в GDI+. Есть ли смысл разбирать формат? Хотелось бы получить что-то похожее на то что сделано в C# XAML (не используя сторонние программы)

<Path Stroke="Black" Fill="Gray"
  Data="M 10,100 C 10,300 300,-200 300,100"/>
Answer 1

GDI+ не поддерживает работу с SVG. Также в Windows нет другого стандартного средства для работы с SVG, доступного во всех поддерживаемых версиях ОС. Можно предложить несколько обходных путей.

Вариант 1 - Парсинг XML

Если вам нужен только небольшой набор элементов из SVG, можно написать свой парсер, вытянуть из SVG данные и отрисовать объекты вручную. Поскольку SVG основан на XML, можно использовать любую библиотеку для работы с XML, например MSXML. Пример чтения данных есть здесь.

Вариант 2 - Использование метафайлов

GDI+ поддерживает метафайлы (EMF/WMF), стандартный для Windows формат векторной графики. Он поддерживает далеко не все, что есть в SVG, но с базовыми фигурами справляется. Можно преобразовать SVG в метафайл с помощью сторонней программы или онлайн-сервиса, и использовать в программе уже его.

Например, создадим такой простейший SVG:

<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.0">
<circle cx="200" cy="200" r="150" stroke="red" fill="green" stroke-width="10"/>
</svg>

Перегоним его в WMF с помощью онлайн-сервиса, получим такой файл (он по какой-то причине некорректно отображается в Paint, но он не поврежден; при отрисовке с помощью кода ниже он нормально отображается).

Напишем код для отрисовки метафайла:

#include <Windows.h>
#include <gdiplus.h>
#pragma comment(lib, "gdiplus.lib")
const int W_IMAGE = 6000; //для метафайлов почему-то приходится задавать очень большую ширину и высоту, иначе они не попадают в область отображения.
const int H_IMAGE = 6000;
Gdiplus::Metafile * wmf = NULL;
//...
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_CREATE:
        {
        //загрузка изображения
        wmf = new Gdiplus::Metafile(L"D:\\images\\circle.wmf");
        }
        break;
    case WM_PAINT:
        {
            //отрисовка изображения
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            Gdiplus::Graphics g(hdc);            
            if(wmf != NULL){
                g.DrawImage(wmf,0, 0, 0, 0, W_IMAGE, H_IMAGE, Gdiplus::Unit::UnitPixel);
            }
            EndPaint(hWnd, &ps);
        }
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

Результат:

Вариант 3 - Преобразование в растровое изображение

Можно преобразовать SVG в Bitmap (в отличие от предыдущего способа, это легко сделать на лету, программно) и нарисовать его. Например так, с помощью MSHTML и OLE (требует наличия IE 11):

#include <string>
#include <iostream>
#include <fstream>
#include <sstream>
#include <Windows.h>
#include <oleidl.h>
#include <Mshtml.h>
#include <gdiplus.h>
#pragma comment(lib, "gdiplus.lib")
const int W_IMAGE = 600;
const int H_IMAGE = 600;
//Глобальные переменные
HINSTANCE hInst;                               // текущий экземпляр
WCHAR szTitle[100] = L"Window";                  // текст строки заголовка
WCHAR szWindowClass[100] = L"MyClass";           // имя класса главного окна
Gdiplus::Bitmap* svgbitmap = NULL;             // объект изображения
//объявления функций, включенных в этот модуль кода:
ATOM                MyRegisterClass(HINSTANCE hInstance);
BOOL                InitInstance(HINSTANCE, int);
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);
void DocumentWrite(IHTMLDocument2* pdoc, const WCHAR* content) {
    HRESULT hr = 0;
    VARIANT *param;
    BSTR bstr = SysAllocString(content);
    // Creates a new one-dimensional array
    SAFEARRAY *psaStrings = SafeArrayCreateVector(VT_VARIANT, 0, 1);
    if (psaStrings == NULL) {
        goto cleanup;
    }
    hr = SafeArrayAccessData(psaStrings, (LPVOID*)&param);
    param->vt = VT_BSTR;
    param->bstrVal = bstr;
    hr = SafeArrayUnaccessData(psaStrings);
    hr = pdoc->write(psaStrings);
cleanup:
    // SafeArrayDestroy calls SysFreeString for each BSTR
    if (psaStrings != NULL) {
        SafeArrayDestroy(psaStrings);
    }
}
// *** Преобразование SVG в Bitmap ***
Gdiplus::Bitmap* SvgToBitmap(const WCHAR* svgcontent, int w_image, int h_image) {
    const int HIMETRIC_INCH = 2540; 
    SIZEL sz = { 0 };
    RECTL rcClient = { 0 };
    HDC screendc;
    HDC hdc;
    WCHAR svghtml1[] = L"<html><head><meta http-equiv=\"X-UA-Compatible\" content=\"IE=11\" /></head><body>";
    WCHAR svghtml2[] = L"</body></html>";
    Gdiplus::Bitmap* bmp = NULL;
    Gdiplus::Graphics* g = NULL;
    IHTMLDocument2* d2 = NULL;
    IOleObject* pObj = NULL;
    IViewObject* pView = NULL;
    BOOL b = SystemParametersInfo(SPI_GETWORKAREA, 0, &rcClient, 0);
    if (b == FALSE) { rcClient.bottom = 480; rcClient.right = 640; }
    int width = (int)(rcClient.right - rcClient.left);
    int height = (int)(rcClient.bottom - rcClient.top);

    //создание документа
    HRESULT hr = CoCreateInstance(CLSID_HTMLDocument, NULL, CLSCTX_INPROC_SERVER, IID_IHTMLDocument2, (LPVOID*)&d2);
    if (FAILED(hr)) {
        MessageBox(NULL, L"CoCreateInstance failed", NULL, MB_OK|MB_ICONERROR); goto End;
    }
    hr = d2->QueryInterface(IID_IOleObject, (LPVOID*)&pObj);
    if (FAILED(hr)) { MessageBox(NULL, L"QueryInterface failed", 0, 0); goto End; }
    //установка размера документа
    screendc = GetDC(NULL);
    sz.cx = (UINT)MulDiv(width, HIMETRIC_INCH, GetDeviceCaps(screendc, LOGPIXELSX));
    sz.cy = (UINT)MulDiv(height, HIMETRIC_INCH, GetDeviceCaps(screendc, LOGPIXELSY));
    ReleaseDC(NULL,screendc);
    hr = pObj->SetExtent(DVASPECT_CONTENT,&sz);
    if (FAILED(hr)) { MessageBox(NULL, L"SetExtent failed", NULL, MB_OK | MB_ICONERROR); goto End; }
    //запись SVG в документ
    DocumentWrite(d2, svghtml1);
    DocumentWrite(d2, svgcontent);
    DocumentWrite(d2, svghtml2);
    d2->close();    
    //преобразование в Bitmap
    hr = d2->QueryInterface(IID_IViewObject, (LPVOID*)&pView);
    if (FAILED(hr)) { MessageBox(NULL, L"Cannot get IViewObject!", NULL, MB_OK | MB_ICONERROR); goto End; }
    bmp = new Gdiplus::Bitmap(w_image, h_image);
    g = Gdiplus::Graphics::FromImage(bmp);
    hdc = g->GetHDC();
    hr = pView->Draw(DVASPECT_CONTENT,-1, NULL, NULL, NULL, hdc, & rcClient, NULL,NULL, 0); 
    g->ReleaseHDC(hdc); 
    if (FAILED(hr)) MessageBox(NULL, L"Draw failed", NULL, MB_OK | MB_ICONERROR);
End:
    if( g != NULL) delete g;
    if (d2 != NULL)d2->Release();
    if (pObj != NULL)pObj->Release();
    if (pView != NULL)pView->Release(); 
    return bmp;
}
// *** GUI ***
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPWSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);
    CoInitialize(NULL);    
    Gdiplus::GdiplusStartupInput gdiplusStartupInput;
    ULONG_PTR gdiplusToken;
    Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
    MyRegisterClass(hInstance);
    // Выполнить инициализацию приложения
    if (!InitInstance (hInstance, nCmdShow))
    {
        return FALSE;
    }    
    MSG msg;
    while (GetMessage(&msg, nullptr, 0, 0))
    {       
            TranslateMessage(&msg);
            DispatchMessage(&msg);        
    }
    return (int) msg.wParam;
}

ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEXW wcex = { 0 };
    wcex.cbSize = sizeof(WNDCLASSEX);
    wcex.style          = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc    = WndProc;
    wcex.cbClsExtra     = 0;
    wcex.cbWndExtra     = 0;
    wcex.hInstance      = hInstance;   
    wcex.hCursor        = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);    
    wcex.lpszClassName  = szWindowClass;    
    return RegisterClassExW(&wcex);
}

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   hInst = hInstance; // Сохранить маркер экземпляра в глобальной переменной
   HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);
   if (!hWnd)
   {
      return FALSE;
   }
   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);
   return TRUE;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_CREATE:
        {
        //загрузка изображения
        std::wifstream fStream(L"c:\\web\\image.svg");      
        std::wstringstream wstrStream;
        wstrStream << fStream.rdbuf();
        std::wstring s = wstrStream.str();      
        svgbitmap = SvgToBitmap(s.c_str(), W_IMAGE, H_IMAGE);
        }
        break;
    case WM_PAINT:
        {
            //отрисовка изображения
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            Gdiplus::Graphics g(hdc);
            Gdiplus::Status res = g.DrawImage(svgbitmap, 0, 0, 0, 0, W_IMAGE, H_IMAGE, Gdiplus::Unit::UnitPixel);
            EndPaint(hWnd, &ps);
        }
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}
READ ALSO
Изменения внесённые на одной из веток проекта сразу переносятся на все другие ветки

Изменения внесённые на одной из веток проекта сразу переносятся на все другие ветки

При работе с проектом в intellij idea, после внесения каких либо изменений (изменение текста кода или создание файла) эти изменения сразу передаются...

175
Ошибка при подключении к Mysql со spring JdbcTemplate

Ошибка при подключении к Mysql со spring JdbcTemplate

Имеется ошибка, сервер работает нормально, но последнее время проскакивает эта ошибка очень часто, источников не нашел

181
Зависимые от друг друга Spinner -ы

Зависимые от друг друга Spinner -ы

Есть 2 Spinner-aИ допустим пункты (item) 1,2,3,4,5

139
Android не показывает данные от Arduino

Android не показывает данные от Arduino

Из ардуино идут данные датчиковПример - приложение PhysicaloidTest работает и показывает что данные поступают и они верные

171