-
Notifications
You must be signed in to change notification settings - Fork 44
Tutorial 2
Maybe you are frightened to use gtk-fortran because building a GUI is not so easy, or you don't really need it. But you can also make computer graphics in Fortran without any GUI, using the GdkPixbuf library included in GTK.
Simply talking, a pixbuf is a 1D array containing the RGB intensities of each pixel of the image. So pixel(1) is the red intensity (between 0 and 255) of the first pixel (top left), pixel(2) and pixel(3) its green and blue intensities, pixel(4) the red intensity of the next pixel on the same line, and so on. Lines are stored one after the other in the array.
This tutorial is based on the gtk-fortran example pixbuf_without_gui.f90 that we will explain block by block. Note that this example does not depend on your GTK version: we will use only the GdkPixbuf part.
That program just calls some C functions from the GdkPixbuf library to create a pixbuf array, then write values in that array, and finally calls a C function to create the PNG file.
We first state which modules and functions we will use:
program pixbuf_without_gui
use iso_c_binding, only: c_ptr, c_null_char, c_null_ptr, &
& c_f_pointer, c_char, c_int
use gdk_pixbuf, only: gdk_pixbuf_get_n_channels, gdk_pixbuf_get_pixels, &
& gdk_pixbuf_get_rowstride, gdk_pixbuf_new, gdk_pixbuf_savev
use gtk, only: GDK_COLORSPACE_RGB, FALSE
Of course, we need the iso_c_binding
module. But also two gtk-fortran modules: gdk_pixbuf
and gtk
, which are defined in the files src/gdk-pixbuf-auto.f90
, src/gtk.f90
(wich includes src/gtk-auto.inc
and src/gtkenums-auto.inc
). Note that before gtk-fortran 4.2, gdk_pixbuf_savev
was in the gtk_os_dependent
module.
The interfaces defined in those files were automatically generated by the cfwrapper.py
python script, which parses the header files of the GTK libraries in /usr/include/
. But don't bother, it's the maintainer's work!
For example, this is the gdk_pixbuf_new()
interface in src/gdk-pixbuf-auto.f90
:
! GDK_PIXBUF_AVAILABLE_IN_ALL
!GdkPixbuf *gdk_pixbuf_new (GdkColorspace colorspace, gboolean has_alpha, int bits_per_sample, int width, int height);
function gdk_pixbuf_new(colorspace, has_alpha, bits_per_sample, width, height)&
& bind(c)
import :: c_ptr, c_int
type(c_ptr) :: gdk_pixbuf_new
integer(c_int), value :: colorspace
integer(c_int), value :: has_alpha
integer(c_int), value :: bits_per_sample
integer(c_int), value :: width
integer(c_int), value :: height
end function
The first commented line means the function is available in all versions of the library, and is not deprecated. The second commented line is the C prototype of the function from which was derived the Fortran interface.
The bind(c)
statement means that this is an interface to a C function whose name will be the same as the Fortran function. As can be seen in the C prototype in comments, this function will return a C pointer, so we need the c_ptr
type defined in the iso_c_binding
intrinsic module. The arguments are here all integers (including the gboolean
type) and we use the c_int
type. Note also that arguments must be passed by value, except C pointers (and arrays).
We define our variables. Those whose type has the c_
prefix will be arguments to pass to C functions or their results:
implicit none
type(c_ptr) :: my_pixbuf
character(c_char), dimension(:), pointer :: pixel
integer(c_int) :: nch, rowstride, pixwidth, pixheight
integer(c_int) :: cstatus ! Command status
double precision, dimension(1:3) :: x, y
double precision :: xx, yy, diag, r
integer :: s ! Triangle vertex number
integer :: n = 300000 ! Number of points
integer :: i, p
pixel
is a Fortran pointer toward a C array. We use characters because we need one byte unsigned integers.
This pixbuffer has no Alpha channel, only RGB. Those functions were already explained in the Tutorial 1:
pixwidth = 800
pixheight = 800
my_pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8_c_int, &
& pixwidth, pixheight)
nch = gdk_pixbuf_get_n_channels(my_pixbuf)
rowstride = gdk_pixbuf_get_rowstride(my_pixbuf)
print *, "Channels= ", nch, " Rowstride=", rowstride
call c_f_pointer(gdk_pixbuf_get_pixels(my_pixbuf), pixel, &
& (/pixwidth*pixheight*nch/))
pixel = char(0)
The last line means the background will be black (intensity 0 for red, green, blue).
The algorithm used to plot the Sierpinski triangle is very simple:
- start from any point in the plan.
- Choose randomly one vertex of the triangle.
- Compute the middle between the current point and that vertex, and light the corresponding pixel (the new current point).
- Go to 2.
For more information, see:
https://en.wikipedia.org/wiki/Sierpi%C5%84ski_triangle#Chaos_game
With the comments, you will easily understand the algorithm:
! Diagonal of the image:
diag = sqrt(real(pixwidth*pixwidth + pixheight*pixheight, kind(0d0)))
! Coordinates of the triangle vertices:
x = (/ pixwidth/2d0, 0d0, (pixwidth-1)*1d0 /)
y = (/ 0d0, pixheight*sqrt(3d0)/2d0, pixheight*sqrt(3d0)/2d0 /)
! We start at an arbitrary position:
xx = (x(1) + x(2)) / 2d0
yy = (y(1) + y(2)) / 2d0
do i = 1, n
! We choose randomly a vertex number (1, 2 or 3):
call random_number(r)
s = 1 + int(3*r)
! We compute the coordinates of the new point:
xx = (xx + x(s)) / 2d0
yy = (yy + y(s)) / 2d0
! Position of the corresponding pixel in the pixbuffer:
p = 1 + nint(xx)*nch + nint(yy)*rowstride
! Red, Green, Blue values computed from the distances to vertices:
pixel(p) = char(int(255 * sqrt((xx-x(1))**2 + (yy-y(1))**2) / diag))
pixel(p+1) = char(int(255 * sqrt((xx-x(2))**2 + (yy-y(2))**2) / diag))
pixel(p+2) = char(int(255 * sqrt((xx-x(3))**2 + (yy-y(3))**2) / diag))
end do
p
is the position in the 1D array of the red byte of the (xx,yy) pixel. The red, green, blue values are computed to obtain beautiful colour gradients.
We just need to call the gdk_pixbuf_savev()
function:
https://docs.gtk.org/gdk-pixbuf/method.Pixbuf.savev.html
cstatus = gdk_pixbuf_savev(my_pixbuf, "sierpinski_triangle.png"//c_null_char,&
& "png"//c_null_char, c_null_ptr, c_null_ptr, c_null_ptr)
end program pixbuf_without_gui
It's also possible to use other formats like JPEG, but you may need more work to pass the arguments (compression ratio, etc.) PNG is easier, you just need to pass the "png" string and null pointer values.
Let's compile and run the program:
$ gfortran pixbuf_without_gui.f90 $(pkg-config --cflags --libs gtk-4-fortran) && ./a.out
Channels= 3 Rowstride= 2400
As expected, the number of channels is 3 (RGB) and the rowstride is 2400 (3 bytes x 800 pixels). But you should always use the gdk_pixbuf_get_rowstride()
function because the documentation says: "There may be padding at the end of a row. The 'rowstride' value of a pixbuf, as returned by gdk_pixbuf_get_rowstride(), indicates the number of bytes between rows."
No window opens, but you will find a new sierpinski_triangle.png
file in the same directory:
Congratulations! In 45 lines of Fortran code you have created a beautiful picture of a fractal object and saved it to a PNG file. If you don't need a Graphical User Interface and just want to obtain a picture of your scientific results, you now have a template!
- Installation
- My first gtk-fortran application
- Drawing an image in a PNG file (without GUI)
- A program also usable without GUI
- Using Glade3 and gtkf-sketcher (GTK 3)
- Using gtk-fortran as a fpm dependency
- Debugging with GtkInspector
- Learning from examples
- Video tutorials
- How to start my own project from a gtk-fortran example
- git basics
- CMake basics
- Alternatives to CMake
- How to migrate to GTK 4
- How to contribute to gtk-fortran
- How to hack the cfwrapper