Add section 19.

This commit is contained in:
Toshio Sekiya 2021-01-22 22:14:05 +09:00
parent 9847c5fcd7
commit 7f1228cb26
11 changed files with 399 additions and 4 deletions

2
.gitignore vendored
View file

@ -6,6 +6,8 @@ src/img.rb
src/imgsize.rb
src/toi.rb
src/misc/a.out
src/misc/cairo2.c
src/misc/cairo2.pdf
src/tfv/a.out
src/tfe/a.out
src/tfe/hello.txt

View file

@ -201,9 +201,9 @@ end
end
end
task html: htmlfilenames+["html/index.html"]
task html: ["html/index.html"]
file "html/index.html" do
file "html/index.html" => htmlfilenames do
0.upto(srcfiles.size-1) do |i|
h = File.open(srcfiles[i].path) { |file| file.readline }
h = h.gsub(/^#* */,"").chomp
@ -249,9 +249,9 @@ task pdf: "latex" do
sh "mv latex/main.pdf latex/gtk4_tutorial.pdf"
end
task latex: texfilenames+["latex/main.tex"]
task latex: ["latex/main.tex"]
file "latex/main.tex" do
file "latex/main.tex" => texfilenames do
0.upto(srcfiles.size-1) do |i|
main += " \\input{#{srcfiles[i].to_tex}}\n"
end

View file

@ -31,3 +31,4 @@ You can read it without download.
1. [Menu and action](gfm/sec16.md)
1. [Stateful action](gfm/sec17.md)
1. [Ui file for menu and action entries](gfm/sec18.md)
1. [GtkDrawingArea and Cairo](gfm/sec19.md)

197
gfm/sec19.md Normal file
View file

@ -0,0 +1,197 @@
Up: [Readme.md](../Readme.md), Prev: [Section 18](sec18.md)
# GtkDrawingArea and Cairo
If you want to draw dynamically, like an image window of gimp graphics editor, GtkDrawingArea widget is the most suitable widget.
You can draw or redraw an image in this widget freely.
It is called custom drawing.
GtkDrawingArea provides a cairo context so users can draw images by cairo functions.
In this section, I will explain:
1. Cairo, but briefly.
2. GtkDrawingArea with very simple example.
## Cairo
Cairo is a two dimensional graphics library.
First, you need to know surface, source, mask, destination, cairo context and transformation.
- surface represents an image.
It is like a canvas.
We can draw shapes and images with different colors on surfaces.
- source pattern, or simply source, is a kind of paint, which will be transfered to destination surface by cairo functions.
- mask is image mask used in the transference.
- destination is a target surface.
- cairo context manages the transference from source to destination through mask with its functions.
For example, `cairo_stroke` is a function to draw a path to the destination by the transference.
- transformation is applied before the transfer completes.
The transformation is called affine, which is a mathematicsterminology, and represented by matrix multiplication and vector addition.
Scaling, rotation, reflection, shearing and translation are examples of affine transformation.
In this section, we don't use it.
That means we only use identity transformation.
Therefore, the coordinate in source and mask is the same as the coordinate in destination.
![Stroke a rectangle](../image/cairo.png)
The instruction is as follows:
1. Create a surface.
This will be a destnation.
2. Create a cairo context with the surface and set it to the destination.
3. Create a source pattern within the context.
4. Create paths, which are lines, rectangles, arcs, texts or more complicated shapes, to generate a mask.
5. Use drawing operator such as `cairo_stroke` to transfer the paint in the source to the destination.
6. Save the destination surface to a file if necessary.
Here's a simple example code that draws a small square and save it as a png file.
1 #include <cairo.h>
2
3 int
4 main (int argc, char **argv)
5 {
6 cairo_surface_t *surface;
7 cairo_t *cr;
8 int width = 100;
9 int height = 100;
10
11 /* Generate surface and cairo */
12 surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, height);
13 cr = cairo_create (surface);
14
15 /* Drawing starts here. */
16 /* Paint the background white */
17 cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
18 cairo_paint (cr);
19 /* Draw a black rectangle */
20 cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
21 cairo_set_line_width (cr, 2.0);
22 cairo_rectangle (cr, width/2.0 - 20.0, height/2.0 - 20.0, 40.0, 40.0);
23 cairo_stroke (cr);
24
25 /* Write the surface to a png file and clean up cairo and surface. */
26 cairo_surface_write_to_png (surface, "rectangle.png");
27 cairo_destroy (cr);
28 cairo_surface_destroy (surface);
29
30 return 0;
31 }
- 1: Include the header file of cairo.
- 12: `cairo_image_surface_create` creates an image surface.
`CAIRO_FORMAT_RGB24` is a constant which means that each pixel has red, green and blue data.
Each data has 8 bit quantity.
Modern displays have this type of color depth.
Width and hieight are pixels and given as integers.
- 13: Create cairo context.
The surface given as an argument will be set to the destination of the context.
- 17: `cairo_set_source_rgb` creates a source pattern, which is a solid white paint.
The second to fourth argument is red, green and blue color depth respectively.
Their type is float and the values are between zero and one.
(0,0,0) is black and (1,1,1) is white.
- 18: `cairo_paint` copies everywhere in the source to destination.
The destination is filled with white pixels by this command.
- 20: Set the source color to black.
- 21: `cairo_set_line_width` set the width of lines.
In this case, the line width is set to two pixels.
(It is because the transformation is identity.
If we set it different one, for example scaling with the factor three, the actual width in destnation is six (2x3=6) pixels.)
- 22: Draw a rectangle (square).
The top-left coordinate is (width/2.0-20.0, height/2.0-20.0) and the width and height have the same length 40.0.
- 23: `cairo_stroke` transfer the source to destnation through the rectangle in mask.
- 26: Output the image to a png file `rectangle.png`.
- 27: Destroy the context. At the same time the source is destroyed.
- 28: Destroy the destnation surface.
To compile this, type the following.
$ gcc `pkg-config --cflags cairo` cairo.c `pkg-config --libs cairo`
![rectangle.png](../src/misc/rectangle.png)
There are lots of documentations in [Cairo's website](https://www.cairographics.org/).
If you aren't familiar with cairo, it is strongly recommended to read the [tutorial](https://www.cairographics.org/tutorial/) in the website.
## GtkDrawingArea
The following is a very simple example.
1 #include <gtk/gtk.h>
2
3 static void
4 draw_function (GtkDrawingArea *area, cairo_t *cr, int width, int height, gpointer user_data) {
5 cairo_set_source_rgb (cr, 1.0, 1.0, 1.0); /* whilte */
6 cairo_paint (cr);
7 cairo_set_line_width (cr, 2.0);
8 cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); /* black */
9 cairo_rectangle (cr, width/2.0 - 20.0, height/2.0 - 20.0, 40.0, 40.0);
10 cairo_stroke (cr);
11 }
12
13 static void
14 on_activate (GApplication *app, gpointer user_data) {
15 GtkWidget *win = gtk_application_window_new (GTK_APPLICATION (app));
16 GtkWidget *area = gtk_drawing_area_new ();
17
18 gtk_window_set_title (GTK_WINDOW (win), "da1");
19 /* Set initial size of width and height */
20 gtk_drawing_area_set_content_width (GTK_DRAWING_AREA (area), 100);
21 gtk_drawing_area_set_content_height (GTK_DRAWING_AREA (area), 100);
22 gtk_drawing_area_set_draw_func (GTK_DRAWING_AREA (area), draw_function, NULL, NULL);
23 gtk_window_set_child (GTK_WINDOW (win), area);
24
25 gtk_widget_show (win);
26 }
27
28 int
29 main (int argc, char **argv) {
30 GtkApplication *app;
31 int stat;
32
33 app = gtk_application_new ("com.github.ToshioCP.da1", G_APPLICATION_FLAGS_NONE);
34 g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
35 stat =g_application_run (G_APPLICATION (app), argc, argv);
36 g_object_unref (app);
37 return stat;
38 }
39
The function `main` is almost same as before.
The two functions `on_activate` and `draw_function` is important in this example.
- 16: Generate a GtkDrawingArea object.
- 20,21: Set the width and height of the contents of the GtkDrawingArea widget.
These width and height is the size of the destination surface of the cairo context provided by the widget.
- 22: Set a drawng function to the widget.
GtkDrawingArea widget uses the function to draw the contents of itself whenever its necessary.
For example, when a user drag a mouse pointer and resize a top level window, GtkDrawingArea also changes the size.
Then, the whole window needs to be redrawn.
The drawing function has five parameters.
void drawing_function (GtkDrawingArea *drawing_area, cairo_t *cr, int width, int height,
gpointer user_data);
The first parameter is the GtkDrawingArea widget which calls the drawing function.
However, you can't change any properties, for example `content-width` or `content-height`, in this function.
The second parameter is a cairo context given by the widget.
The destnation surface of the context is connected to the contents of the widget.
What you draw to this surface will appear in the widget on the screen.
The third and fourth paranmeters are the size of the destination surface.
- 3-11: The drawing function.
- 4-5: Set the source to be white and paint the destination white.
- 7: Set the line width to be 2.
- 8: Set the source to be black.
- 9: Add a rectangle to the mask.
- 10: Draw the rectangle with black color to the destination.
Compile and run it, then a window with a black rectangle (square) appears.
Try resizing the window.
The square always appears at the center of the window because the drawing function is invoked every moment the window is resized.
![Square in the window](../image/da1.png)
Up: [Readme.md](../Readme.md), Prev: [Section 18](sec18.md)

BIN
image/cairo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
image/cairo.xcf Normal file

Binary file not shown.

BIN
image/da1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

31
src/misc/cairo.c Normal file
View file

@ -0,0 +1,31 @@
#include <cairo.h>
int
main (int argc, char **argv)
{
cairo_surface_t *surface;
cairo_t *cr;
int width = 100;
int height = 100;
/* Generate surface and cairo */
surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, height);
cr = cairo_create (surface);
/* Drawing starts here. */
/* Paint the background white */
cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
cairo_paint (cr);
/* Draw a black rectangle */
cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
cairo_set_line_width (cr, 2.0);
cairo_rectangle (cr, width/2.0 - 20.0, height/2.0 - 20.0, 40.0, 40.0);
cairo_stroke (cr);
/* Write the surface to a png file and clean up cairo and surface. */
cairo_surface_write_to_png (surface, "rectangle.png");
cairo_destroy (cr);
cairo_surface_destroy (surface);
return 0;
}

39
src/misc/da1.c Normal file
View file

@ -0,0 +1,39 @@
#include <gtk/gtk.h>
static void
draw_function (GtkDrawingArea *area, cairo_t *cr, int width, int height, gpointer user_data) {
cairo_set_source_rgb (cr, 1.0, 1.0, 1.0); /* whilte */
cairo_paint (cr);
cairo_set_line_width (cr, 2.0);
cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); /* black */
cairo_rectangle (cr, width/2.0 - 20.0, height/2.0 - 20.0, 40.0, 40.0);
cairo_stroke (cr);
}
static void
on_activate (GApplication *app, gpointer user_data) {
GtkWidget *win = gtk_application_window_new (GTK_APPLICATION (app));
GtkWidget *area = gtk_drawing_area_new ();
gtk_window_set_title (GTK_WINDOW (win), "da1");
/* Set initial size of width and height */
gtk_drawing_area_set_content_width (GTK_DRAWING_AREA (area), 100);
gtk_drawing_area_set_content_height (GTK_DRAWING_AREA (area), 100);
gtk_drawing_area_set_draw_func (GTK_DRAWING_AREA (area), draw_function, NULL, NULL);
gtk_window_set_child (GTK_WINDOW (win), area);
gtk_widget_show (win);
}
int
main (int argc, char **argv) {
GtkApplication *app;
int stat;
app = gtk_application_new ("com.github.ToshioCP.da1", G_APPLICATION_FLAGS_NONE);
g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
stat =g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);
return stat;
}

BIN
src/misc/rectangle.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 B

125
src/sec19.src.md Normal file
View file

@ -0,0 +1,125 @@
# GtkDrawingArea and Cairo
If you want to draw dynamically, like an image window of gimp graphics editor, GtkDrawingArea widget is the most suitable widget.
You can draw or redraw an image in this widget freely.
It is called custom drawing.
GtkDrawingArea provides a cairo context so users can draw images by cairo functions.
In this section, I will explain:
1. Cairo, but briefly.
2. GtkDrawingArea with very simple example.
## Cairo
Cairo is a two dimensional graphics library.
First, you need to know surface, source, mask, destination, cairo context and transformation.
- surface represents an image.
It is like a canvas.
We can draw shapes and images with different colors on surfaces.
- source pattern, or simply source, is a kind of paint, which will be transfered to destination surface by cairo functions.
- mask is image mask used in the transference.
- destination is a target surface.
- cairo context manages the transference from source to destination through mask with its functions.
For example, `cairo_stroke` is a function to draw a path to the destination by the transference.
- transformation is applied before the transfer completes.
The transformation is called affine, which is a mathematicsterminology, and represented by matrix multiplication and vector addition.
Scaling, rotation, reflection, shearing and translation are examples of affine transformation.
In this section, we don't use it.
That means we only use identity transformation.
Therefore, the coordinate in source and mask is the same as the coordinate in destination.
![Stroke a rectangle](../image/cairo.png)
The instruction is as follows:
1. Create a surface.
This will be a destnation.
2. Create a cairo context with the surface and set it to the destination.
3. Create a source pattern within the context.
4. Create paths, which are lines, rectangles, arcs, texts or more complicated shapes, to generate a mask.
5. Use drawing operator such as `cairo_stroke` to transfer the paint in the source to the destination.
6. Save the destination surface to a file if necessary.
Here's a simple example code that draws a small square and save it as a png file.
@@@ misc/cairo.c
- 1: Include the header file of cairo.
- 12: `cairo_image_surface_create` creates an image surface.
`CAIRO_FORMAT_RGB24` is a constant which means that each pixel has red, green and blue data.
Each data has 8 bit quantity.
Modern displays have this type of color depth.
Width and hieight are pixels and given as integers.
- 13: Create cairo context.
The surface given as an argument will be set to the destination of the context.
- 17: `cairo_set_source_rgb` creates a source pattern, which is a solid white paint.
The second to fourth argument is red, green and blue color depth respectively.
Their type is float and the values are between zero and one.
(0,0,0) is black and (1,1,1) is white.
- 18: `cairo_paint` copies everywhere in the source to destination.
The destination is filled with white pixels by this command.
- 20: Set the source color to black.
- 21: `cairo_set_line_width` set the width of lines.
In this case, the line width is set to two pixels.
(It is because the transformation is identity.
If we set it different one, for example scaling with the factor three, the actual width in destnation is six (2x3=6) pixels.)
- 22: Draw a rectangle (square).
The top-left coordinate is (width/2.0-20.0, height/2.0-20.0) and the width and height have the same length 40.0.
- 23: `cairo_stroke` transfer the source to destnation through the rectangle in mask.
- 26: Output the image to a png file `rectangle.png`.
- 27: Destroy the context. At the same time the source is destroyed.
- 28: Destroy the destnation surface.
To compile this, type the following.
$ gcc `pkg-config --cflags cairo` cairo.c `pkg-config --libs cairo`
![rectangle.png](misc/rectangle.png)
There are lots of documentations in [Cairo's website](https://www.cairographics.org/).
If you aren't familiar with cairo, it is strongly recommended to read the [tutorial](https://www.cairographics.org/tutorial/) in the website.
## GtkDrawingArea
The following is a very simple example.
@@@ misc/da1.c
The function `main` is almost same as before.
The two functions `on_activate` and `draw_function` is important in this example.
- 16: Generate a GtkDrawingArea object.
- 20,21: Set the width and height of the contents of the GtkDrawingArea widget.
These width and height is the size of the destination surface of the cairo context provided by the widget.
- 22: Set a drawng function to the widget.
GtkDrawingArea widget uses the function to draw the contents of itself whenever its necessary.
For example, when a user drag a mouse pointer and resize a top level window, GtkDrawingArea also changes the size.
Then, the whole window needs to be redrawn.
The drawing function has five parameters.
void drawing_function (GtkDrawingArea *drawing_area, cairo_t *cr, int width, int height,
gpointer user_data);
The first parameter is the GtkDrawingArea widget which calls the drawing function.
However, you can't change any properties, for example `content-width` or `content-height`, in this function.
The second parameter is a cairo context given by the widget.
The destnation surface of the context is connected to the contents of the widget.
What you draw to this surface will appear in the widget on the screen.
The third and fourth paranmeters are the size of the destination surface.
- 3-11: The drawing function.
- 4-5: Set the source to be white and paint the destination white.
- 7: Set the line width to be 2.
- 8: Set the source to be black.
- 9: Add a rectangle to the mask.
- 10: Draw the rectangle with black color to the destination.
Compile and run it, then a window with a black rectangle (square) appears.
Try resizing the window.
The square always appears at the center of the window because the drawing function is invoked every moment the window is resized.
![Square in the window](../image/da1.png)