"C++ Programming Principles and Practice" Notes Chapter 12 A Display Model

This chapter introduces a display model (the output part of a GUI), and gives usage examples and basic concepts such as screen coordinates, lines, and colors.

12.1 Why do we need graphics?

Why do we devote four chapters to graphics and one chapter to GUIs?

  • Graphics are useful. For example, scientific computing and data analysis require data graphics.
  • Graphics are fun. The effect of a piece of code can be displayed immediately.
  • Graphics provide a lot of interesting code to read. Part of learning to program is reading a lot of code and getting a feel for good code.
  • Graphical design examples are plentiful. The graphics field is rich with concrete, practical examples of design decisions and design techniques.
  • Graphics are useful for explaining object -oriented programming concepts and language features. Graphics provide some very easy-to-understand examples of object-oriented design.
  • Some key graphics concepts are not straightforward.

12.2 A display model

The iostream library is a character-oriented input/output stream. This chapter and the next four introduce another technology: graphics and the graphical user interface (GUI), which can be displayed directly on the computer screen. Graphics include shapes such as points, lines, rectangles, and circles , and GUIs include widgets such as windows, buttons, and input boxes .

The basic model is as follows: we use the basic graphics (such as lines) provided by the graphics library to combine more complex graphics (such as polygons); then attach these graphics objects to a window object representing the physical screen; finally, the display engine ( graphics library/GUI library) to draw these graphics on the screen.

display model

12.3 First example

The following program draws a triangle on the screen:

draw triangle

The result of the operation is as follows:

draw_triangle

Let's analyze this program line by line. First include the (author provided) graphics library header file:

#include "Graph.h"          // get access to our graphics library facilities
#include "Simple_window.h"  // get access to our window library

Next, tell the compiler that graphics-related classes are in the namespace Graph_lib:

using namespace Graph_lib;  // our graphics facilities are in Graph_lib

Then, main()create a window in win:

Point tl(100, 100);             // to become top left corner of window
Simple_window win(tl, 600, 400, "Canvas");  // make a simple window

Among them, tlit represents the coordinates of the upper left corner of the window on the screen, the width and height are 600 and 400 pixels respectively, and the string "Canvas" will be displayed in the title bar of the upper left corner of the window.

Next, create a polygon object polyand add three points to get a triangle:

Graph_lib::Polygon poly;        // make a shape (a polygon)

poly.add(Point(300, 200));      // add a point
poly.add(Point(350, 100));      // add another point
poly.add(Point(400, 200));      // add a third point

The points here represent coordinates within the window.

Then set the sides of the triangle to red (purely for display):

poly.set_color(Color::red);     // adjust properties of poly

Finally, polyattach to the window win:

win.attach(poly);               // connect poly to the window

Only then will the graph be drawn. the last line of the program

win.wait_for_button();          // give control to the display engine

wait_for_button()Gives control to the GUI system, and tells the GUI system to display the window (while drawing graphics attached to it). After that, it will wait for the user to click the ( Simple_windowincluded) "Next" button. When the button is clicked, the program will terminate and close the window.

The OS adds a title bar/frame to each window, so you get the three buttons in the upper right corner "for free".

12.4 Using the GUI library

To write GUI programs, you must use a GUI library (used to call the underlying interface of the operating system to draw windows and graphics). However, C++ does not provide a standard GUI library, so the author chooses one of the many available C++ GUI libraries .

The GUI library used in this book is called FLTK (Fast Light Toolkit, pronounced "fulltick") and can be downloaded from fltk.org . Our code is portable to any platform that uses FLTK (Windows, Linux, macOS, etc.).

Note: The code in the book does not use FLTK directly, but uses the GUI library provided by the author. This GUI library is another layer encapsulated on the basis of FLTK, which provides interface classes for shapes and GUI components (if there is no special description, the "GUI library" mentioned later refers to the GUI library provided by the author).

The source code of the GUI library can be downloaded from the author's website : https://www.stroustrup.com/Programming/code.tar

Note: There are several compilation errors in the code, which need to be modified as follows

  • Window.h: Add two #includeand two usingdeclarations at the beginning:
#include <string>
#include <vector>
// ...

using std::string;
using std::vector;
// ...
  • Simple_window.h: struct Simple_window : Windowchange tostruct Simple_window : Graph_lib::Window
  • Graph.h: Vector_ref(const Vector<T>&);and Vector_ref& operator=(const Vector<T>&);in VectortheVector_ref

This GUI library consists of 5 header files and 4 source files:

  • head File
    • Point.h
    • Window.h
    • Simple_window.h
    • Graph.h
    • GUI.h
  • Source File
    • Window.cpp
    • Simple_window.cpp
    • Graph.cpp
    • GUI.cpp

The inclusion relationship between header files is as follows:

GUI library header files

Among them, Window.h, Simple_window.h and GUI.h provide GUI components such as Window, Simple_windowand and so on. The class diagram is as follows:Button

GUI library windows and widget classes

Graph.h provides shape classes Shapeand subclasses such as Line, Rectangle, and so on. The class diagram is as follows:Circle

GUI library graphics class

12.8 Getting the program running

Note: Like Chapter 6, the author of this chapter does not talk about how to make the program run until the last section (the text does not explain in detail, only appendix D is mentioned, and Appendix D only introduces the use of Visual Studio. method). This is actually the most difficult part for beginners. The build method varies on different operating systems and using different build tools, but basically includes the following steps:

  • Compile the FLTK source code and generate the FLTK library file;
  • Compile the GUI library source code and generate the GUI library file;
  • Compile our own source code and link to GUI library and FLTK library to generate executable file.

The following two construction methods using Make and CMake are introduced in detail.

12.8.1 Using Make

12.8.1.1 Install FLTK

This section uses the Make command to compile and install the FLTK library on the Ubuntu system.

First download the source code (eg fltk-1.3.8-source.tar.gz) from the FLTK official website , and execute the following command after decompression:

cd fltk-1.3.8/
./configure --prefix=/usr/local
make
sudo make install
sudo ldconfig

Among them, configurethe command is used to generate the build configuration file, --prefixthe option specifies the installation path, the header file will be copied to the $prefix/include directory, and the library file will be copied to the $prefix/lib directory. If the error "Configure could not find required X11 libraries, aborting" is reported, you need to install the X11 library first:

sudo apt-get install libx11-dev

makeThe command compiles the source code into a library file, which is generated in the fltk-1.3.8/lib directory; make installthe command copies the header file, compiled library files and executable files to --prefixthe directory specified by the option (here /usr/local) :

$ cd /usr/local
$ ls bin/
fltk-config  fluid  ...

$ ls include/
FL  ...

$ ls lib/
lib/libfltk.a        lib/libfltk_images.a  lib/libfltk_png.a
lib/libfltk_forms.a  lib/libfltk_jpeg.a    lib/libfltk_z.a
...

Among them, fltk-config is an auxiliary program that can automatically generate compilation and linking options:

$ fltk-config --cxx
g++

$ fltk-config --cxxflags
-I/usr/local/include -I/usr/local/include/FL/images -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_THREAD_SAFE -D_REENTRANT

$ fltk-config --ldflags
-L/usr/local/lib -lfltk -lpthread -ldl -lm -lX11

For example, here is the Hello World program in the official FLTK document Writing Your First FLTK Program :

#include <FL/Fl.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Window.H>

int main(int argc, char* argv[]) {
    
    
    Fl_Window* window = new Fl_Window(340, 180);
    Fl_Box* box = new Fl_Box(20, 40, 300, 100, "Hello, World!");
    box->box(FL_UP_BOX);
    box->labelfont(FL_BOLD_ITALIC);
    box->labelsize(36);
    box->labeltype(FL_SHADOW_LABEL);
    window->end();
    window->show(argc, argv);
    return Fl::run();
}

The program can be compiled with the following command:

g++ `fltk-config --cxxflags` -o hello_fltk hello_fltk.cpp `fltk-config --ldflags`

This command is equivalent to

g++ -I/usr/local/include -I/usr/local/include/FL/images -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_THREAD_SAFE -D_REENTRANT -o hello_fltk hello_fltk.cpp -L/usr/local/lib -lfltk -lpthread -ldl -lm -lX11

The result of the operation is as follows:

hello_fltk

In addition, fltk-config also provides a more convenient command:

fltk-config --compile hello_fltk.cpp

This command automatically combines compile options and link options into one compile command and executes it, which is also equivalent to the above compile command.

-INote: Since /usr/local/include and /usr/local/lib are in the default header file and library file search path list, the and options in the compilation command can be omitted, thus simplifying -Lto

g++ -o hello_fltk hello_fltk.cpp -lfltk -lpthread -ldl -lm -lX11

To uninstall FLTK, execute in the fltk-1.3.8 directory

sudo make uninstall

reference:

12.8.1.2 Compile the GUI library

After installing FLTK, compile the GUI library source code provided by the author into a library file.

First download the source code code.tar , extract the GUI directory, and correct compilation errors according to the method in section 12.4. Since the source code provided by the author already contains the Makefile, execute the command directly in the GUI directory maketo build the library file libbookgui.a, which is in the same directory as the source code.

12.8.1.3 Compile your own program code

Suppose the program in section 12.3 is saved in ch12/draw_triangle.cpp, and the directory ch12 and GUI are in the same directory:

PPP-code/
    GUI/
        Window.h
        Graph.h
        ...
        libbookgui.a
    ch12/
        draw_triangle.cpp

Execute the following command in the ch12 directory:

g++ -I../GUI -o draw_triangle draw_triangle.cpp -L../GUI -lbookgui `fltk-config --ldflags --use-images`

The executable file draw_triangle can be obtained. Among them, -I../GUIthe option tells the compiler the search path of the GUI header file, so that #include "Graph.h"it can find Graph.h in the GUI directory; -L../GUIthe option tells the linker the search path of the GUI library file, so that -lbookguiit can find libbookgui.a in the GUI directory; fltk-config --ldflags --use-imagesit will automatically generate the required Linked FLTK library.

The result of the operation is as follows:

draw_triangle

12.8.2 Using CMake (recommended)

CMake is an open source, cross-platform C++ build tool, and the new version of FLTK already supports CMake build. Building with CMake has the following advantages over Make:

  • Simple, no need to manually write complex compilation commands
  • There is no need to manually install FLTK, the module using CMake FetchContentcan automatically download and build the FLTK library
  • CMake is cross-platform, so it works on Windows, Linux and macOS systems
  • Not only can be used on the command line, any IDE that supports CMake (such as CLion, Visual Studio, etc.) is applicable

For the installation of CMake, please refer to the CMake build tool usage tutorial . CMake declares build targets using a file called CMakeLists.txt. Create a CMakeLists.txt file in the project root directory, GUI directory and ch12 directory:

PPP-code/
    CMakeLists.txt
    GUI/
        CMakeLists.txt
        Window.h
        Graph.h
        ...
    ch12/
        CMakeLists.txt
        draw_triangle.cpp

The content of CMakeLists.txt in the root directory is as follows:

cmake_minimum_required(VERSION 3.20)
project(PPP_code)

set(CMAKE_CXX_STANDARD 14)

include(FetchContent)
set(FLTK_BUILD_TEST OFF CACHE BOOL " " FORCE)
FetchContent_Declare(
  fltk
  GIT_REPOSITORY https://github.com/fltk/fltk.git
  GIT_TAG release-1.3.8
)
FetchContent_MakeAvailable(fltk)

add_subdirectory(GUI)
add_subdirectory(ch12)

Among them, FetchContent_Declare()the command declares the fltk library and its Git warehouse address, and FetchContent_MakeAvailable()the source code of the fltk library will be downloaded automatically.

The content of GUI/CMakeLists.txt is as follows:

file(GLOB SOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.cpp)
add_library(GUI ${SOURCES})
target_link_libraries(GUI fltk fltk_images)
target_include_directories(GUI PUBLIC ${fltk_SOURCE_DIR} ${fltk_BINARY_DIR})
target_include_directories(GUI INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})

Here a library file GUI is declared and linked to the fltk and fltk_images libraries.

The content of ch12/CMakeLists.txt is as follows:

add_executable(draw_triangle draw_triangle.cpp)
target_link_libraries(draw_triangle GUI)

Here an executable draw_triangle is declared and linked to the GUI library (CMake will automatically handle transitive dependencies and link it to the fltk library).

Execute the following command in the project root directory:

cmake -G "Unix Makefiles" -B cmake-build
cmake --build cmake-build --target draw_triangle

CMake will automatically download and build the FLTK library during the first build. After the build is complete, the executable file cmake-build/ch12/draw_triangle can be obtained, and the same effect as above can be obtained by running it.

Note: The method given here is the method used by my own code warehouse PPP-code .

reference:

12.8.3 Using Visual Studio

reference:

12.5 Coordinate system

A computer screen is a rectangular area made up of pixels . A pixel is a point that can be set to a certain color. The most common way to model a screen in a program is a rectangle composed of pixels, each pixel is determined by an x ​​(horizontal) coordinate and a y (vertical) coordinate, and the upper left corner of the screen is the coordinate origin, as shown in the figure below.

screen coordinate system

Unlike the coordinate system in mathematics, the y coordinate grows downward.

In a GUI program, a window is a rectangular area divided from the screen and controlled by the program. A window can be thought of as a small screen (the coordinate system inside the window is modeled the same way as a screen). For example:

Simple_window win(tl, 600, 400, "Canvas");

Create a window with a width of 600 pixels and a height of 400 pixels (excluding the title bar), so that the x coordinates in the window are 0~599 from left to right, and the y coordinates are 0~399 from top to bottom. The area of ​​the window that can be drawn is often called the canvas.

12.6 Shape

The header file Graph.h of the GUI library defines a set of shape classes, see section 12.4.

12.7 Using shape classes

This section introduces some basic features of the GUI library: Simple_window, Window, Shape, Text, , Polygon, Line, Lines, Rectangle, Color, Line_style, Point, AxisThe purpose is to give you a comprehensive understanding of these features, rather than a detailed understanding of a certain class. The next chapter will introduce the design of each class.

12.7.1 Graphics header file and main function

First you need to include the header file of the GUI library:

#include "Graph.h"
#include "Window.h"

or

#include "Graph.h"
#include "Simple_window.h"

Window.h contains features related to windows, and Graph.h contains features related to drawing shapes (including text) on windows. These properties are defined in Graph_liba namespace. For simplicity, the use usingdirective makes the program use Graph_libthe names in directly:

using namespace Graph_lib;

12.7.2 An almost blank window

Point tl(100, 100);     // top left corner of our window
Simple_window win(tl, 600, 400, "Canvas");
// screen coordinate tl for top left corner
// window size(600*400)
// title: Canvas
win.wait_for_button();  // display!

This code creates a Simple_windowwindow with a "Next" button, upper left corner at (100, 100), width and height 600 and height 400, titled "Canvas". The control is given to the GUI system by calling win.wait_for_button(), so that the window is drawn on the screen, as shown in the figure below.

almost blank window

12.7.3 Axes

Axis xa(Axis::x, Point(20, 300), 280, 10, "x axis");    // make an Axis
win.attach(xa);     // attach xa to the window, win
Axis ya(Axis::y, Point(20, 300), 280, 10, "y axis");
ya.set_color(Color::cyan);              // choose a color
ya.label.set_color(Color::dark_red);    // choose a color for the text
win.attach(ya);

This code creates an x-axis ( Axis::x) and a y-axis ( Axis::y) with an origin at (20, 300) (the window coordinate system), a length of 280, and 10 ticks. win.attach()Attach the axis object to the window by calling it , so that it can be drawn.

Axis

12.7.4 Plotting function graphs

Let's plot a sine function graph:

Function sine(sin, 0, 100, Point(20, 150), 1000, 50, 50);   // sine curve
win.attach(sine);

Among them, the object sinenamed uses the function value generated by the standard library function to draw a sine curve, the independent variable range is [0, 100), and (20, 150) is used as the "origin", a total of 1000 points are drawn, and the x and y coordinates Functionare sin()Zoom 50 times.

function

Note that points outside the rectangular area of ​​the window will be ignored by the GUI system.

12.7.5 Polygons

A polygon Polygonis described as a series of points, connected by lines:

Polygon poly;
poly.add(Point(300, 200));      // three points make a triangle
poly.add(Point(350, 100));
poly.add(Point(400, 200));
poly.set_color(Color::red);
poly.set_style(Line_style::dash);
win.attach(poly);

As in the example in Section 12.3, we add a triangle as an example of a polygon and set the color and linetype. All shapes have a line type, which is solid ( Line_style::solid) by default, and dashed ( Line_style::dash) here.

polygon

12.7.6 Rectangle

Rectangles are the easiest shapes to work with. For example, rectangles are easy to describe (upper-left corner, width and height, or upper-left and lower-right corners, etc.), easy to tell whether a point is inside a rectangle, and easy to quickly draw a rectangle of pixels in hardware.

Most graphics libraries handle rectangles better than other closed shapes. Therefore, we provide a class independent Polygonof the class Rectangle. RectangleDescribed by upper left corner, width and height:

Graph_lib::Rectangle r(Point(200, 200), 100, 50);   // top left corner, width, height
win.attach(r);

rectangle

One can create one that looks like a rectangle Closed_polyline:

Closed_polyline poly_rect;
poly_rect.add(Point(100, 50));
poly_rect.add(Point(200, 50));
poly_rect.add(Point(200, 100));
poly_rect.add(Point(100, 100));
win.attach(poly_rect);

closed polygon

Although poly_rectthe drawn result is a rectangle, it is not an object in memory Rectangle. The easiest way to prove it is to add one more point:

poly_rect.add(Point(50, 75));

At this point its drawing result is no longer a rectangle:

Closed Polygon 2

Rather Rectanglethan happen to look like a rectangle, it's fundamentally guaranteed to be a rectangle.

12.7.7 Padding

The previously drawn shapes are outlines and can also be filled with color:

r.set_fill_color(Color::yellow);    // color the inside of the rectangle
poly.set_style(Line_style(Line_style::dash, 4));
poly_rect.set_style(Line_style(Line_style::dash, 2));
poly_rect.set_fill_color(Color::green);

filling

Any closed shape can be filled.

12.7.8 Text

TextText can be placed anywhere using an object:

Text t(Point(150, 150), "Hello, graphical world!");
win.attach(t);

Where (150, 150) is the coordinates of the lower left corner of the text box.

text

Using the basic graphic elements in the image above, you can build arbitrarily complex and subtle displays. Note that the code in this chapter has no loops and select statements, all data is "hard-coded", and the output is just a simple combination of basic graphic elements. Once we start using data and algorithms to combine these basic graphs, things start to get interesting.

The axis label in Section 12.7.3 is an Textobject that can be changed by set_color()changing the text color. In addition, you can also set the font and font size:

t.set_font(Font::times_bold);
t.set_font_size(20);

text font

12.7.9 Image

Images can be loaded from a file:

Image ii(Point(100, 50), "img/image.jpg");  // 400*212-pixel jpg
win.attach(ii);

image

If the image is too large, the portion that exceeds the window area is "cropped".

Note: The file name "img/image.jpg" is a relative path, relative to the working directory , so the program must be run in the directory where img is located, otherwise the file will not be found. If the program is run on the command line, the working directory is the current working directory of the command line:

# working directory is "/home/zzy/PPP-code/ch12"
[zzy@ubuntu ~/PPP-code/ch12]$ ../cmake-build/ch12/shape_primitives

If you are running the program in the IDE, you can manually set the working directory:

set working directory

12.7.10 and many more

The following code shows more features of the graphics library:

Circle c(Point(100, 200), 50);
win.attach(c);

Ellipse e(Point(100, 200), 75, 25);
e.set_color(Color::dark_red);
win.attach(e);

Mark m(Point(100, 200), 'x');
win.attach(m);

std::ostringstream oss;
oss << "screen size: " << x_max() << '*' << y_max()
        << "; window size: " << win.x_max() << '*' << win.y_max();
Text sizes(Point(100, 20), oss.str());
win.attach(sizes);

Image cal(Point(225, 225), "img/snow_cpp.gif");     // 320*240-pixel gif
cal.set_mask(Point(40, 40), 200, 150);  // display center part of image
win.attach(cal);

Full code:

basic shape

finally

simple exercise

basic shape

exercise

Guess you like

Origin blog.csdn.net/zzy979481894/article/details/128783030