OpenFPGA/libs/EXTERNAL/libezgl/src/canvas.cpp

316 lines
9.6 KiB
C++

/*
* Copyright 2019 University of Toronto
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Authors: Mario Badr, Sameh Attia and Tanner Young-Schultz
*/
#include "ezgl/canvas.hpp"
#include "ezgl/graphics.hpp"
#include <gtk/gtk.h>
#include <cassert>
#include <cmath>
#include <functional>
namespace ezgl {
static cairo_surface_t *create_surface(GtkWidget *widget)
{
GdkWindow *parent_window = gtk_widget_get_window(widget);
int const width = gtk_widget_get_allocated_width(widget);
int const height = gtk_widget_get_allocated_height(widget);
// Cairo image surfaces are more efficient than normal Cairo surfaces
// However, you cannot use X11 functions to draw on image surfaces
#ifdef EZGL_USE_X11
return gdk_window_create_similar_surface(parent_window, CAIRO_CONTENT_COLOR_ALPHA, width, height);
#else
return gdk_window_create_similar_image_surface(
parent_window, CAIRO_FORMAT_ARGB32, width, height, 0);
#endif
}
static cairo_t *create_context(cairo_surface_t *p_surface)
{
cairo_t *context = cairo_create(p_surface);
// Set the antialiasing mode of the rasterizer used for drawing shapes
// Set to CAIRO_ANTIALIAS_NONE for maximum speed
// See https://www.cairographics.org/manual/cairo-cairo-t.html#cairo-antialias-t
cairo_set_antialias(context, CAIRO_ANTIALIAS_NONE);
return context;
}
bool canvas::print_pdf(const char *file_name, int output_width, int output_height)
{
cairo_surface_t *pdf_surface;
cairo_t *context;
int surface_width = 0;
int surface_height = 0;
// create pdf surface based on canvas size
if(output_width == 0 && output_height == 0){
surface_width = gtk_widget_get_allocated_width(m_drawing_area);
surface_height = gtk_widget_get_allocated_height(m_drawing_area);
}else{
surface_width = output_width;
surface_height = output_height;
}
pdf_surface = cairo_pdf_surface_create(file_name, surface_width, surface_height);
if(pdf_surface == NULL)
return false; // failed to create due to errors such as out of memory
context = create_context(pdf_surface);
// draw on the newly created pdf surface & context
cairo_set_source_rgb(context, m_background_color.red / 255.0, m_background_color.green / 255.0,
m_background_color.blue / 255.0);
cairo_paint(context);
using namespace std::placeholders;
camera pdf_cam = m_camera;
pdf_cam.update_widget(surface_width, surface_height);
renderer g(context, std::bind(&camera::world_to_screen, pdf_cam, _1), &pdf_cam, pdf_surface);
m_draw_callback(&g);
// free surface & context
cairo_surface_destroy(pdf_surface);
cairo_destroy(context);
return true;
}
bool canvas::print_svg(const char *file_name, int output_width, int output_height)
{
cairo_surface_t *svg_surface;
cairo_t *context;
int surface_width = 0;
int surface_height = 0;
// create pdf surface based on canvas size
if(output_width == 0 && output_height == 0){
surface_width = gtk_widget_get_allocated_width(m_drawing_area);
surface_height = gtk_widget_get_allocated_height(m_drawing_area);
}else{
surface_width = output_width;
surface_height = output_height;
}
svg_surface = cairo_svg_surface_create(file_name, surface_width, surface_height);
if(svg_surface == NULL)
return false; // failed to create due to errors such as out of memory
context = create_context(svg_surface);
// draw on the newly created svg surface & context
cairo_set_source_rgb(context, m_background_color.red / 255.0, m_background_color.green / 255.0,
m_background_color.blue / 255.0);
cairo_paint(context);
using namespace std::placeholders;
camera svg_cam = m_camera;
svg_cam.update_widget(surface_width, surface_height);
renderer g(context, std::bind(&camera::world_to_screen, svg_cam, _1), &svg_cam, svg_surface);
m_draw_callback(&g);
// free surface & context
cairo_surface_destroy(svg_surface);
cairo_destroy(context);
return true;
}
bool canvas::print_png(const char *file_name, int output_width, int output_height)
{
cairo_surface_t *png_surface;
cairo_t *context;
int surface_width = 0;
int surface_height = 0;
// create pdf surface based on canvas size
if(output_width == 0 && output_height == 0){
surface_width = gtk_widget_get_allocated_width(m_drawing_area);
surface_height = gtk_widget_get_allocated_height(m_drawing_area);
}else{
surface_width = output_width;
surface_height = output_height;
}
png_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, surface_width, surface_height);
if(png_surface == NULL)
return false; // failed to create due to errors such as out of memory
context = create_context(png_surface);
// draw on the newly created png surface & context
cairo_set_source_rgb(context, m_background_color.red / 255.0, m_background_color.green / 255.0,
m_background_color.blue / 255.0);
cairo_paint(context);
using namespace std::placeholders;
camera png_cam = m_camera;
png_cam.update_widget(surface_width, surface_height);
renderer g(context, std::bind(&camera::world_to_screen, png_cam, _1), &png_cam, png_surface);
m_draw_callback(&g);
// create png output file
cairo_surface_write_to_png(png_surface, file_name);
// free surface & context
cairo_surface_destroy(png_surface);
cairo_destroy(context);
return true;
}
gboolean canvas::configure_event(GtkWidget *widget, GdkEventConfigure *, gpointer data)
{
// User data should have been set during the signal connection.
g_return_val_if_fail(data != nullptr, FALSE);
auto ezgl_canvas = static_cast<canvas *>(data);
auto &p_surface = ezgl_canvas->m_surface;
auto &p_context = ezgl_canvas->m_context;
if(p_surface != nullptr) {
cairo_surface_destroy(p_surface);
}
if(p_context != nullptr) {
cairo_destroy(p_context);
}
// Something has changed, recreate the surface.
p_surface = create_surface(widget);
// Recreate the context
p_context = create_context(p_surface);
// The camera needs to be updated before we start drawing again.
ezgl_canvas->m_camera.update_widget(ezgl_canvas->width(), ezgl_canvas->height());
// Draw to the newly created surface.
ezgl_canvas->redraw();
// Update the animation renderer
if(ezgl_canvas->m_animation_renderer != nullptr)
ezgl_canvas->m_animation_renderer->update_renderer(p_context, p_surface);
g_info("canvas::configure_event has been handled.");
return TRUE; // the configure event was handled
}
gboolean canvas::draw_surface(GtkWidget *, cairo_t *context, gpointer data)
{
// Assume context and data are non-null.
auto &p_surface = static_cast<canvas *>(data)->m_surface;
// Assume surface is non-null.
cairo_set_source_surface(context, p_surface, 0, 0);
cairo_paint(context);
return FALSE;
}
canvas::canvas(std::string canvas_id,
draw_canvas_fn draw_callback,
rectangle coordinate_system,
color background_color)
: m_canvas_id(std::move(canvas_id))
, m_draw_callback(draw_callback)
, m_camera(coordinate_system)
, m_background_color(background_color)
{
}
canvas::~canvas()
{
if(m_surface != nullptr) {
cairo_surface_destroy(m_surface);
}
if(m_context != nullptr) {
cairo_destroy(m_context);
}
if(m_animation_renderer != nullptr) {
delete m_animation_renderer;
}
}
int canvas::width() const
{
return gtk_widget_get_allocated_width(m_drawing_area);
}
int canvas::height() const
{
return gtk_widget_get_allocated_height(m_drawing_area);
}
void canvas::initialize(GtkWidget *drawing_area)
{
g_return_if_fail(drawing_area != nullptr);
m_drawing_area = drawing_area;
m_surface = create_surface(m_drawing_area);
m_context = create_context(m_surface);
m_camera.update_widget(width(), height());
// Draw to the newly created surface for the first time.
redraw();
// Connect to configure events in case our widget changes shape.
g_signal_connect(m_drawing_area, "configure-event", G_CALLBACK(configure_event), this);
// Connect to draw events so that we draw our surface to the drawing area.
g_signal_connect(m_drawing_area, "draw", G_CALLBACK(draw_surface), this);
// GtkDrawingArea objects need specific events enabled explicitly.
gtk_widget_add_events(GTK_WIDGET(m_drawing_area), GDK_BUTTON_PRESS_MASK);
gtk_widget_add_events(GTK_WIDGET(m_drawing_area), GDK_BUTTON_RELEASE_MASK);
gtk_widget_add_events(GTK_WIDGET(m_drawing_area), GDK_POINTER_MOTION_MASK);
gtk_widget_add_events(GTK_WIDGET(m_drawing_area), GDK_SCROLL_MASK);
g_info("canvas::initialize successful.");
}
void canvas::redraw()
{
// Clear the screen and set the background color
cairo_set_source_rgb(m_context, m_background_color.red / 255.0, m_background_color.green / 255.0,
m_background_color.blue / 255.0);
cairo_paint(m_context);
using namespace std::placeholders;
renderer g(m_context, std::bind(&camera::world_to_screen, &m_camera, _1), &m_camera, m_surface);
m_draw_callback(&g);
gtk_widget_queue_draw(m_drawing_area);
g_info("The canvas will be redrawn.");
}
renderer *canvas::create_animation_renderer()
{
if(m_animation_renderer == nullptr) {
using namespace std::placeholders;
m_animation_renderer = new renderer(m_context, std::bind(&camera::world_to_screen, &m_camera, _1), &m_camera, m_surface);
}
return m_animation_renderer;
}
} // namespace ezgl