Search code examples
c++cairogtkmmgtkmm3

Preserve DrawingArea 'image' on draw signal


I am trying to make simple square where you could paint with mouse. Problem is, whenever draw signal is happens, cairo surface seems to be cleared entirely. I understand this because after first queue_draw() white background is gone and I see my GTK theme color (which is grey).

I thought I could save surface or context, but you can't just create empty surface in cairo, and I can't create it using this->get_window()->create_cairo_surface() (where this is object of class inherited from Gtk::DrawingArea) because when constructor is called, widget isn't attached to any window yet, so it is a null pointer. I mean, I could create some public function called you_are_added_to_window_create_cairo_surface() but I'd really like not to do this.

So I really don't know what to do and what I don't understand about cairo.

How do I preserve, or save 'canvas' current state, so whatever is actually being drawn is just applied on existing drawing?

Here is callback function of my class:

bool MyDrawingArea::on_draw(const Cairo::RefPtr<Cairo::Context> & cr) {
    /* clear and fill background with white in the beginning */
    if (first_draw) {
        cr->save();
        cr->set_source_rgb(255.0, 255.0, 255.0);
        cr->paint();
        cr->restore();

        first_draw = false;
    }

    cr->save();

    cr->set_source_rgb(0.0, 0.0, 0.0);

    cr->begin_new_path();
    while (!dots_queue.empty()) {
        auto dot = dots_queue.front();
        cr->line_to(dot.first, dot.second);
        dots_queue.pop();
    }
    cr->close_path();
    cr->stroke();

    cr->restore();

    return false;
}

Solution

  • So while storing all actions works, it's really not ok if you are trying to have your program save your drawings, you will have to use second surface to save everything on.

    My solution combines both answers of Uli Schlachter.

    First, I have structure, in which I store last drawing action, since last Button Press, and until Button Release. This allows me to show things such as lines in real time, while keeping canvas clean of it.

    Second, I store everything drawn on canvas on a surface, which is created like that:

    // this - is object of class, derived from DrawingArea
    auto allocation = this->get_allocation(); 
    
    this->surface = Cairo::ImageSurface::create(
        Cairo::Format::FORMAT_ARGB32,
        allocation.get_width(),
        allocation.get_height()
    );
    

    Then, on each draw signal, I restore it like that:

    cr->save();
    cr->set_source(surface, 0.0, 0.0);
    cr->paint();
    cr->restore();
    

    Whenever I want to save surface, i.e. apply drawing on to canvas, I do the following:

    Cairo::RefPtr<Cairo::Context> t_context = Cairo::Context::create(surface);
    t_context->set_source(cr->get_target(), -allocation.get_x(), -allocation.get_y());
    t_context->paint();
    

    Here is the important moment. Without adjusting for the allocation coordinates, your canvas is going to slide away on each surface save and restore.

    With that, I can easily keep my drawings on canvas, load canvas from file (because I am using ImageSurface), or save it to the file.