venerdì 9 gennaio 2009

Drawing regular polygons on Cairo

The next object I want to implement on GGredit is an object that allow to draw a generic regular polygon, the user can define the number of sides and GGredit draw this polygon (inkscape have a similar tool named "Polygon and Star").



The Ellipse


Once you know how to draw an ellipse is easy to draw regular polygons or stars. For draw an ellipse we need on input:

  • double cx: the x center of the ellipse

  • double cy: the y center of the ellipse

  • double rx: the horizzontal radius of the ellipse

  • double ry: the vertical radius of the ellipse


These values can be easily evaluated starting from a rectangle.
Since the size of a full circle is 2π, we need a loop that draw all point between 0 and 2 * M_PI (M_PI is the constant on cmath for PI).

#include
.
.
.
for (double alpha = 0; alpha <= 2 * M_PI; aplha+=0.1)
{
double px = cx + rx * cos (alpha);
double py = cy + ry * sin (alpha);
// px, py is current point
}

For loop radians I use a step of 0.1, this is only for simplify since in effect we have infinite points on an ellipse.
Draw al points on Cairo is a beat more sophisticated since we need to define a path using move_to() and line_to() functions. So we need to call the move_to() function for the first point out of the loop and the call all the line_to() functions on the loop.

double alpha = 0;
double alpha_step = 0.1;

// First point
double px1 = cx + rx * cos (alpha);
double py1 = cy + ry * sin (alpha);
cr->move_to (px1, py1); // cr is the cairo context

for (double alpha = 0; alpha <= 2 * M_PI; aplha+=alpha_step)
{
double px = cx + rx * cos (alpha);
double py = cy + ry * sin (alpha);
cr->line_to (px, py); // cr is the cairo context
}

This is only theory, Cairo have the arc() function for draw ellipses.

Polygons


The step of the loop is the key for draw polygons. If whe take only three equidistant points of an ellipse we have a triangle. For get three equidistant points we simply need to evaluate the alpha_step using: (M_PI * 2) / 3.



We can generically evaluate the step needed for draw a polygon by dividing M_PI * 2 for the number of sides of the required polygon: (M_PI * 2) / sides_count.
This is a Gtk::DrawingArea inherited widget that display a polygon:

/*
* PolygonView.h
*
* Created on: Sep 9, 2008
* Author: user
*/

#ifndef POLYGONVIEW_H_
#define POLYGONVIEW_H_

#include

class PolygonView : public Gtk::DrawingArea
{
public:
PolygonView();
virtual ~PolygonView();

double getSidesCount() const { return sides_count_; };
void setSidesCound(double value) { sides_count_ = value; };

void rotate(double cx, double cy, double radians);
void saveSvg(const std::string& file_name);

protected:
Gdk::Color back_color_;
Gdk::Color stroke_color_;
Gdk::Color fill_color_;
Gdk::Color help_color_;
Gdk::Color rotation_color_;
Gdk::Rectangle polygon_bounds_;
double rotation_center_x_;
double rotation_center_y_;
double rotation_degrees_;
double sides_count_;
bool draw_help_;
bool draw_rotation_;

protected:
bool on_expose_event(GdkEventExpose* event);

void draw(Cairo::RefPtr< Cairo::Context > context_ref);
void draw_ellipse(Cairo::RefPtr< Cairo::Context > context_ref, const Gdk::Rectangle& bounds);
};

#endif /* POLYGONVIEW_H_ */

PolygonView.h


/*
* PolygonView.cpp
*
* Created on: Sep 9, 2008
* Author: user
*/

#include "PolygonView.h"

#include
#include

PolygonView::PolygonView() :
back_color_( "white" ),
stroke_color_( "black" ),
fill_color_( "white" ),
help_color_( "gray" ),
rotation_color_( "red" ),
polygon_bounds_( 20, 20, 200, 200),
rotation_center_x_( 110 ),
rotation_center_y_( 110 ),
rotation_degrees_( 0 ),
sides_count_( 5 ),
draw_help_( true ),
draw_rotation_( false )
{
}

PolygonView::~PolygonView()
{
}

void PolygonView::rotate(double cx, double cy, double radians)
{
rotation_center_x_ = cx;
rotation_center_y_ = cy;
rotation_degrees_ = radians;
}

bool PolygonView::on_expose_event(GdkEventExpose *event)
{
Glib::RefPtr< Gdk::Window > window_ref = get_window();

if (window_ref)
{
Gtk::Allocation allocation = get_allocation();

Cairo::RefPtr< Cairo::Context > context_ref = window_ref->create_cairo_context();

if (event)
{
context_ref->rectangle (event->area.x, event->area.y, event->area.width, event->area.height);
context_ref->clip();
}

// Draw background
context_ref->set_source_rgb (back_color_.get_red_p(),
back_color_.get_green_p(),
back_color_.get_blue_p());

context_ref->rectangle (0, 0, allocation.get_width(), allocation.get_height());
context_ref->fill();

draw (context_ref);
}

return true;
}

void PolygonView::saveSvg(const std::string& file_name)
{
double wx = polygon_bounds_.get_width() + polygon_bounds_.get_x() * 2;
double wy = polygon_bounds_.get_height() + polygon_bounds_.get_y() * 2;
Cairo::RefPtr< Cairo::SvgSurface > surface_ref = Cairo::SvgSurface::create ( file_name, wx, wy);
Cairo::RefPtr< Cairo::Context > context_ref = Cairo::Context::create (surface_ref);

draw (context_ref);

context_ref->show_page();

}

void PolygonView::draw(Cairo::RefPtr< Cairo::Context > context_ref)
{
// Draw not rotated vector
if (draw_rotation_)
{
context_ref->save();

context_ref->set_source_rgba (help_color_.get_red_p(),
help_color_.get_green_p(),
help_color_.get_blue_p(), 0.5);

context_ref->move_to (rotation_center_x_, rotation_center_y_);
context_ref->line_to (rotation_center_x_ + 20, rotation_center_y_);
context_ref->stroke();

context_ref->restore();
}

context_ref->save();

// Set rotation
context_ref->translate (rotation_center_x_, rotation_center_y_);
context_ref->rotate (rotation_degrees_);
context_ref->translate (-rotation_center_x_, -rotation_center_y_);

// Draw polygon
double rx = polygon_bounds_.get_width() / 2.0;
double ry = polygon_bounds_.get_height() / 2.0;
double cx = polygon_bounds_.get_x() + rx;
double cy = polygon_bounds_.get_y() + ry;

// Define first vertex
double alpha = 0.0;
context_ref->move_to (cx + rx * cos(alpha), cy + ry * sin(alpha));

// Define other vertexes
double alpha_step = 2 * M_PI / sides_count_;
for (int i = 1; i < sides_count_; i++)
{
alpha += alpha_step;
context_ref->line_to (cx + rx * cos(alpha), cy + ry * sin(alpha));
}
context_ref->close_path();

// Stroke and fill polygon
context_ref->set_source_rgb (stroke_color_.get_red_p(),
stroke_color_.get_green_p(),
stroke_color_.get_blue_p());

context_ref->stroke_preserve();

context_ref->set_source_rgb (fill_color_.get_red_p(),
fill_color_.get_green_p(),
fill_color_.get_blue_p());

context_ref->fill();

if (draw_rotation_)
{
// Draw rotation center
context_ref->set_source_rgba (rotation_color_.get_red_p(),
rotation_color_.get_green_p(),
rotation_color_.get_blue_p(), 0.5);

Gdk::Rectangle rot_center (rotation_center_x_ - 3, rotation_center_y_ - 3, 6, 6);

draw_ellipse (context_ref, rot_center);
context_ref->stroke();

context_ref->move_to (rotation_center_x_, rotation_center_y_ - 6);
context_ref->line_to (rotation_center_x_, rotation_center_y_ + 6);
context_ref->stroke();

context_ref->move_to (rotation_center_x_ - 6, rotation_center_y_);
context_ref->line_to (rotation_center_x_ + 6, rotation_center_y_);
context_ref->stroke();

context_ref->move_to (rotation_center_x_, rotation_center_y_);
context_ref->line_to (rotation_center_x_ + 20, rotation_center_y_);
context_ref->stroke();
}

if (draw_help_)
{
// Draw center
context_ref->set_source_rgba (help_color_.get_red_p(),
help_color_.get_green_p(),
help_color_.get_blue_p(), 0.5);

Gdk::Rectangle center (cx - 3, cy - 3, 6, 6);

draw_ellipse (context_ref, center);
context_ref->stroke();

context_ref->move_to (cx, cy - 6);
context_ref->line_to (cx, cy + 6);
context_ref->stroke();

context_ref->move_to (cx - 6, cy);
context_ref->line_to (cx + 6, cy);
context_ref->stroke();

// Draw 0, M_PI/2, M_PI, M_PI/2*3
context_ref->move_to (cx + (rx - 3) * cos(0.0), cy + (ry - 3) * sin(0.0));
context_ref->line_to (cx + (rx + 3) * cos(0.0), cy + (ry + 3) * sin(0.0));
context_ref->stroke();

context_ref->move_to (cx + (rx - 3) * cos(M_PI / 2.0), cy + (ry - 3) * sin(M_PI / 2.0));
context_ref->line_to (cx + (rx + 3) * cos(M_PI / 2.0), cy + (ry + 3) * sin(M_PI / 2.0));
context_ref->stroke();

context_ref->move_to (cx + (rx - 3) * cos(M_PI), cy + (ry - 3) * sin(M_PI));
context_ref->line_to (cx + (rx + 3) * cos(M_PI), cy + (ry + 3) * sin(M_PI));
context_ref->stroke();

context_ref->move_to (cx + (rx - 3) * cos(M_PI / 2.0 * 3.0), cy + (ry - 3) * sin(M_PI / 2.0 * 3.0));
context_ref->line_to (cx + (rx + 3) * cos(M_PI / 2.0 * 3.0), cy + (ry + 3) * sin(M_PI / 2.0 * 3.0));
context_ref->stroke();

// Draw bounds ellipse
draw_ellipse (context_ref, polygon_bounds_);
context_ref->stroke();
}

context_ref->restore();
}

void PolygonView::draw_ellipse(Cairo::RefPtr< Cairo::Context > context_ref, const Gdk::Rectangle& bounds)
{
context_ref->save();

context_ref->translate (bounds.get_x() + (bounds.get_width() / 2.0),
bounds.get_y() + (bounds.get_height() / 2.0));

context_ref->scale (bounds.get_width() / 2.0,
bounds.get_height() / 2.0);

context_ref->arc (0.0, 0.0, 1.0, 0.0, 2 * M_PI);

context_ref->restore();
}

PolygonView.cpp

4 commenti:

  1. Thanks for the simple explanation... Really helped me!

    RispondiElimina
  2. VI NISTE NORMALNI!!!

    RispondiElimina
  3. Davidson garment is a quintessential Harley abercrombie Outlet davidson item, especially if you feature ones Harley decked abercrombie and fitch over. Featuring the whole distinctive line of leather abercrombie sale jackets, t-shirts, buckles, hats, belts, boots abercrombie & fitch and helmets, feeling of guilt reason this is abercrombie not to accessorize to max.A decent buy Harley enthusiast will abercrombie uk go in closet and grab a Harley t-shirt, leather jacket and boots. But, abercrombie london does your closet contain present accessory considering all abercrombie and Fitch Polo of? The Harley helmet. With out them your road abercrombie Polos trip could end in disaster.

    RispondiElimina
  4. Doing the same thing for thomas sabo diamonds is going to cost you thomas sabo sale an arm and a leg as the colored diamonds thomas sabo jewellery are hard to come by. thomas sabo charms The deposit and jewelry boxes you have, thomas sabo online as well as desk and cabinets, vehicle doors and windows, sabo jewellery home and store front doors can all get ruined. thomas sabo charms sale Using a skilled locksmith you'll cheap thomas sabo charms be able to have the assurance that you get the best work that will be done right and guaranteed.All to often, discount thomas sabo charms instead of finding an automotive locksmith, thomas sabo charms clearance Washington DC residents try to take matters in to their own hands.

    RispondiElimina