| /* |
| * 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::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); |
| |
| #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); |
| |
| #ifdef EZGL_USE_X11 |
| if (x11_display != nullptr) { |
| current_line_cap = cap; |
| 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); |
| } |
| |
| #ifdef EZGL_USE_X11 |
| if (x11_display != nullptr) { |
| current_line_dash = dash; |
| 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); |
| |
| #ifdef EZGL_USE_X11 |
| if (x11_display != nullptr) { |
| current_line_width = width; |
| 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); |
| } |
| } |