Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to draw in C++ with Cairo to a window using GDI (instead of creating an image)?

Tags:

c++

c

cairo

I managed to find this code snippet and compile it with Cairo:

#define LIBCAIRO_EXPORTS
#include <cairo/cairo.h>
#include <cairo/cairo-win32.h>

int main(int argc, char** argv)
{
    cairo_surface_t *surface;
    cairo_t *cr;
    surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 240, 80);
    cr = cairo_create (surface);

    cairo_select_font_face (cr, "serif", CAIRO_FONT_SLANT_NORMAL,
                                         CAIRO_FONT_WEIGHT_BOLD);
    cairo_set_font_size (cr, 32.0);
    cairo_set_source_rgb (cr, 0.0, 0.0, 1.0);
    cairo_move_to (cr, 10.0, 50.0);
    cairo_show_text (cr, "Hello, World");
    cairo_destroy (cr);
    cairo_surface_write_to_png (surface, "hello.png");
    cairo_surface_destroy (surface);

    return 0;
}

As you can see, it create an image with the text "Hello World" and saved it on the drive. How do I go about creating a win32 surface and draw to a window instead?

I am failing to use: cairo_win32_surface_create

It requires a hdc and I don't know what that is. I tried to read a few tutorials but none seems to walk you through printing to a new window.

I found this link: http://windrealm.org/cairo-gdi/

It has a working demo but it uses int WINAPI WinMain. I don't wish to use that.

like image 983
AturSams Avatar asked Apr 16 '13 21:04

AturSams


2 Answers

There are several ways to get a handle to a device context in Windows. For instance it is typical that the WM_PAINT handler will call BeginPaint to acquire a device context, update the contents to the window and then call EndPaint. In cases where BeginPaint and EndPaint cannot be used (e.g. outside of a WM_PAINT message) you can use GetDC update the window and then call ReleaseDC.

The device contexts returned by BeginPaint and GetDC allow you to draw directly to a window. Sometimes however you want to draw directly to a bitmap. In this case you would call CreateCompatibleDC, select the bitmap you want to draw to, do your drawing and then call DeleteDC.

In your case what you're looking for is something like the following:

HDC dc = GetDC(windowHandle);
cairo_win32_surface_create(dc);
ReleaseDC(windowHandle, dc);

If you do not want to create a window at this stage (since you are simply saving an image) you can probably get away with using the desktop window to acquire a device context.

HWND windowHandle = GetDesktopWindow();
HDC dc = GetDC(windowHandle);
cairo_win32_surface_create(dc);
ReleaseDC(windowHandle, dc);

There are other calls that can be used to acquire or create a device context. You can find a list of these and related functions here

Creating and using a window is a bit more involved so I'm going to provide you with the basic steps and some sample code that you can use to play around with. To create and use a window...

  • Register a window class with RegisterClass
  • Create the window with CreateWindow or CreateWindowEx
  • Process messages with a message pump by calling GetMessage, TranslateMessage and DispatchMessage

Additionally you will have to implement a function to handle processing of window messages such as WM_PAINT.

NOTE: The following code is UNTESTED but should be correct.

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    if(message == WM_PAINT)
    {
        HDC dc;
        PAINTSTRUCT ps;
        dc = BeginPaint(hwnd, &ps);

        // do your drawing here

        EndPaint(hwnd, &ps);
    }

    return 0;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PSTR szCmdLine, int iCmdShow)
{
    static TCHAR szClassName[] = TEXT("DrawSurfaceClass");
    HWND         hwnd;
    MSG          msg;
    WNDCLASS     wndclass;

    ///////////////////////////////////////////////////////////
    //  Register a window "class"
    ///////////////////////////////////////////////////////////
    wndclass.style         = CS_HREDRAW | CS_VREDRAW;
    wndclass.lpfnWndProc   = WndProc;
    wndclass.cbClsExtra    = 0;
    wndclass.cbWndExtra    = 0;
    wndclass.hInstance     = hInstance;
    wndclass.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
    wndclass.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wndclass.hbrBackground =(HBRUSH)COLOR_WINDOW;
    wndclass.lpszMenuName  = NULL;
    wndclass.lpszClassName = szClassName;

    if(!RegisterClass(&wndclass))
    {
        //  error
        return 1;
    }

    ///////////////////////////////////////////////////////////
    //  Create the window and display it (if iCmdShow says so)
    ///////////////////////////////////////////////////////////
    hwnd = CreateWindow(
        szAppName,
        TEXT("Draw Surface"),
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT,
        CW_USEDEFAULT, CW_USEDEFAULT,
        NULL, NULL, hInstance, NULL);

    ShowWindow(hwnd, iCmdShow);
    UpdateWindow(hwnd);


    ///////////////////////////////////////////////////////////
    //  Run the message pump so the window proc recieves events
    ///////////////////////////////////////////////////////////
    while(GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return msg.wParam;
}
like image 51
Captain Obvlious Avatar answered Sep 20 '22 23:09

Captain Obvlious


I found the easiest way for beginners to open a window and draw some lines into it, is downloading and setting up the gtk+ and using ZetCode's tutorials.

You will probably need to download GTK from here: http://gtk.hexchat.org/

Setting up Gtk+: Using GTK+ in Visual C++

Cairo tutorials: http://zetcode.com/gfx/cairo/

Remember to include all the GTK's dll files in the directory where your compiled executable is.

like image 24
AturSams Avatar answered Sep 17 '22 23:09

AturSams