画圆弧和圆

在开罗中,画圆弧、圆、椭圆使用的是同一个函数:Cairo::Context::arc()。这个函数有五个参数。前两个参数是圆弧中心点的坐标,第三个参数是圆弧的半径,最后两个参数是圆弧的起点和终点。所有的角度都使用弧度制,所以画圆和画一个弧度为0到2*M_PI的圆弧是一样的。角度0沿着X轴的正方向(对于用户空间而言)。角度M_PI/2(角度制的90°)沿着Y轴的正方向(对于用户空间而言)。角度沿着X轴的正方向向Y轴的正方向逐渐增加。所以使用默认的变换矩阵的时候,角度沿着顺时针方向增加(请记住Y轴的正方向指向下方)。

想要绘制椭圆的话,你可以在X和Y方向用不同的量缩放当前的变换矩阵。例如,如果你想要绘制一个以xy为中心,大小为widthheight的椭圆:

context->save();
context->translate(x, y);
context->scale(width / 2.0, height / 2.0);
context->arc(0.0, 0.0, 1.0, 0.0, 2 * M_PI);
context->restore();

16.4.1. 示例

这是一个简单的示例程序,它在绘图区域绘制了一个圆弧、一个圆和一个椭圆。

Figure 16-5绘图区域 - 圆弧

源代码

File: myarea.h (For use with gtkmm 4)

#ifndef GTKMM_EXAMPLE_MYAREA_H
#define GTKMM_EXAMPLE_MYAREA_H

#include <gtkmm/drawingarea.h>

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

protected:
  void on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height);
};

#endif // GTKMM_EXAMPLE_MYAREA_H

File: main.cc (For use with gtkmm 4)

#include "myarea.h"
#include <gtkmm/application.h>
#include <gtkmm/window.h>

class ExampleWindow : public Gtk::Window
{
public:
  ExampleWindow();

protected:
  MyArea m_area;
};

ExampleWindow::ExampleWindow()
{
  set_title("DrawingArea");
  set_child(m_area);
}

int main(int argc, char** argv)
{
  auto app = Gtk::Application::create("org.gtkmm.example");

  return app->make_window_and_run<ExampleWindow>(argc, argv);
}

File: myarea.cc (For use with gtkmm 4)

#include "myarea.h"
#include <cairomm/context.h>
#include <cmath>

MyArea::MyArea()
{
  set_draw_func(sigc::mem_fun(*this, &MyArea::on_draw));
}

MyArea::~MyArea()
{
}

void MyArea::on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height)
{
  const int lesser = std::min(width, height);

  // coordinates for the center of the window
  const int xc = width / 2;
  const int yc = height / 2;

  cr->set_line_width(lesser * 0.02);  // outline thickness changes
                                      // with window size

  // first draw a simple unclosed arc
  cr->save();
  cr->arc(width / 3.0, height / 4.0, lesser / 4.0, -(M_PI / 5.0), M_PI);
  cr->close_path();   // line back to start point
  cr->set_source_rgb(0.0, 0.8, 0.0);
  cr->fill_preserve();
  cr->restore();  // back to opaque black
  cr->stroke();   // outline it

  // now draw a circle
  cr->save();
  cr->arc(xc, yc, lesser / 4.0, 0.0, 2.0 * M_PI); // full circle
  cr->set_source_rgba(0.0, 0.0, 0.8, 0.6);    // partially translucent
  cr->fill_preserve();
  cr->restore();  // back to opaque black
  cr->stroke();

  // and finally an ellipse
  double ex, ey, ew, eh;
  // center of ellipse
  ex = xc;
  ey = 3.0 * height / 4.0;
  // ellipse dimensions
  ew = 3.0 * width / 4.0;
  eh = height / 3.0;

  cr->save();

  cr->translate(ex, ey);  // make (ex, ey) == (0, 0)
  cr->scale(ew / 2.0, eh / 2.0);  // for width: ew / 2.0 == 1.0
                                  // for height: eh / 2.0 == 1.0

  cr->arc(0.0, 0.0, 1.0, 0.0, 2 * M_PI);  // 'circle' centered at (0, 0)
                                          // with 'radius' of 1.0

  cr->set_source_rgba(0.8, 0.0, 0.0, 0.7);
  cr->fill_preserve();
  cr->restore();  // back to opaque black
  cr->stroke();
}

关于这个示例代码有几件事需要注意。首先这个示例与之前的示例的区别也只在on_draw()函数中,因此我们重点接受这个函数。另外,函数的第一部分与之前的也几乎一样,所以我们将会跳过该部分。

请注意,在这个情况下我们几乎使用窗口的宽和高表示了包括线条宽度在内的所有的内容(通过缩放用户坐标系)。所以当我们调整窗口大小的时候,所有的内容都会随着窗口一同缩放。还请注意,函数中有三个进行绘图的部分都用一组save()/restore()包裹,以便我们在每次绘完图都能返回已知状态。

在绘制圆弧的部分介绍了一个新的函数:close_path()。这个函数的效果实际上就是在当前点和路径的第一个点之间画一条直线。但调用close_path()所绘制的线条和使用line_to()手工绘制一条回到起点的线条会有很大的不同。如果你使用close_path(),则这些线将会很好的连接到一起。如果你改用line_to(),那么这些线将在同一个点结束但是开罗不会对此做任何特殊的连接。

逆时针绘图

函数Cairo::Context::arc_negative()的功能与Cairo::Context::arc()函数完全一致,但是他们的绘制方向是相反的。