744 lines
22 KiB
C++
744 lines
22 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/graphics.hpp"
|
|
|
|
#include <cassert>
|
|
#include <glib.h>
|
|
|
|
namespace ezgl {
|
|
|
|
renderer::renderer(cairo_t *cairo,
|
|
transform_fn transform,
|
|
camera *p_camera,
|
|
cairo_surface_t *m_surface)
|
|
: m_cairo(cairo), m_transform(std::move(transform)), m_camera(p_camera), rotation_angle(0)
|
|
{
|
|
#ifdef EZGL_USE_X11
|
|
// Check if the created cairo surface is an XLIB surface
|
|
if (cairo_surface_get_type(m_surface) == CAIRO_SURFACE_TYPE_XLIB) {
|
|
// get the underlying x11 drawable used by cairo surface
|
|
x11_drawable = cairo_xlib_surface_get_drawable(m_surface);
|
|
|
|
// get the x11 display
|
|
x11_display = cairo_xlib_surface_get_display(m_surface);
|
|
|
|
// create the x11 context from the drawable of the cairo surface
|
|
if (x11_display != nullptr) {
|
|
x11_context = XCreateGC(x11_display, x11_drawable, 0, 0);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
renderer::~renderer()
|
|
{
|
|
#ifdef EZGL_USE_X11
|
|
// free the x11 context
|
|
if (x11_display != nullptr) {
|
|
XFreeGC(x11_display, x11_context);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void renderer::update_renderer(cairo_t *cairo, cairo_surface_t *m_surface)
|
|
{
|
|
// Update Cairo Context
|
|
m_cairo = cairo;
|
|
|
|
// Update X11 Context
|
|
#ifdef EZGL_USE_X11
|
|
// Check if the created cairo surface is an XLIB surface
|
|
if (cairo_surface_get_type(m_surface) == CAIRO_SURFACE_TYPE_XLIB) {
|
|
// get the underlying x11 drawable used by cairo surface
|
|
x11_drawable = cairo_xlib_surface_get_drawable(m_surface);
|
|
|
|
// get the x11 display
|
|
x11_display = cairo_xlib_surface_get_display(m_surface);
|
|
|
|
// create the x11 context from the drawable of the cairo surface
|
|
if (x11_display != nullptr) {
|
|
XFreeGC(x11_display, x11_context);
|
|
x11_context = XCreateGC(x11_display, x11_drawable, 0, 0);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Restore graphics attributes
|
|
set_color(current_color);
|
|
set_line_width(current_line_width);
|
|
set_line_cap(current_line_cap);
|
|
set_line_dash(current_line_dash);
|
|
}
|
|
|
|
void renderer::set_coordinate_system(t_coordinate_system new_coordinate_system)
|
|
{
|
|
current_coordinate_system = new_coordinate_system;
|
|
}
|
|
|
|
void renderer::set_visible_world(rectangle new_world)
|
|
{
|
|
// Change the aspect ratio of the new_world to align with the aspect ratio of the initial world
|
|
// Get the width and height of the new_world
|
|
point2d n_center = new_world.center();
|
|
double n_width = new_world.width();
|
|
double n_height = new_world.height();
|
|
|
|
// Get the aspect ratio of the initial world
|
|
double i_width = m_camera->get_initial_world().width();
|
|
double i_height = m_camera->get_initial_world().height();
|
|
double i_aspect_ratio = i_width / i_height;
|
|
|
|
// Make sure the required area is entirely visible
|
|
if (n_width/i_aspect_ratio >= n_height) {
|
|
// Change the height
|
|
double new_height = n_width/i_aspect_ratio;
|
|
new_world ={{n_center.x-n_width/2, n_center.y-new_height/2}, n_width, new_height};
|
|
}
|
|
else {
|
|
// Change the width
|
|
double new_width = n_height/i_aspect_ratio;
|
|
new_world ={{n_center.x-new_width/2, n_center.y-n_height/2}, new_width, n_height};
|
|
}
|
|
|
|
// set the visible bounds of the world
|
|
m_camera->set_world(new_world);
|
|
}
|
|
|
|
rectangle renderer::get_visible_world()
|
|
{
|
|
// m_camera->get_world() is not good representative of the visible world since it doesn't
|
|
// account for the drawable margins.
|
|
// TODO: precalculate the visible world in camera class to speedup the clipping
|
|
|
|
// Get the world and screen dimensions
|
|
rectangle world = m_camera->get_world();
|
|
rectangle screen = m_camera->get_screen();
|
|
|
|
// Calculate the margins by converting the screen origin to world coordinates
|
|
point2d margin = screen.bottom_left() * m_camera->get_world_scale_factor();
|
|
|
|
// The actual visible world
|
|
return {(world.bottom_left() - margin), (world.top_right() + margin)};
|
|
}
|
|
|
|
rectangle renderer::get_visible_screen()
|
|
{
|
|
// Get the widget dimensions
|
|
return m_camera->get_widget();
|
|
}
|
|
|
|
rectangle renderer::world_to_screen(const rectangle& box)
|
|
{
|
|
point2d origin = m_transform(box.bottom_left());
|
|
point2d top_right = m_transform(box.top_right());
|
|
|
|
return rectangle(origin, top_right);
|
|
}
|
|
|
|
bool renderer::rectangle_off_screen(rectangle rect)
|
|
{
|
|
if(current_coordinate_system == SCREEN)
|
|
return false;
|
|
|
|
rectangle visible = get_visible_world();
|
|
|
|
if(rect.right() < visible.left())
|
|
return true;
|
|
|
|
if(rect.left() > visible.right())
|
|
return true;
|
|
|
|
if(rect.top() < visible.bottom())
|
|
return true;
|
|
|
|
if(rect.bottom() > visible.top())
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
void renderer::set_color(color c)
|
|
{
|
|
set_color(c.red, c.green, c.blue, c.alpha);
|
|
}
|
|
|
|
void renderer::set_color(color c, uint_fast8_t alpha)
|
|
{
|
|
set_color(c.red, c.green, c.blue, alpha);
|
|
}
|
|
|
|
void renderer::set_color(uint_fast8_t red,
|
|
uint_fast8_t green,
|
|
uint_fast8_t blue,
|
|
uint_fast8_t alpha)
|
|
{
|
|
// set color for cairo
|
|
cairo_set_source_rgba(m_cairo, red / 255.0, green / 255.0, blue / 255.0, alpha / 255.0);
|
|
|
|
// set current_color
|
|
current_color = {red, green, blue, alpha};
|
|
|
|
#ifdef EZGL_USE_X11
|
|
// check transparency
|
|
if(alpha != 255)
|
|
transparency_flag = true;
|
|
else
|
|
transparency_flag = false;
|
|
|
|
// set color for x11 (no transparency)
|
|
if (x11_display != nullptr) {
|
|
unsigned long xcolor = 0;
|
|
xcolor |= (red << 2 * 8 | red << 8 | red) & 0xFF0000;
|
|
xcolor |= (green << 2 * 8 | green << 8 | green) & 0xFF00;
|
|
xcolor |= (blue << 2 * 8 | blue << 8 | blue) & 0xFF;
|
|
xcolor |= 0xFF000000;
|
|
XSetForeground(x11_display, x11_context, xcolor);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void renderer::set_line_cap(line_cap cap)
|
|
{
|
|
auto cairo_cap = static_cast<cairo_line_cap_t>(cap);
|
|
cairo_set_line_cap(m_cairo, cairo_cap);
|
|
|
|
current_line_cap = cap;
|
|
|
|
#ifdef EZGL_USE_X11
|
|
if (x11_display != nullptr) {
|
|
XSetLineAttributes(x11_display, x11_context, current_line_width,
|
|
current_line_dash == line_dash::none ? LineSolid : LineOnOffDash,
|
|
current_line_cap == line_cap::butt ? CapButt : CapRound, JoinMiter);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void renderer::set_line_dash(line_dash dash)
|
|
{
|
|
if(dash == line_dash::none) {
|
|
int num_dashes = 0; // disables dashing
|
|
|
|
cairo_set_dash(m_cairo, nullptr, num_dashes, 0);
|
|
} else if(dash == line_dash::asymmetric_5_3) {
|
|
static double dashes[] = {5.0, 3.0};
|
|
int num_dashes = 2; // asymmetric dashing
|
|
|
|
cairo_set_dash(m_cairo, dashes, num_dashes, 0);
|
|
}
|
|
|
|
current_line_dash = dash;
|
|
|
|
#ifdef EZGL_USE_X11
|
|
if (x11_display != nullptr) {
|
|
XSetLineAttributes(x11_display, x11_context, current_line_width,
|
|
current_line_dash == line_dash::none ? LineSolid : LineOnOffDash,
|
|
current_line_cap == line_cap::butt ? CapButt : CapRound, JoinMiter);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void renderer::set_line_width(int width)
|
|
{
|
|
cairo_set_line_width(m_cairo, width == 0 ? 1 : width);
|
|
|
|
current_line_width = width;
|
|
|
|
#ifdef EZGL_USE_X11
|
|
if (x11_display != nullptr) {
|
|
XSetLineAttributes(x11_display, x11_context, current_line_width,
|
|
current_line_dash == line_dash::none ? LineSolid : LineOnOffDash,
|
|
current_line_cap == line_cap::butt ? CapButt : CapRound, JoinMiter);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void renderer::set_font_size(double new_size)
|
|
{
|
|
cairo_set_font_size(m_cairo, new_size);
|
|
}
|
|
|
|
void renderer::format_font(std::string const &family, font_slant slant, font_weight weight)
|
|
{
|
|
cairo_select_font_face(m_cairo, family.c_str(), static_cast<cairo_font_slant_t>(slant),
|
|
static_cast<cairo_font_weight_t>(weight));
|
|
}
|
|
|
|
void renderer::format_font(std::string const &family,
|
|
font_slant slant,
|
|
font_weight weight,
|
|
double new_size)
|
|
{
|
|
set_font_size(new_size);
|
|
format_font(family, slant, weight);
|
|
}
|
|
|
|
void renderer::set_text_rotation(double degrees)
|
|
{
|
|
// convert the given angle to rad
|
|
rotation_angle = -degrees * M_PI / 180;
|
|
}
|
|
|
|
void renderer::set_horiz_text_just(text_just horiz_just)
|
|
{
|
|
if (horiz_just != text_just::top && horiz_just != text_just::bottom)
|
|
horiz_text_just = horiz_just;
|
|
}
|
|
|
|
void renderer::set_vert_text_just(text_just vert_just)
|
|
{
|
|
if (vert_just != text_just::right && vert_just != text_just::left)
|
|
vert_text_just = vert_just;
|
|
}
|
|
|
|
void renderer::draw_line(point2d start, point2d end)
|
|
{
|
|
if(rectangle_off_screen({start, end}))
|
|
return;
|
|
|
|
if(current_coordinate_system == WORLD) {
|
|
start = m_transform(start);
|
|
end = m_transform(end);
|
|
}
|
|
|
|
#ifdef EZGL_USE_X11
|
|
if(!transparency_flag && x11_display != nullptr) {
|
|
XDrawLine(x11_display, x11_drawable, x11_context, start.x, start.y, end.x, end.y);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
cairo_move_to(m_cairo, start.x, start.y);
|
|
cairo_line_to(m_cairo, end.x, end.y);
|
|
|
|
cairo_stroke(m_cairo);
|
|
}
|
|
|
|
void renderer::draw_rectangle(point2d start, point2d end)
|
|
{
|
|
if(rectangle_off_screen({start, end}))
|
|
return;
|
|
|
|
draw_rectangle_path(start, end, false);
|
|
}
|
|
|
|
void renderer::draw_rectangle(point2d start, double width, double height)
|
|
{
|
|
if(rectangle_off_screen({start, {start.x + width, start.y + height}}))
|
|
return;
|
|
|
|
draw_rectangle_path(start, {start.x + width, start.y + height}, false);
|
|
}
|
|
|
|
void renderer::draw_rectangle(rectangle r)
|
|
{
|
|
if(rectangle_off_screen({{r.left(), r.bottom()}, {r.right(), r.top()}}))
|
|
return;
|
|
|
|
draw_rectangle_path({r.left(), r.bottom()}, {r.right(), r.top()}, false);
|
|
}
|
|
|
|
void renderer::fill_rectangle(point2d start, point2d end)
|
|
{
|
|
if(rectangle_off_screen({start, end}))
|
|
return;
|
|
|
|
draw_rectangle_path(start, end, true);
|
|
}
|
|
|
|
void renderer::fill_rectangle(point2d start, double width, double height)
|
|
{
|
|
if(rectangle_off_screen({start, {start.x + width, start.y + height}}))
|
|
return;
|
|
|
|
draw_rectangle_path(start, {start.x + width, start.y + height}, true);
|
|
}
|
|
|
|
void renderer::fill_rectangle(rectangle r)
|
|
{
|
|
if(rectangle_off_screen({{r.left(), r.bottom()}, {r.right(), r.top()}}))
|
|
return;
|
|
|
|
draw_rectangle_path({r.left(), r.bottom()}, {r.right(), r.top()}, true);
|
|
}
|
|
|
|
// For speed, use a fixed size polygon point buffer when possible
|
|
// Dynamically allocate an arbitrary size buffer only when necessary.
|
|
#define X11_MAX_FIXED_POLY_PTS 100
|
|
|
|
void renderer::fill_poly(std::vector<point2d> const &points)
|
|
{
|
|
assert(points.size() > 1);
|
|
|
|
// Conservative but fast clip test -- check containing rectangle of polygon
|
|
double x_min = points[0].x;
|
|
double x_max = points[0].x;
|
|
double y_min = points[0].y;
|
|
double y_max = points[0].y;
|
|
|
|
for(std::size_t i = 1; i < points.size(); ++i) {
|
|
x_min = std::min(x_min, points[i].x);
|
|
x_max = std::max(x_max, points[i].x);
|
|
y_min = std::min(y_min, points[i].y);
|
|
y_max = std::max(y_max, points[i].y);
|
|
}
|
|
|
|
if(rectangle_off_screen({{x_min, y_min}, {x_max, y_max}}))
|
|
return;
|
|
|
|
point2d next_point = points[0];
|
|
|
|
#ifdef EZGL_USE_X11
|
|
if(!transparency_flag && x11_display != nullptr) {
|
|
XPoint fixed_trans_points[X11_MAX_FIXED_POLY_PTS];
|
|
XPoint *trans_points = fixed_trans_points;
|
|
|
|
if(points.size() > X11_MAX_FIXED_POLY_PTS) {
|
|
trans_points = new XPoint[points.size()];
|
|
}
|
|
|
|
for(size_t i = 0; i < points.size(); i++) {
|
|
if(current_coordinate_system == WORLD)
|
|
next_point = m_transform(points[i]);
|
|
else
|
|
next_point = points[i];
|
|
trans_points[i].x = static_cast<long>(next_point.x);
|
|
trans_points[i].y = static_cast<long>(next_point.y);
|
|
}
|
|
|
|
XFillPolygon(x11_display, x11_drawable, x11_context, trans_points, points.size(), Complex,
|
|
CoordModeOrigin);
|
|
|
|
if(points.size() > X11_MAX_FIXED_POLY_PTS)
|
|
delete[] trans_points;
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
if(current_coordinate_system == WORLD)
|
|
next_point = m_transform(points[0]);
|
|
|
|
cairo_move_to(m_cairo, next_point.x, next_point.y);
|
|
|
|
for(std::size_t i = 1; i < points.size(); ++i) {
|
|
if(current_coordinate_system == WORLD)
|
|
next_point = m_transform(points[i]);
|
|
else
|
|
next_point = points[i];
|
|
cairo_line_to(m_cairo, next_point.x, next_point.y);
|
|
}
|
|
|
|
cairo_close_path(m_cairo);
|
|
cairo_fill(m_cairo);
|
|
}
|
|
|
|
void renderer::draw_elliptic_arc(point2d center,
|
|
double radius_x,
|
|
double radius_y,
|
|
double start_angle,
|
|
double extent_angle)
|
|
{
|
|
if(rectangle_off_screen(
|
|
{{center.x - radius_x, center.y - radius_y}, {center.x + radius_x, center.y + radius_y}}))
|
|
return;
|
|
|
|
// define the stretch factor (i.e. An ellipse is a stretched circle)
|
|
double stretch_factor = radius_y / radius_x;
|
|
|
|
draw_arc_path(center, radius_x, start_angle, extent_angle, stretch_factor, false);
|
|
}
|
|
|
|
void renderer::draw_arc(point2d center, double radius, double start_angle, double extent_angle)
|
|
{
|
|
if(rectangle_off_screen(
|
|
{{center.x - radius, center.y - radius}, {center.x + radius, center.y + radius}}))
|
|
return;
|
|
|
|
draw_arc_path(center, radius, start_angle, extent_angle, 1, false);
|
|
}
|
|
|
|
void renderer::fill_elliptic_arc(point2d center,
|
|
double radius_x,
|
|
double radius_y,
|
|
double start_angle,
|
|
double extent_angle)
|
|
{
|
|
if(rectangle_off_screen(
|
|
{{center.x - radius_x, center.y - radius_y}, {center.x + radius_x, center.y + radius_y}}))
|
|
return;
|
|
|
|
// define the stretch factor (i.e. An ellipse is a stretched circle)
|
|
double stretch_factor = radius_y / radius_x;
|
|
|
|
draw_arc_path(center, radius_x, start_angle, extent_angle, stretch_factor, true);
|
|
}
|
|
|
|
void renderer::fill_arc(point2d center, double radius, double start_angle, double extent_angle)
|
|
{
|
|
if(rectangle_off_screen(
|
|
{{center.x - radius, center.y - radius}, {center.x + radius, center.y + radius}}))
|
|
return;
|
|
|
|
draw_arc_path(center, radius, start_angle, extent_angle, 1, true);
|
|
}
|
|
|
|
void renderer::draw_text(point2d point, std::string const &text)
|
|
{
|
|
// call the draw_text function with no bounds
|
|
draw_text(point, text, DBL_MAX, DBL_MAX);
|
|
}
|
|
|
|
void renderer::draw_text(point2d point, std::string const &text, double bound_x, double bound_y)
|
|
{
|
|
// the center point of the text
|
|
point2d center = point;
|
|
|
|
// roughly calculate the center point for pre-clipping
|
|
if (horiz_text_just == text_just::left)
|
|
center.x += bound_x/2;
|
|
else if (horiz_text_just == text_just::right)
|
|
center.x -= bound_x/2;
|
|
if (vert_text_just == text_just::top)
|
|
center.y -= bound_y/2;
|
|
else if (vert_text_just == text_just::bottom)
|
|
center.y += bound_y/2;
|
|
|
|
if(rectangle_off_screen({{center.x - bound_x / 2, center.y - bound_y / 2}, bound_x, bound_y}))
|
|
return;
|
|
|
|
// get the width and height of the drawn text
|
|
cairo_text_extents_t text_extents{0,0,0,0,0,0};
|
|
cairo_text_extents(m_cairo, text.c_str(), &text_extents);
|
|
|
|
// get more information about the font used
|
|
cairo_font_extents_t font_extents{0,0,0,0,0};
|
|
cairo_font_extents(m_cairo, &font_extents);
|
|
|
|
// get text width and height in world coordinates (text width and height are constant in widget coordinates)
|
|
double scaled_width = text_extents.width * m_camera->get_world_scale_factor().x;
|
|
double scaled_height = text_extents.height * m_camera->get_world_scale_factor().y;
|
|
|
|
// if text width or height is greater than the given bounds, don't draw the text.
|
|
// NOTE: text rotation is NOT taken into account in bounding check (i.e. text width is compared to bound_x)
|
|
if(scaled_width > bound_x || scaled_height > bound_y) {
|
|
return;
|
|
}
|
|
|
|
// save the current state to undo the rotation needed for drawing rotated text
|
|
cairo_save(m_cairo);
|
|
|
|
// transform the given point
|
|
if(current_coordinate_system == WORLD)
|
|
center = m_transform(point);
|
|
else
|
|
center = point;
|
|
|
|
// calculating the reference point to center the text around "center" taking into account the rotation_angle
|
|
// for more info about reference point location: see https://www.cairographics.org/tutorial/#L1understandingtext
|
|
point2d ref_point = {0, 0};
|
|
|
|
ref_point.x = center.x -
|
|
(text_extents.x_bearing + (text_extents.width / 2)) * cos(rotation_angle) -
|
|
(-font_extents.descent + (text_extents.height / 2)) * sin(rotation_angle);
|
|
|
|
ref_point.y = center.y -
|
|
(text_extents.y_bearing + (text_extents.height / 2)) * cos(rotation_angle) -
|
|
(text_extents.x_bearing + (text_extents.width / 2)) * sin(rotation_angle);
|
|
|
|
// adjust the reference point according to the required justification
|
|
if (horiz_text_just == text_just::left) {
|
|
ref_point.x += (text_extents.width / 2) * cos(rotation_angle);
|
|
ref_point.y += (text_extents.width / 2) * sin(rotation_angle);
|
|
}
|
|
else if (horiz_text_just == text_just::right) {
|
|
ref_point.x -= (text_extents.width / 2) * cos(rotation_angle);
|
|
ref_point.y -= (text_extents.width / 2) * sin(rotation_angle);
|
|
}
|
|
if (vert_text_just == text_just::top) {
|
|
ref_point.x -= (text_extents.height / 2) * sin(rotation_angle);
|
|
ref_point.y += (text_extents.height / 2) * cos(rotation_angle);
|
|
}
|
|
else if (vert_text_just == text_just::bottom) {
|
|
ref_point.x += (text_extents.height / 2) * sin(rotation_angle);
|
|
ref_point.y -= (text_extents.height / 2) * cos(rotation_angle);
|
|
}
|
|
|
|
// move to the reference point, perform the rotation, and draw the text
|
|
cairo_move_to(m_cairo, ref_point.x, ref_point.y);
|
|
cairo_rotate(m_cairo, rotation_angle);
|
|
cairo_show_text(m_cairo, text.c_str());
|
|
|
|
// restore the old state to undo the performed rotation
|
|
cairo_restore(m_cairo);
|
|
}
|
|
|
|
void renderer::draw_rectangle_path(point2d start, point2d end, bool fill_flag)
|
|
{
|
|
if(current_coordinate_system == WORLD) {
|
|
start = m_transform(start);
|
|
end = m_transform(end);
|
|
}
|
|
|
|
#ifdef EZGL_USE_X11
|
|
if(!transparency_flag && x11_display != nullptr) {
|
|
// Add 0.5 for extra half-pixel accuracy
|
|
int start_x = static_cast<int>(start.x + 0.5);
|
|
int start_y = static_cast<int>(start.y + 0.5);
|
|
int end_x = static_cast<int>(end.x + 0.5);
|
|
int end_y = static_cast<int>(end.y + 0.5);
|
|
|
|
if(fill_flag)
|
|
XFillRectangle(x11_display, x11_drawable, x11_context, std::min(start_x, end_x),
|
|
std::min(start_y, end_y), std::abs(end_x - start_x), std::abs(end_y - start_y));
|
|
else
|
|
XDrawRectangle(x11_display, x11_drawable, x11_context, std::min(start_x, end_x),
|
|
std::min(start_y, end_y), std::abs(end_x - start_x), std::abs(end_y - start_y));
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
cairo_move_to(m_cairo, start.x, start.y);
|
|
cairo_line_to(m_cairo, start.x, end.y);
|
|
cairo_line_to(m_cairo, end.x, end.y);
|
|
cairo_line_to(m_cairo, end.x, start.y);
|
|
|
|
cairo_close_path(m_cairo);
|
|
|
|
// actual drawing
|
|
if(fill_flag)
|
|
cairo_fill(m_cairo);
|
|
else
|
|
cairo_stroke(m_cairo);
|
|
}
|
|
|
|
void renderer::draw_arc_path(point2d center,
|
|
double radius,
|
|
double start_angle,
|
|
double extent_angle,
|
|
double stretch_factor,
|
|
bool fill_flag)
|
|
{
|
|
// point_x is a point on the arc outline
|
|
point2d point_x = {center.x + radius, center.y};
|
|
|
|
// transform the center point of the arc, and the other point
|
|
if(current_coordinate_system == WORLD) {
|
|
center = m_transform(center);
|
|
point_x = m_transform(point_x);
|
|
}
|
|
|
|
// calculate the new radius after transforming to the new coordinates
|
|
radius = point_x.x - center.x;
|
|
|
|
#ifdef EZGL_USE_X11
|
|
if(!transparency_flag && x11_display != nullptr) {
|
|
if(fill_flag)
|
|
XFillArc(x11_display, x11_drawable, x11_context, center.x - radius,
|
|
center.y - radius * stretch_factor, 2 * radius, 2 * radius * stretch_factor,
|
|
start_angle * 64, extent_angle * 64);
|
|
else
|
|
XDrawArc(x11_display, x11_drawable, x11_context, center.x - radius,
|
|
center.y - radius * stretch_factor, 2 * radius, 2 * radius * stretch_factor,
|
|
start_angle * 64, extent_angle * 64);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
// save the current state to undo the scaling needed for drawing ellipse
|
|
cairo_save(m_cairo);
|
|
|
|
// scale the drawing by the stretch factor to draw elliptic circles
|
|
cairo_scale(m_cairo, 1 / stretch_factor, 1);
|
|
center.x = center.x * stretch_factor;
|
|
radius = radius * stretch_factor;
|
|
|
|
// start a new path (forget the current point). Alternative for cairo_move_to() for drawing non-filled arc
|
|
cairo_new_path(m_cairo);
|
|
|
|
// if the arc will be filled in, start drawing from the center of the arc
|
|
if(fill_flag)
|
|
cairo_move_to(m_cairo, center.x, center.y);
|
|
|
|
// calculating the ending angle
|
|
double end_angle = start_angle + extent_angle;
|
|
|
|
// draw the arc in counter clock-wise direction if the extent angle is positive
|
|
if(extent_angle >= 0) {
|
|
cairo_arc_negative(
|
|
m_cairo, center.x, center.y, radius, -start_angle * M_PI / 180, -end_angle * M_PI / 180);
|
|
}
|
|
// draw the arc in clock-wise direction if the extent angle is negative
|
|
else {
|
|
cairo_arc(
|
|
m_cairo, center.x, center.y, radius, -start_angle * M_PI / 180, -end_angle * M_PI / 180);
|
|
}
|
|
|
|
// if the arc will be filled in, return back to the center of the arc
|
|
if(fill_flag)
|
|
cairo_close_path(m_cairo);
|
|
|
|
// restore the old state to undo the scaling needed for drawing ellipse
|
|
cairo_restore(m_cairo);
|
|
|
|
// actual drawing
|
|
if(fill_flag)
|
|
cairo_fill(m_cairo);
|
|
else
|
|
cairo_stroke(m_cairo);
|
|
}
|
|
|
|
void renderer::draw_surface(surface *p_surface, point2d top_left)
|
|
{
|
|
// Check if the surface is properly created
|
|
if(cairo_surface_status(p_surface) != CAIRO_STATUS_SUCCESS)
|
|
return;
|
|
|
|
// pre-clipping
|
|
double s_width = (double)cairo_image_surface_get_width(p_surface);
|
|
double s_height = (double)cairo_image_surface_get_height(p_surface);
|
|
|
|
if(rectangle_off_screen({{top_left.x, top_left.y - s_height}, s_width, s_height}))
|
|
return;
|
|
|
|
// transform the given top_left point
|
|
if(current_coordinate_system == WORLD)
|
|
top_left = m_transform(top_left);
|
|
|
|
// Create a source for painting from the surface
|
|
cairo_set_source_surface(m_cairo, p_surface, top_left.x, top_left.y);
|
|
|
|
// Actual drawing
|
|
cairo_paint(m_cairo);
|
|
}
|
|
|
|
surface *renderer::load_png(const char *file_path)
|
|
{
|
|
// Create an image surface from a PNG image
|
|
cairo_surface_t *png_surface = cairo_image_surface_create_from_png(file_path);
|
|
|
|
return png_surface;
|
|
}
|
|
|
|
void renderer::free_surface(surface *p_surface)
|
|
{
|
|
// Check if the surface is properly created
|
|
if (cairo_surface_status(p_surface) == CAIRO_STATUS_SUCCESS)
|
|
cairo_surface_destroy(p_surface);
|
|
}
|
|
}
|