507 109 51MB
English Pages [466] Year 2023
2D Computer Graphics in modern c++ and standard library Håkan Blomqvist This book is for sale at http://leanpub.com/2dcomputergraphicsinmoderncandstandardlibrary This version was published on 2023-05-14
This is a Leanpub book. Leanpub empowers authors and publishers with the Lean Publishing process. Lean Publishing is the act of publishing an in-progress ebook using lightweight tools and many iterations to get reader feedback, pivot until you have the right book and build traction once you do. © 2023 Håkan Blomqvist
Contents 1 2D Drawing Basics . . . . . . 1.1 History . . . . . . . . . . . 1.2 Standard Library . . . . . 1.3 Compile Code Examples 1.4 Prerequisite . . . . . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
2 2 2 3 3
2 Draw Pixels with Standard Library . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4
3 Texture Generation with Standard Library . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8
4 Time to draw some lines with Standard Library . . . . . . . . . . . . . . . . . . . . . . . . . . 11 5 There are no straight curves, only curved lines . . . . . . . . . . . . . . . . . . . . . . . . . . 19 6 Rectangles with Standard Library . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 7 Circles with Standard Library . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 8 Triangles with Standard Library . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 9 Animation with Standard Library . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 10 Drawing Charts and a Font . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 11 Presentation, Collaboration, and Investigation 11.1 Presentation . . . . . . . . . . . . . . . . . . . . 11.2 Draw a bar chart . . . . . . . . . . . . . . . . . 11.3 Draw a pie chart . . . . . . . . . . . . . . . . . 11.4 Draw a donut chart . . . . . . . . . . . . . . . 11.5 Draw a table . . . . . . . . . . . . . . . . . . . . 11.6 Draw a more advanced chart 1 . . . . . . . . . 11.7 Draw a more advanced chart 2 . . . . . . . . . 11.8 Collaboration . . . . . . . . . . . . . . . . . . . 11.9 Investigation . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
36 36 36 37 37 38 39 50 57 57
12 PPM++ header-only library . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
CONTENTS
13 In the Rearview Mirror . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 14 Appendix A: Source Code Listings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
1 2D Drawing Basics We have all been there when you just want to present som data in a nice and graphical way. You start by implementing a GUI library so that you can set up a canvas to draw on… but then what? Or you download and build a library that can handle reading and writing PNG images, as you need lossless images. Or you do the same just to find out a files entropy. Or you’re coding on a game and want to test the look of animation sprites in a parallax setting. What ever the reason is… it’s too complicated to do and takes more time than you would consider doing. So no presentation, no collaboration, and no investigation using graphics. It should just be easy to create graphics in standard C++. And this book is all about that. How do we draw a pixel, a line, or a circle? That is questions we going to answer.
1.1 History 2D Drawings with computers started out in, as most things computer related, in academia. It was first created as a visualization tool for scientists and engineers in US government and US corporate research centers such as Bell Labs and Boeing in the 1950s. Later the tools would be developed at Universities in the 60s and 70s at places such as Ohio State University, MIT, University of Utah, Cornell, North Carolina and the New York Institute of Technology. The early breakthroughs that took place in academic centers continued at research centers such as the famous Xerox PARC in the 1970s. These efforts broke first into broadcast video graphics and then major motion pictures in the late 70¹s and early 1980s. Computer graphic research continues today around the world, now joined by the research and development departments of entertainment and production companies. Since 1974 ACM SIGGRAPH has evolved to become an international community of researchers, artists, developers, filmmakers, scientists, and business professionals who share an interest in computer graphics and interactive techniques. Each year the latest and greatest with this research gets presented.
1.2 Standard Library There are no support for drawing in the Standard Library. There are no media support what so ever in the Standard Library. And there are no need to. With the tools available to us, we will be able to draw pixels, lines and circles.
1 2D Drawing Basics
3
1.3 Compile Code Examples All code is compiled using clang++, but of course works fine with g++. Everything is C++17 except std::lerp, as its part of C++20 (a C++17 solution will be presented).
1.4 Prerequisite You’ll need to already know how to program in C++. This book is about how to draw and and not about how to program.
2 Draw Pixels with Standard Library We’re using RGB elements to draw a pixel in C++. To handle RGB elements as one unit for convenience there is something called std::tuple. Reference: https://en.cppreference.com/w/cpp/utility/tuple e.g. a function that takes three integers as input and returns it as a tuple 01_tuples.cpp demonstrates the usage of ‘std::tuple’ in C++17. It uses a tuple to store and manipulate RGB color elements as a single entity. The code includes the ‘’ headers and defines a function ‘get_rgb_elements_as_a_tuple’ that takes three integers (r, g, and b) and returns a tuple containing these integers. The ‘main’ function initializes three sets of RGB values (r1, g1, b1, r2, g2, b2, and r3, g3, b3). Initially, only the first set (r1, g1, b1) is given values, while the other two sets are initialized to 0. The code then prints the initial RGB values of all three sets. Next, it calls ‘get_rgb_elements_as_a_tuple’ with the first set of RGB values (r1, g1, b1) and assigns the returned tuple to ‘rgb’. Two different methods are then used to “unroll” the ’ ’ tuple into the other two sets of RGB values: 1. “unroll #1”: Using ‘std::get’ to access individual elements of the tuple by index and assigning them to r2, g2, and b2. 2. “unroll #2”: Using ‘std::tie’ to unpack the tuple into r3, g3, and b3. After each unroll, the RGB values of all three sets are printed to verify that the tuple has been correctly unpacked into the second and third sets of RGB values. take a look in Appendix A: 01_tuples.cpp Now that we know how to store and unroll a tuple of rgb elements. Let’s talk about adding rgb tuples to an image — to set a pixel where we want it to be in the image. But, before that we need to talk about the way computers memory (1D) is physically represented and the representation of an image (2D). To know where to store the tuple we need to translate (2D) into (1D). To store the pixel at a certain x and y in the image, but in memory. index = x + width ∗ y
2 Draw Pixels with Standard Library
5
X + width * Y = 2 + 10 * 3 = 32 Index = 32 Let’s create a function that handles the index-calculations for us. A function that takes X and Y as input and returns the index. Or, take a look in Appendix A: 02_index.cpp Besides calculating index we need to know how many tuples with we need to store in a std::vector for a beforehand defined width and height of an image. For instance, an image that has a width of 640 and a height of 360 is 640 * 360 (230400) in size for a std::vector with those dimensions. Reference: https://en.cppreference.com/w/cpp/container/vector As always we need to include the header: #include To set the vector container size we calculate it by multiplying the image width times the height. We use the Resize function in std::vector to set the size of the vector. Then we can use the vectors operator[] and operator= for index and assigns. Also, why not assign the color black to all the pixels while we are at it, as a clear image function. For the source code, take a look in Appendix A: 03_calcsize.cpp Note that this code example uses float for the rgb elements. We do that as it’s preferred later when we work with colors and that red, green and blue are a float number between 0.0 - 1.0 and not an integer.Enough with the calculations of metadata about the image. It’s time to draw some pixels and save it into a textile. Yes, there are a few image formats that are text based. In this book we’re going to use PPM file format. This graphics format comes from the open source Netpbm project that is several graphics programs and a programming library — used mainly in the Unix world. PPM stands for Portable PixMap format and is portable due to its written as ASCII (Type: P3). A PPM
6
2 Draw Pixels with Standard Library
file consists of two parts: a header and the image data. Don’t worry about the it being ASCII, PPM can be converted to PNG, JPG or any other format. Here’s how the PPM format looks like as ASCII: 1 2 3 4 5 6 7 8 9 10 11
P3 # "P3" means this is a RGB color image in ASCII 3 2 # "3 2" is the width and height of the image in pixels 255 # "255" is the maximum value for each color # The part above is the header # The part below is the image data: RGB triplets 255 0 0 # red 0 255 0 # green 0 0 255 # blue 255 255 0 # yellow 255 255 255 # white 0 0 0 # black
The header starts with the ”P3” indicator. Second line contains the width and height of the image separated with a whitespace. Third line has the number 255 stated, this is going to be used for all examples in this book. The 255 is about the max value for each rgb element. And as we see below the header, each pixel is represented as three integer values between 0 - 255 for rgb. Include header: #include Reference: https://en.cppreference.com/w/cpp/io/basic_fstream For the source code, take a look in Appendix A: 04_helloworld.cpp
04_helloworld_square.ppm
04_helloworld.ppm
That’s great, but even more awesome if we also could read a PPM image too. For the source code, take a look in Appendix A: 05_randomwalker.cpp
7
2 Draw Pixels with Standard Library
05_randomwalker.ppm
Now, we have a lot of code to go through in the 05 example. There are one new header and few new functions that needs some explanation. Example 05 was not only about reading a PPM file. We implemented a random walker. Let go through the code function by function. Reference: https://en.cppreference.com/w/cpp/header/random Add random header to the file: #include Following functions are new: • • • • • •
walker update_x_and_y read_image get_color get_width_and_height startsWithCaseInsensitive
We start by reading 04_helloworld.ppm into image1 and at the same time set width and height from the PPM file using get_width_and_height. read_image uses the helper function startsWithCaseInsensitive to sort through the PPM data. get_color is used to read the rgb color. We use the walker function to implement a random walker algorithm. Walker uses random function to randomize the walker. For each walker generation we use the update_x_and_y function to move the walker in a random direction.
3 Texture Generation with Standard Library Neat, now we can draw pixels. Let’s play around with that a bit and generate different types of textures that can be useful to have. Let’s generate textures by using boolean XOR (Exclusive-OR) AND and OR. The code generates and saves three different textures based on bitwise operations: XOR, AND, and OR. To achieve this, it defines several functions to create, manipulate, and save the images. The ‘get_index’ function is responsible for converting 2D grid coordinates (x, y) into a 1D array index, while the ‘calc_size’ function calculates the total number of elements in a 2D grid. The ‘clear_image’ function sets all the pixels of an image to a given color, effectively clearing the image. The code also provides three functions to create different textures: ‘draw_xor’, ‘draw_and’, and ‘draw_or’. Each function iterates through the image’s pixels and calculates the corresponding bitwise operation (XOR, AND, or OR) on the x and y coordinates of each pixel. The resulting value is then used as the color intensity for that pixel. In the ‘main’ function, the program starts by initializing the image vector and color tuple, followed by resizing the image vector based on the calculated size. The image is then cleared using a dark blue color. After that, the XOR texture is generated and saved to the “06_xor_texture.ppm” file. Subsequently, the AND texture is generated and saved to the “06_and_texture.ppm” file. Finally, the OR texture is generated and saved to the “06_or_texture.ppm” file. For the source code, take a look in Appendix A: 06_xor_and_or_texture.cpp
06_xor_texture.ppm
9
3 Texture Generation with Standard Library
06_and_texture.ppm
06_or_texture.ppm
XOR, AND and OR type of textures are the simplest ones. It’s just x XOR y, x AND y, or x OR y. These textures are nice to have if you just want to do an example. Excellent for prototyping use. Procedural texture generation is another way to create textures. With random noise we can generate textures like clouds, marble, and wood. Reference: https://en.cppreference.com/w/cpp/numeric/math/fabs This code generates and saves three different textures: Clouds, Marble, and Wood. It includes several functions to create, manipulate, and save images. The ‘get_index’, ‘calc_size’, ‘clear_image’, and ‘save_image’ functions were explained in the previous response, and they serve the same purpose here. Additionally, a new function ‘generate_noise’ is introduced, which fills a noise vector with random values in the range of 0.0 to 1.0. The ‘smooth_noise’, ‘turbulence’, and texture generation functions (‘generate_cloud’, ‘generate_marble’, and ‘generate_wood’) are the core of this code. The ‘smooth_noise’ function calculates a bilinear interpolation of the noise values at a given position, while the ‘turbulence’ function sums up the smooth noise values at different scales to generate more complex patterns. The ‘generate_cloud’ function uses the ‘turbulence’ function to create a cloud-like texture. In contrast, the ‘generate_marble’ function calculates a sine pattern that is distorted by turbulence to mimic the appearance of marble. Finally, the ‘generate_wood’ function creates a wood-like texture by calculating the distance from the center of the image and applying a sine pattern combined with turbulence. In the ‘main’ function, the program initializes the noise and image vectors and resizes them based on
10
3 Texture Generation with Standard Library
the calculated size. The image is then cleared using a dark blue color. The noise vector is generated, and the Cloud texture is created and saved to the “07_cloud_texture.ppm” file. Subsequently, the Marble texture is generated and saved to the “07_marble_texture.ppm” file. Finally, the Wood texture is generated and saved to the “07_wood_texture.ppm” file. For the source code, take a look in Appendix A: 07_clouds_marble_wood_texture.cpp
07_cloud_texture.ppm
07_marble_texture.ppm
07_wood_texture.ppm
Uses bilinear interpolation to smooth the noise. Turbulence is added for marble and wood. Procedural generation in itself is an interesting topic.
4 Time to draw some lines with Standard Library Well, not yet, first we go through some theory about light and color that is good to know. A C++ program designed to manipulate images in the PPM format, specifically the P3 (ASCII) format. It includes various functions to perform image manipulation tasks such as creating a negative image, adjusting brightness and darkness, converting to grayscale, swapping color channels, and blending two images. The program uses the Standard Library and has a main function to perform userspecified manipulations on an input image file and save the result to an output file. The code begins by importing necessary headers and declaring several utility functions like ‘startsWithCaseInsensitive’, ‘get_index’, ‘calc_size’, ‘get_width_and_height’, and ‘get_color’. These functions help with string manipulation, getting an index in a 1D array representing a 2D image, calculating the size of an image, and extracting image dimensions and color values from strings. The ‘read_image’ function reads an image from a file and stores it as a vector of tuples with float values representing the red, green, and blue (RGB) components of each pixel. The function reads the file line by line, determining the file format, dimensions, and maximum color value, and then populates the image vector with the pixel data. Several functions perform image manipulations like ‘negative_image’, ‘double_as_dark’, ‘onefive_as_dark’, ‘twice_as_bright’, ‘add_for_brighter’, ‘sub_for_darker’, ‘greyscale_image’, ‘zero_gb_image’, ‘r_zero_b_image’, ‘rg_zero_image’, ‘grb_image’, ‘bgr_image’, ‘rrb_image’, and ‘blended_two_images’. These functions take an image (vector of tuples) and its dimensions as input and modify the image in place. Some functions also take additional parameters like the blending factor ‘alpha’ for ‘blended_two_images’. The main function of the program reads an input image file, performs the user-specified image manipulations using the functions mentioned above, and writes the modified image to an output file. The main function also takes care of validating the user input and making sure the provided parameters are valid. For the source code, take a look in Appendix A: 08_light_and_color.cpp For every image generated, 04_helloworld.ppm get’s reread.
4 Time to draw some lines with Standard Library
12
08_negative_image.ppm
Subtraction Eg. original color rgb(203, 37, 101) as negative: rgb(255-203, 255-37, 255-101) which is the same as: rgb(52, 218, 154).
08_double_as_dark.ppm
Division Eg. rgb(203, 37, 101) as double as dark: rgb(203/2, 37/2, 101/2) which is the same as: rgb(101, 18, 50).
08_onefive_as_dark.ppm
Division Eg. rgb(203, 37, 101) as 1.5 as dark: rgb(203/1.5, 37/1.5, 101/1.5) which is the same as: rgb(135, 25, 67).
08_twice_as_bright.ppm
Multiplication Eg. rgb(203, 37, 101) as twice as bright: rgb(203x2, 37x2, 101x2) which is the same as: rgb(255, 74, 202).
13
4 Time to draw some lines with Standard Library
08_add_for_brighter.ppm
Addition Eg. rgb(203, 37, 101) add for brighter: rgb(203+30, 37+30, 101+30) which is nthe same as: Eg. rgb(233, 67, 131)
08_sub_for_darker.ppm
Subtraction Eg. rgb(203, 37, 101) sub for darker: rgb(203-30, 37-30, 101-30) which is nthe same as: Eg. rgb(173, 7, 71)
08_greyscale_image.ppm
Greyscale = (0.2126R) + (0.7152G) + (0.0722*B);
08_zero_gb_image.ppm
R = 0;
08_r_zero_b_image.ppm
G = 0;
14
4 Time to draw some lines with Standard Library
08_rg_zero_image.ppm
B = 0;
08_grb_image.ppm
RGB => GRB
08_bgr_image.ppm
RGB => BGR
08_rrb_image.ppm
RGB => RRB
08_blended_two_images.ppm
4 Time to draw some lines with Standard Library
15
Or blend two images together as above (based on bg: 06_xor_texture.ppm and fg: 04_helloworld_square.ppm). Or as below, with bg: 05_randomwalker.ppm and fg: 04_helloworld.ppm.
08_blended_two_images(2).ppm
But, now, let’s draw some lines. This program is designed to generate two images: one with random lines and another with a damped sine wave. The program includes various utility functions that handle operations like line drawing, color conversion, line clipping, and saving images. The ‘main’ function initializes the image as a vector of tuples, each representing a pixel color in the form of RGB values. It first generates random lines by picking random points and colors, then checks if the line is within the image’s dimensions using line clipping. If the line passes the clipping check, it is drawn on the image. The resulting image, containing 15,000 random lines, is saved as “09_random_lines.ppm”. Next, the program creates an image of a damped sine wave. The image is initialized with a clear background and a red line in the middle. The sine wave is drawn with a black color, and its damping factor is represented with a grey color. The damped sine wave image is saved as “09_damped_sine.ppm”. To achieve these functionalities, the program utilizes functions like ‘get_index’ and ‘calc_size’ to handle image indexing and size calculations, ‘clear_image’ to set the image background to white, and ‘h_to_rgb’ to convert hue values to RGB. Additionally, the program uses ‘draw_line’ to draw lines on the image, ‘find_region’ and ‘clip_line’ to handle line clipping, and ‘save_image’ to save the generated images to a file in PPM format. Bresenham’s line algorithm, used in the ‘draw_line’ function, is an efficient and widely-used technique for drawing lines on a raster grid, such as a computer screen or a digital image. Developed by Jack E. Bresenham in 1962, the algorithm is notable for its simplicity, speed, and ability to produce accurate line drawings using only integer arithmetic. It works by plotting a series of points between two given endpoints, taking into account the discrete nature of raster grids and minimizing the error between the ideal line and the plotted points. The algorithm begins by calculating the differences in the x and y coordinates of the two endpoints, and then determining the absolute values of these differences to establish the overall slope of the line. Based on the slope, the algorithm chooses whether to increment the x or y coordinate at each step, depending on which produces the least error. It does so by maintaining an error variable that accumulates the difference between the ideal line and the plotted points. This error variable is used to decide when to increment the other coordinate to keep the line as close to the ideal line as possible. Bresenham’s line algorithm is particularly well-suited for computer graphics because it avoids the
16
4 Time to draw some lines with Standard Library
use of floating-point arithmetic and division, which can be computationally expensive. Instead, it relies on integer operations, which are generally faster and more efficient. This makes the algorithm ideal for use in low-level graphics systems, such as early computer displays and video game consoles, where computational resources are limited. The ‘find_region’ and ‘clip_line’ functions uses the Cohen-Sutherland line clipping algorithm. This algorithm is used to determine the parts of a line segment that lie within a rectangular viewport or window. The main goal of the algorithm is to reduce the amount of line drawing computation required for lines that are partially or entirely outside the viewport. The Cohen-Sutherland algorithm assigns a 4-bit region code to each endpoint of a line segment based on its position relative to the viewport. The bits correspond to the top, bottom, right, and left regions outside the viewport. The ‘find_region’ function computes this region code for a given point. If both endpoints have a region code of 0, the line segment is entirely inside the viewport, and no clipping is required. If the bitwise AND of the two region codes is non-zero, the line segment is entirely outside the viewport and can be discarded. When a line segment straddles the viewport boundary, the ‘clip_line’ function comes into play. This function iteratively clips the line segment against each edge of the viewport by calculating the intersection point between the line and the edge. It then replaces the endpoint that lies outside the viewport with the intersection point and updates its region code. This process is repeated until the line segment is either fully inside the viewport or determined to be outside the viewport. For the source code, take a look in Appendix A: 09_lines.cpp
09_random_lines.ppm
In this example we use HSV (Hue, Saturation, and Value) as a color model and converts it back to RGB afterwords. A random Hue gets produced and with the h_to_rgb(float h) function we get a RGB value back. We also use clip_line function together with find_region function for clipping every line that is clipped between the visible and non-visible screen. ‘std::mt19937’ is a C++ standard library implementation of the Mersenne Twister pseudorandom number generator (PRNG) algorithm. The name “mt19937” is derived from the fact that the Mersenne Twister uses a Mersenne prime number, 2^19937 - 1, as the basis for its internal state. This PRNG is widely used for its high-quality random number generation, large period, and efficient performance. Mersenne Twister is a popular choice for various applications, including simulations, statistical modeling, and computer games, where high-quality random numbers are needed. The ‘std::mt19937’ generator is part of the C++11 ‘’’ library is designed to offer a more versatile and flexible approach to random number generation compared to older C++ functions like ‘rand()’ and
17
4 Time to draw some lines with Standard Library
‘srand()’. To use ‘std::mt19937’, you first need to create an instance of the generator and seed it with an initial value. The quality of the random numbers generated depends on the seed value, which is typically derived from a high-resolution clock or other sources of entropy. Once the generator is seeded, you can use it to generate random numbers within a specified range or to generate random numbers according to a probability distribution using distribution classes provided by the ‘’’ header.
09_damped_sine.ppm
Here we use the previous X and Y to draw a continous line that gets shaped into the demped sine as calculated. A damped sine wave is a sinusoidal function that gradually decreases in amplitude over time due to the presence of a damping factor. In mathematical terms, a damped sine wave can be represented as A(t) = A₀ * sin(ωt + φ) * e^(-αt), where A₀ is the initial amplitude, ω is the angular frequency, φ is the phase, α is the damping factor, and t represents time. The damping factor, α, determines the rate at which the amplitude decreases; a larger damping factor results in a more rapid decrease in amplitude, while a smaller damping factor causes the amplitude to decay more slowly. Damped sine waves can be effectively used in animations to create natural-looking motion or to simulate physical processes that involve oscillatory behavior. For example, they can be employed to animate the movement of a pendulum, the bouncing of a ball, or the oscillation of a spring. In these cases, the damped sine wave helps to model the gradual loss of energy due to friction, air resistance, or other dissipative forces that are present in real-world systems. By incorporating damped sine waves into animations, designers can create more realistic and visually appealing motion that closely mimics the behavior observed in nature, making the animation more immersive and engaging for the audience. Also, we can draw a line with starting x and y point, length and degrees.
09_angle_lines.ppm
The code is designed to create an image with lines drawn at different angles. The code begins with several include statements, bringing in necessary libraries, followed by a series of function definitions. The ‘get_index’, ‘calc_size’, and ‘clear_image’ functions are utility functions for image manipulation. ‘get_index’ calculates a one-dimensional index from two-dimensional coordinates, ‘calc_size’
4 Time to draw some lines with Standard Library
18
calculates the total size of the image given its width and height, and ‘clear_image’ sets all pixels in the image to white. ‘draw_line’ is a function that draws a line between two points in the image with a specified color. The ‘find_region’, ‘clip_line’, and ‘get_angle_line’ functions are used for line clipping and angle calculations. ‘find_region’ determines which region of the image a point lies in, ‘clip_line’ clips a line to the image boundaries, and ‘get_angle_line’ calculates the endpoint of a line given a starting point, angle, and length. Finally, ‘save_image’ is a function that writes the image data to a file in PPM format. The ‘main’ function initializes the image and sets its dimensions. It then draws lines at various angles (0, 45, 90, 135, 180, 225, 270, and 315 degrees) around the center of the image with a fixed length. The clip_line function is used to ensure that lines are only drawn within the boundaries of the image. After drawing all the lines, the image is saved to a file named “09_angle_lines.ppm”.
5 There are no straight curves, only curved lines Bézier curve is a parametric curve. There are three types of Bézier curves: • Linear Bézier curves • Quadratic Bézier curves • Cubic Bézier curves Linear Interpolation, is a mathematical function that returns a value between two others at a point on a linear scale. Most commonly it’s used for moving or changing values over a period of time. With std::lerp (Linear Interpolation) function in C++20 we can draw curves using Linear, Quadratic and Cubic Bézier curves (using a std::lerp inception; As they build on each other). Reference: https://en.cppreference.com/w/cpp/numeric/lerp C++17 version: https://github.com/emsr/cxx_linear Use the lerp and midpoint code above if you’re only allowed to use C++17. The C++ code demonstrates how to draw linear, quadratic, and cubic Bezier curves using dots on an image. It creates an image with various types of Bezier curves and saves the result as a PPM file. The code starts by including the necessary header files and then defines several functions to compute the points on the Bezier curves, manipulate the image, and render the curves on the image. The linear, quadratic, and cubic Bezier curve functions take the coordinates of the start, control, and end points, along with the number of splits as input, and compute the points on the curves. Utility functions are defined to help with image manipulation and rendering, such as getting an index in the image vector, calculating the size of the image, clearing the image, and drawing lines. In addition to the Bezier curve functions, the code also has functions to draw the Bezier curve points and markers on the image, which are used for better visualization. Another function, save_image, is defined to save the image as a PPM file. In the main function, the program initializes the width and height of the image, creates a vector for storing the image data, and then clears the image. Next, it draws linear, quadratic, and cubic Bezier curves by using the previously defined functions. The program also draws the control lines and markers for better visualization. Finally, it saves the resulting image as a PPM file named “10_curved_dots.ppm”. For the source code, take a look in Appendix A: 10_curved_dots.cpp
20
5 There are no straight curves, only curved lines
10_curved_dots.ppm
This is what std::lerp does… it’s these points that then can use a line function and dra the curve. The curve is controlled with the two control points. This C++ code is designed to create an image of curved lines using linear, quadratic, and cubic Bezier curves. It first includes the necessary libraries and then defines several functions to calculate Bezier curves, draw lines and dots, and save the image. The ‘linear_bezier_curves’, ‘quadratic_bezier_curves’, and ‘cubic_bezier_curves’ functions are used to generate the points for linear, quadratic, and cubic Bezier curves, respectively. The ‘draw_line’ function draws a straight line between two points. The ‘draw_xy_line’ function draws a line using the points generated from Bezier curves, while the ‘draw_xy_dots’ function draws dots at the points generated by the Bezier curves. The ‘draw_marker’ function creates a small marker at a specified point, and the ‘save_image’ function saves the image to a file in PPM format. In the ‘main’ function, the code sets up an image of specified width and height and fills it with a white background. Then, it proceeds to draw lines using the Bezier curves functions, along with markers and dots at various points. Finally, it saves the created image to a file. The code demonstrates the use of linear, quadratic, and cubic Bezier curves to create smooth curves between points in an image, as well as drawing markers and dots at specific points along the curve. For the source code, take a look in Appendix A: 10_curved_lines.cpp
10_curved_lines.ppm
Here we have the curves drawn with the line function and the points is marked as well as also the control points.
6 Rectangles with Standard Library A rectangle is a closed 2D shape, having 4 sides, 4 corners, and 4 right angles (90°).The opposite sides of a rectangle are equal and parallel. Since, a rectangle is a 2D shape, it is characterized by two dimensions, width, and height. To draw a rectangle you’ll need X and Y together with the rectangles width and height. The C++ code generates an image containing two rectangles, one outlined and one filled, saved as a PPM file. The code consists of several functions, each serving a specific purpose in order to create and manipulate the image. The first few functions in the code are used for basic image handling. The ‘get_index’ function calculates the index of a pixel in a 1D vector representation of a 2D image given its (x, y) coordinates and the image width. The ‘calc_size’ function calculates the size of the image given its width and height, while the ‘clear_image’ function fills the image vector with white pixels (R=1.0, G=1.0, B=1.0). Next, there are functions for drawing shapes on the image. The ‘draw_line’ function draws a line between two points in the image using Bresenham’s line algorithm, an efficient method for drawing lines on a raster grid. The ‘draw_rect’ function draws an outlined rectangle by connecting its four corners using the ‘draw_line’ function. The ‘draw_filled_rect’ function, on the other hand, draws a filled rectangle by iterating over its area and setting the color of each pixel inside the rectangle. Finally, the ‘save_image’ function saves the image to a PPM file, which is a simple, uncompressed image file format. In the ‘main’ function, the image parameters, such as width, height, and the position and size of the two rectangles are initialized. Then, a vector representing the image is created and initialized with white pixels. After that, the two rectangles are drawn on the image. The resulting image is then saved to a PPM file named “11_rectangles.ppm”. For the source code, take a look in Appendix A: 11_rectangles.cpp
11_rectangles.ppm
Here we have a rectangle and a filled rectangle. The code reads a series of rectangle specifications from a file, and then generates an image containing those rectangles, with a background color and overlapping filled rectangles. The primary purpose
22
6 Rectangles with Standard Library
of the code is to create an image where the filled rectangles are blended with the background color using an alpha channel for transparency. The code starts by including necessary headers and defining helper functions such as ‘startsWithCaseInsensitive’, ‘get_index’, ‘calc_size’, ‘clear_image’, ‘parse_bginfo_line’, ‘parse_rect_line’, ‘blend_colors’, ‘draw_filled_rect’, and ‘save_image’. These functions perform various operations, such as string manipulation, parsing input lines, calculating indices for a 2D image stored as a 1D vector, blending colors, drawing rectangles, and saving the final image as a PPM file. In the ‘main’ function, the program reads lines from the input file “11_squint_with_your_eyes.txt”. If a line starts with “r”, it is considered a rectangle specification and parsed using the parse_rect_line function. The rectangle’s coordinates, color, and alpha values are then used to draw the rectangle on the image using the ‘draw_filled_rect’ function. If a line starts with “b”, it is considered a background specification and parsed using the ‘parse_bginfo_line’ function. The background color and image dimensions are extracted from the line, and the image vector is resized accordingly. The ‘clear_image’ function is called to fill the entire image with the background color. After processing all the lines from the input file, the program saves the resulting image to a PPM file called “11_squint_with_your_eyes.ppm” using the ‘save_image’ function. The PPM file format is a simple, text-based format that stores pixel color information as plain text. The code writes the PPM header, followed by the pixel color values, one pixel per line, in the output file. For the source code, take a look in Appendix A: 11_rectangles_from_file.cpp For the source code, take a look in Appendix A: 11_squint_with_your_eyes.txt
11_squint_with_your_eyes.ppm
This is an example how you can read data and present it into a image.
7 Circles with Standard Library A circle is a round-shaped figure that has no corners or edges. The midpoint circle algorithm is a widely-used technique for efficiently drawing circles on raster displays, such as computer screens or images. The algorithm was developed as an extension of Bresenham’s line algorithm, which is used for drawing lines. The midpoint circle algorithm employs integer arithmetic and incremental calculations to minimize computational overhead and avoid the use of computationally expensive floating-point arithmetic, such as square root operations. It generates a set of points on the circle’s circumference, taking advantage of the circle’s inherent symmetry to reduce the number of calculations required. The algorithm works by starting at the topmost point of the circle and incrementally moving towards the x-axis while calculating the appropriate y-coordinate for each x-coordinate. It does this by evaluating a decision parameter at each step to determine whether to increment the x-coordinate, the y-coordinate, or both. The decision parameter is based on the circle equation, and it helps to determine whether the midpoint between two candidate pixels is inside or outside the circle. If the midpoint is inside the circle, the algorithm chooses the pixel closer to the x-axis; if it is outside, the algorithm chooses the pixel closer to the y-axis. One of the key features of the midpoint circle algorithm is its ability to exploit the eightfold symmetry of a circle. Since a circle is symmetrical about both the x and y axes, as well as the lines y = x and y = -x, the algorithm only needs to compute the points in one-eighth of the circle and then derive the remaining points by reflection. This significantly reduces the number of calculations required to draw the entire circle. The result is an efficient method for drawing circles in computer graphics, particularly when working with low-level raster graphics operations or limited computational resources. This C++ code generates an image containing two circles, one filled and one unfilled, with a white background. The primary purpose of the code is to demonstrate the process of drawing circles using a simple implementation of the midpoint circle algorithm and saving the resulting image as a PPM file. The code starts by including necessary headers and defining helper functions, such as ‘get_index’, ‘calc_size’, ‘clear_image’,‘draw_circle’, ‘draw_filled_circle’, and ‘save_image’. These functions perform various operations, such as calculating indices for a 2D image stored as a 1D vector, clearing the image by setting all pixel colors to white, drawing unfilled and filled circles using the midpoint circle algorithm, and saving the final image as a PPM file. In the ‘main’ function, the program sets the width and height of the image, as well as the positions and radii of the two circles. The x and y coordinates, as well as the radius of the circles, are calculated based on the dimensions of the image. The image vector is resized accordingly, and the ‘clear_image’ function is called to fill the entire image with the white background color.
24
7 Circles with Standard Library
Next, the ‘draw_circle’ function is called to draw the first circle (unfilled) on the image, and the ‘draw_filled_circle’ function is called to draw the second circle (filled) on the image. Both functions use the midpoint circle algorithm to determine which pixels to set to the desired color (black in this case). The ‘draw_filled_circle’ function additionally fills the interior of the circle by drawing horizontal lines between the boundary points. Finally, the ‘save_image’ function is called to save the resulting image as a PPM file called “12_circle.ppm”. The PPM file format is a simple, text-based format that stores pixel color information as plain text. The code writes the PPM header, followed by the pixel color values, one pixel per line, in the output file. For the source code, take a look in Appendix A: 12_circle.cpp
12_circle.ppm
Here we have a circle and a filled circle. A program that reads circle specifications from a text file and generates a Portable Pixmap (PPM) file containing an image of the circles. The code starts by including necessary libraries, such as those for file streams, tuples, vectors, and mathematical functions. Then, several utility functions are defined to perform tasks like case-insensitive string matching, index calculations, image size determination, and image clearing. The program then includes functions for parsing the circle and background information from the input file. These functions, ‘parse_circle_line’ and ‘parse_bginfo_line’, process each line of the input file and extract the relevant information. For circle specifications, it extracts the coordinates, radius, and color information, while for background information, it extracts the image dimensions and color. Next, the code defines several functions to handle color blending, region finding, and line clipping. The ‘blend_colors’ function combines two colors based on their transparency, while ‘find_region’ and ‘clip_line’ deal with determining which portions of the circle are visible within the image boundaries. The main functionality of the program is found in the ‘draw_filled_circle’ function, which takes the circle’s center coordinates, radius, color, and image dimensions as arguments. It uses an efficient algorithm to draw filled circles, updating the image buffer with the circle’s color. The ‘save_image’ function saves the generated image to a PPM file. Finally, the ‘main’ function reads the input file and calls the appropriate functions to parse the circle and background information. It initializes an empty image and fills it with the background color, then iterates through each circle specification and draws the circle onto the image. Once all circles have been drawn, it saves the image to a file and exits.
25
7 Circles with Standard Library
For the source code, take a look in Appendix A: 12_circles_from_file.cpp For the source code, take a look in Appendix A: 12_squint_with_your_eyes.txt
12_squint_with_your_eyes.ppm
Another example of reading data and rendering an image. Let’s draw a wedge using arc. The algorithm used to draw the wedge in the program is a combination of trigonometry and the Bresenham’s line algorithm. The trigonometric aspect calculates the points along the arc of the wedge, while Bresenham’s line algorithm is used to draw the straight lines that form the sides of the wedge. To calculate the points along the arc, the algorithm begins by converting the starting and ending angles to radians. Then, it iteratively computes the x and y coordinates using the cosine and sine functions of the current angle, and adds them to a list of coordinates. The iteration continues with a small increment in the angle until the ending angle is reached. For the straight sides of the wedge, Bresenham’s line algorithm is used to connect the center of the circle to the start and end points of the arc. Bresenham’s line algorithm is a widely used technique for drawing lines in computer graphics, as it efficiently calculates the integer points along the line without using floating-point arithmetic. The algorithm iteratively updates the x and y coordinates while minimizing the error between the actual line and the drawn points. The combination of the arc points and the lines generated by Bresenham’s algorithm creates the complete wedge shape.
12_wedge.ppm
This C++ program generates an image with a radial pattern resembling a wedge or a pie slice. The image is saved in the PPM format. The program begins by including necessary headers and defining functions for handling image data, such as calculating the size of the image, clearing it, and saving it to a file. It also contains functions to draw lines and wedges, both filled and unfilled, on the image. The main part of the program initializes the image dimensions and creates an empty white image of the specified size.
7 Circles with Standard Library
26
The program then sets the center, radius, and angles for the wedge to be drawn. The center of the wedge is set to be the midpoint of the image, and the radius is set to 80% of half of the image height. The starting and ending angles of the wedge are defined in degrees, with 0 and 348 degrees being used in this example. Next, the program draws a filled black wedge with the specified dimensions, followed by a filled gray wedge with half the radius, creating a “doughnut” shape. Then, it draws an unfilled purple wedge outline around the larger black wedge. Finally, the image is saved to a file named “12_wedge.ppm”. For the source code, take a look in Appendix A: 12_wedge.cpp
8 Triangles with Standard Library A triangle is a closed, 2 dimensional shape with 3 sides, 3 angles, and 3 vertices. A triangle is also a polygon. Create an image containing two triangles, one outlined and the other filled, and save it as a PPM file. The code is divided into several sections, such as defining functions for operations on images, drawing lines and triangles, and the main program logic. The program begins by including necessary headers and then defining several utility functions. The ‘get_index’ function calculates the index of a given pixel in a 1D vector based on its x and y coordinates and the image width. The ‘calc_size’ function calculates the total number of pixels in an image given its width and height. The ‘clear_image’ function sets all pixels in an image to a specified color. The ‘draw_line’ function uses a modified Bresenham’s line algorithm to draw a line between two points on an image. The ‘draw_triangle’ and ‘draw_filled_triangle’ functions utilize the ‘draw_line’ function to draw the edges of triangles, either outlined or filled, using an incremental scanline rendering approach. The ‘save_image’ function takes an image represented as a vector of RGB color tuples, its width and height, and a filename as arguments, and saves the image as a PPM file. In the main function, the program first initializes the width and height of the image, as well as the coordinates of the vertices of the two triangles. It then creates a vector to store the image and sets its initial color to white using the ‘clear_image’ function. Next, it sets the color to black and calls the ‘draw_triangle’ function to draw the first triangle and the ‘draw_filled_triangle’ function to draw the second triangle. Finally, the program saves the image using the ‘save_image’ function, resulting in a PPM file containing the two triangles. For the source code, take a look in Appendix A: 13_triangles.cpp
13_triangles.ppm
Here we have a triangle and a filled triangled This C++ code reads a file containing information about triangles and their colors, then generates a raster image of the triangles. The program starts by including necessary headers, such as iostream,
28
8 Triangles with Standard Library
fstream, tuple, vector, random, cmath, and algorithm. It defines several utility functions to handle string processing, coordinate calculations, and image manipulation. The main part of the code consists of functions to parse input strings, blend colors, find the region of a point, clip lines, draw filled triangles, and save the generated image to a file. The ‘parse_bginfo_line()’ and ‘parse_triangle_line()’ functions read lines from the input file and convert the data into the appropriate data types for further processing. The ‘blend_colors()’ function is used to blend colors using the alpha value, which determines how transparent the color should be when applied to the image. The ‘find_region()’ and ‘clip_line()’ functions are used to determine if a point is within the image bounds and to clip any lines that are outside of the image bounds, respectively. The ‘draw_filled_triangle()’ function is responsible for rendering triangles onto the image. It uses a scanline algorithm to fill the triangles with the specified color. This function takes care of edge cases and ensures that the triangles are drawn correctly within the image boundaries. Finally, the ‘save_image()’ function writes the generated image data to a file in PPM (Portable Pixmap) format. For the source code, take a look in Appendix A: 13_triangles_from_file.cpp For the source code, take a look in Appendix A: 13_squint_with_your_eyes.txt
13_squint_with_your_eyes.ppm
Another example.
9 Animation with Standard Library Below is four example of animation you can do… of course the animation might be more releated to graphs then images. You can animate shapes, color transition, opacity and movment. A C++ program that creates a simple animation using a random walker algorithm. It reads an image file in PPM format, generates a sequence of images, and saves them in a specified directory. The user can then use a tool like FFmpeg to convert the image sequence into a video format. The program begins by defining several helper functions for tasks such as reading and writing PPM images, updating the positions of the walkers, and blending two images together. The ‘main’ function reads an image from a file, creates a second image buffer, and then initializes the walker positions to the center of the image. It then iterates over a loop for a large number of steps, moving the walkers randomly in one of the four cardinal directions and updating the image buffer with the color from the first image at the new walker positions. After every 100 steps, the program saves the current state of the image buffer as a new PPM file. The animation created by this program consists of two walkers moving randomly over the image, revealing its contents as they traverse it. For the source code, take a look in Appendix A: 14_animation05.cpp An animation program that generates a series of images based on input data from a text file. The text file contains information about the background and rectangles to be drawn on the images. The program begins by including necessary libraries and defining functions that will be used later on. Functions include ‘startsWithCaseInsensitive’ for checking if a string starts with a specific substring, ‘get_index’ and ‘calc_size’ for converting 2D coordinates into 1D indices, and ‘clear_image’ for initializing an image with a specified background color. The program also contains functions for parsing the input text file, such as ‘parse_bginfo_line’ and ‘parse_rect_line’, which extract relevant information from the input lines. Another important function is ‘blend_colors’, which combines two colors based on their alpha values. The ‘draw_filled_rect’ function is responsible for drawing filled rectangles on the image using the ‘blend_colors’ function. The ‘get_filname’ function generates a filename for the output image, and the ‘save_image’ function saves the image to a file. The ‘main’ function of the program begins by creating an output directory and opening the input text file. The program then iterates through each line of the text file, checking if the line starts with ‘r’ for rectangle or ‘b’ for background. If it starts with ‘r’, the program parses the rectangle information, draws the rectangle on the image, and saves the image. If the line starts with ‘b’, the program parses the background information, resizes the image, sets the background color, and saves
9 Animation with Standard Library
30
the image. The program prints the filenames of the saved images to the console and closes the input file at the end. For the source code, take a look in Appendix A: 14_animation11.cpp Works the same as 14_animation11.cpp. Uses cricles instead of rectangles. For the source code, take a look in Appendix A: 14_animation12.cpp Works the same as 14_animation11.cpp. Uses triangles instead of rectangles. For the source code, take a look in Appendix A: 14_animation13.cpp Just experiment and use what you think looks good.
10 Drawing Charts and a Font The flood-fill algorithm is a popular technique in computer graphics and image processing for filling an area with a specified color. This algorithm works by starting at a seed pixel and recursively or iteratively filling neighboring pixels that share the same color as the seed pixel, ultimately replacing them with a new target color. The process continues until all the connected pixels sharing the original color are filled with the new color. The flood-fill algorithm has several variations, such as 4-way and 8-way connectivity, which determine how pixels are considered neighbors based on their relative positions. Flood-fill algorithms have various applications, particularly in image editing software, computer games, and pattern recognition tasks. In image editing software, the flood-fill tool, commonly known as the paint bucket, is used to fill areas with a uniform color or pattern. This tool allows users to easily modify images by changing the color of specific regions or creating new shapes. In computer games, the flood-fill algorithm can be employed for tasks like procedural map generation, pathfinding, or detecting connected components in a game world. In pattern recognition, flood-fill algorithms can be used to identify and isolate distinct regions or objects within an image based on their color or texture properties. Despite its popularity and numerous applications, the flood-fill algorithm does have some limitations. Its performance can be affected by the size of the area to be filled and the complexity of the image. Recursive implementations of the flood-fill algorithm are prone to stack overflows when dealing with large areas, while iterative implementations can be memory-intensive. Additionally, the algorithm may fail to provide the desired output if there are gaps or holes in the boundaries of the region to be filled. In such cases, modifications to the algorithm, like tolerance-based flood-fill techniques, can be applied to address these issues and achieve more accurate results. A program that demonstrates the creation and manipulation of a radar chart, which is a graphical representation of multivariate data in the form of a two-dimensional chart. This code can be compiled using the command provided at the beginning of the code. The program begins by including the required header files and defining a set of functions to manipulate images, such as calculating the index, clearing the image, drawing shapes, and clipping lines. Some functions, like ‘get_index’ and ‘calc_size’, are utility functions that help with image manipulation, while others, like ‘draw_circle’ and ‘draw_filled_circle’, are responsible for drawing specific shapes on the image. The ‘floodfill_algorithm’ function is an implementation of the flood-fill algorithm that fills an enclosed area of an image with a specified color. The ‘get_all_ys’ and ‘draw_line_coords’ functions are used for drawing polygons on the image. The ‘draw_polygon’ and ‘draw_filled_polygon’ functions use these helper functions to draw and fill polygons, respectively.
32
10 Drawing Charts and a Font
The ‘draw_line’ function takes an image, a set of coordinates, a color, and the width and height of the image, and draws a line on the image using the given coordinates and color. The ‘find_region’ and ‘clip_line’ functions are used to clip lines based on the width and height of the image, ensuring that lines do not extend beyond the boundaries of the image. For the source code, take a look in Appendix A: 15_radarchart.cpp
15_radarchart.ppm
As 15_radarchart.ppm width is 1920 pixels and height is 1080, the size of the PPM is getting a bit big to write as a textfile. That is why save_image()-function saves the PPM as a P6 with the RGB as 3 byte binary data. This code is written in C++ and is used to generate a radial chart with three colored segments representing three different categories of data. The chart consists of 52 wedges, each representing a week. The data for each category is provided in three arrays ‘a1’, ‘a2’, and ‘a3’. The chart is then saved as an image in PPM (Portable Pixmap) format. The program begins by including the necessary header files and defining some utility functions for working with images, such as ‘get_index’, ‘calc_size’, ‘clear_image’, and others. These functions are used for creating and manipulating the image data structure, a vector of tuples representing RGB colors. Functions like ‘draw_filled_circle’, ‘draw_filled_wedge’, and ‘save_image’ are used to draw and save the final image. The ‘main’ function starts by initializing the image data structure and setting up color tuples for the background and three data categories. It calculates the center of the image, the radius, and other necessary parameters for drawing the radial chart. It then draws a filled circle with a specific background color and iterates through the 52 wedges, drawing filled wedges for each of the three categories using the data provided in ‘a1’, ‘a2’, and ‘a3’. The wedges are drawn using the ‘draw_filled_wedge’ function, which in turn uses the ‘draw_line_coords’ function to create line coordinates for the wedges. After drawing all the wedges, the image is saved to a file named “15_weekchart.ppm” using the ‘save_image’ function. For the source code, take a look in Appendix A: 15_weekchart.cpp
15_weekchart.ppm
10 Drawing Charts and a Font
33
Also as a P6 PPM as it has the same width and height as 15_radarchart.ppm. This C++ code reads and writes P6 PPM (Portable PixMap) image files. The main purpose is to load an image file, and then save it to another file without any modification. The code is organized in three main sections: the ‘load_image’ function, the ‘save_image’ function, and the ‘main’ function. The ‘load_image’ function takes a reference to a vector of tuples of floats, width and height integers, and a string for the filename. It opens the file in binary mode, reads the magic number (which should be “P6”), skips any comments, reads the image dimensions and maximum color value, and then reads the pixel data into the vector of tuples. The pixel data is normalized by dividing the individual color values by 255.0, which makes it easier to work with in future operations. The ‘save_image’ function is responsible for writing an image back to a file. It takes the same arguments as the ‘load_image’ function, but instead of reading the data, it writes the image data to a file. It writes the magic number, image dimensions, and maximum color value, and then writes the pixel data, converting the normalized float values back to the 0-255 range. The ‘main’ function starts by defining a vector of tuples to store the image data and integer variables for width and height. It then calls the ‘load_image’ function to read the image file, prints out the width and height of the image, and finally calls the ‘save_image’ function to save the image data to a new file. This code can be used as a starting point for more complex image manipulation tasks, as it provides a simple way to read and write PPM files. To read a P6 PPM, take a look in Appendix A: 15_read_p6_ppm.cpp Let’s create a font and render all the letters in it.
This code generates images of hand-drawn styled letters and numbers. The program defines a set of functions to draw each character, using the Bresenham’s line algorithm to get the points of the lines that make up the characters. It also defines functions to draw filled circles, clear the image, and save the image in the PPM (Portable Pixmap) format. The main function (draw_and_save_font) iterates through each character, draws it with the specified color, width, height, margin, and line size, and saves the resulting image to a PPM file. Each draw_font_ function has two main components:
10 Drawing Charts and a Font
34
• An array of points that define the character’s shape, where the points are represented by their relative X and Y coordinates. • An array of line segments that connect these points, where each line segment is represented by a pair of indices corresponding to the points array. In each function, the code iterates through the line segments, calculates the coordinates of the actual points on the image canvas by scaling and translating the relative coordinates, and adds the points to the points vector. The draw function takes the points vector and the other parameters, and updates the image by either drawing a single pixel (when the line size is 1) or drawing a filled circle centered at each point. Finally, the save_image function writes the image data to a PPM file, using the specified filename. Here’s a brief explanation of the purpose of each function: • draw_font_: Draws the specified character by adding its line points to the points vector. • get_line_points: Uses Bresenham’s line algorithm to get the points of a line segment. • draw_filled_circle: Draws a filled circle centered at the specified point with the specified radius. • clear_image: Resets the image to a white background. • save_image: Saves the image to a PPM file with the specified filename. • draw: Updates the image with the specified points, color, line size, etc. • draw_and_save_font: Main function that iterates through each character, draws it, and saves the image to a file. To draw each letter in the font, take a look in Appendix A: 15_draw_font.cpp
11 Presentation, Collaboration, and Investigation 11.1 Presentation Creating charts and diagrams can be a great way to visualize data and communicate information. This chapter will show you how to create 2D charts and diagrams using C++. We’ll start by showing you how to create basic shapes and lines. Then we’ll make more complex shapes like circles and rectangles. Finally, we’ll show you how to add text and labels to your charts and diagrams. By the end of this chapter, you’ll know how to create professional-looking 2D charts and diagrams using C++. So let’s get started!
11.2 Draw a bar chart Draw a bar chart by just hacking away. This generates an image of a bar chart with specific characteristics. The chart has 5 bars and 4 icons drawn in the image, as well as percentage values. The code is structured in various functions that are called in the ‘main’ function to build and save the image. The program starts with the necessary includes, such as iostream, fstream, tuple, vector, random, and cmath. The ‘get_index’ function calculates an index for accessing elements of the image vector. The ‘calc_size’ function calculates the size of the image based on the width, height, and margin. The ‘clear_image’ function initializes the image with a specific color (a shade of gray). The ‘draw_filled_rect’ function takes the position (x, y), dimensions (w, h), image reference, color, width, and height as parameters and draws a filled rectangle on the image. The ‘draw_line’ function takes the image reference, a tuple of coordinates, color, width, and height as parameters and draws a line on the image. The ‘save_image’ function takes the image reference, width, height, and filename, and saves the image as a PPM file. In the ‘main’ function, the program initializes the image, fills it with the background color, and then draws the bar chart by calling ‘draw_filled_rect’ and ‘draw_line’ functions with specific parameters. The bars, icons, and percentages are drawn using these functions. After drawing the chart, the image is saved to a file with a specified filename.
37
11 Presentation, Collaboration, and Investigation
16_draw_bar_chart.ppm
Drawing a bar chart, take a look in Appendix A: 16_draw_bar_chart.cpp
11.3 Draw a pie chart The code is designed to create and output a simple pie chart. It first includes necessary headers such as iostream, fstream, tuple, vector, random, cmath, and algorithm to handle various functions required in the program. It then defines a set of functions for various calculations and operations. The function ‘get_width_from_height’ calculates the width of the pie chart based on the height. ‘get_margin_from_height’ calculates the margin size for the chart based on the height. ‘get_index’ returns the index of a pixel in the image vector given its x and y coordinates and the width of the image. The ‘calc_size’ function calculates the size of the chart based on its width, height, and margin. The ‘stamp’ function is responsible for stamping the image of a letter onto the pie chart, while the ‘clear_image’ function sets the initial color of the pie chart to a light orange color. Functions ‘get_line_points’, ‘draw_font_M’, ‘draw_font_A’, ‘draw_font_R’, ‘draw_font_S’, ‘draw_font_V’, ‘draw_font_E’, ‘draw_font_N’, ‘draw_font_U’, and ‘draw_font_C’ handle drawing of the individual letters on the pie chart using line segments and points. Each letter is represented by a set of coordinates and lines connecting them.
16_draw_pie_chart.ppm
Pie chart in PPM, take a look in Appendix A: 16_draw_pie_chart.cpp
11.4 Draw a donut chart The code provided is a C++ program that creates a donut chart with the letters “MARS” in the center. It uses a variety of functions to draw lines, circles, and characters on a 2D grid, ultimately constructing the desired image.
11 Presentation, Collaboration, and Investigation
38
The program starts by including necessary headers and defining functions to perform operations such as calculating width and margin based on height, calculating the index of a 2D point in a 1D array, and clearing the image by setting a background color. Functions are also defined for drawing lines between two points and drawing filled circles. There are several functions dedicated to drawing individual characters like ‘M’, ‘A’, ‘R’, and ‘S’. These functions first define an array of points representing the outline of each character and then another array of lines connecting those points. Using the ‘get_line_points’ function, lines are drawn between the specified points to create the shape of the characters. The ‘draw_filled_circle’ function takes in the center coordinates of a circle, its radius, and a color, and then fills the circle with that color. The ‘draw_filled_circle2’ function is a slightly modified version of ‘draw_filled_circle’ without the margin parameter. The ‘get_all_ys’ function collects all the y-coordinates from a set of coordinates, sorts them, and removes duplicates. The ‘draw_line_coords’ function is a helper function for drawing lines between two points and storing the resulting coordinates in a vector. The ‘stamp’ function is used to place one image (letter) onto another image (donut chart) at a specified offset. The ‘draw_filled_circle_segment’ function draws a filled segment of a circle, given the start and end angles. Finally, the ‘main’ function of the program sets up the dimensions and colors for the donut chart and uses the functions defined earlier to draw the chart and letters. The resulting image is then saved as a PPM file, which can be opened with an image viewer that supports the format.
16_draw_donut_chart.ppm
Pie chart in PPM, take a look in Appendix A: 16_draw_donut_chart.cpp
11.5 Draw a table This C++ code is designed to draw a table, save the image as a PPM file, and demonstrate various graphics functions such as drawing lines, circles, and rectangles. It includes various functions to perform these tasks and a main function to tie them all together. First, the necessary libraries are included at the beginning of the code. The ‘get_width_from_height’ function calculates the width of the table based on its height, while the ‘get_margin_from_height’ function calculates the margin based on the height. The ‘get_index’ function calculates the index of a point in a one-dimensional vector based on its x and y coordinates and the given width.
39
11 Presentation, Collaboration, and Investigation
The ‘calc_size’ function calculates the size of the image, taking into account the width, height, and margin. The ‘clear_image’ function initializes the image vector by setting the color of each pixel to a light brownish color. The ‘stamp’ function stamps the input image (i.e., the letters) onto another image at a specified position. The ‘get_line_points’ function calculates the points along a line using Bresenham’s line algorithm, given the coordinates of the two endpoints and a color. This function is used in the ’draw_font*’_functions, where each function (e.g., ‘draw_font_M’, ‘draw_font_A’, etc.) is responsible for drawing the respective letter using lines. Each of these functions sets up arrays containing the coordinates of the points and lines that make up the letter, and then calls ‘get_line_points’ to calculate the points along each line. The ‘draw_filled_circle’ and ‘draw_filled_rect’ functions draw filled circles and rectangles, respectively, on the image. The ‘save_image’ function saves the image as a PPM file, given the image data, its dimensions, and the output filename. The draw function is used to draw a set of points on the image. The points are drawn as filled circles with a specified line size. Finally, the ‘main’ function initializes the image, sets the colors and dimensions for the table, and calls the various drawing functions to create the image. It then saves the image to a file named “table.ppm”.
16_draw_table.ppm
Pie chart in PPM, take a look in Appendix A: 16_draw_table.cpp.cpp
11.6 Draw a more advanced chart 1 The given function, ‘generate_noise_image’, is designed to create a noise image by generating random grayscale pixel values. The noise image is represented as a vector of tuples, where each tuple contains three float values representing the red, green, and blue channels of a pixel. The function takes two parameters: a reference to an ‘std::vector’ of tuples named image which will store the generated noise ‘image’, and two integers ‘width’ and ‘height’ representing the dimensions of the desired noise image. The function begins by defining a tuple named ‘color’ and a float variable named ‘rndnum’, which will be used to store random grayscale values. Next, the function sets up a random number generator using the ‘std::random_device’ and ‘std::mt19937’ classes from the C++ standard library. The random number generator is configured to produce float values uniformly distributed between 0.0 and 1.0. The function then iterates through each pixel in the image, using two nested loops. The outer loop iterates through the height of the image, while the inner loop iterates through the width. For each
11 Presentation, Collaboration, and Investigation
40
pixel, a random number is generated using the previously configured random number generator, and the ‘color’ tuple is set to contain the random number for all three channels (red, green, and blue). This creates a grayscale color, as all channels have the same value. The ‘color’ tuple is then assigned to the corresponding position in the ‘image’ vector using the ‘get_index’ function, which converts 2D coordinates (x, y) into a 1D index suitable for accessing the vector. By the end of the function, the ‘image’ vector will contain a representation of a noise image with the specified width and height, where each pixel is a randomly generated grayscale value.
Step #1: 16_draw_market_size1.ppm
The ‘generate_linear_gradient_image’ function creates a linear gradient image by blending two colors across the image vertically. The resulting image is represented as a vector of tuples, where each tuple contains three float values representing the red, green, and blue channels of a pixel. The function takes four parameters: a reference to an ‘std::vector’ of tuples named image which will store the generated gradient ‘image’, two tuples named ‘fcolor’ and ‘tcolor’ representing the starting and ending colors of the gradient, and two integers ‘width’ and ‘height’ representing the dimensions of the desired gradient image. The function begins by defining a tuple named ‘color’ and double variables ‘resultRed’, ‘resultGreen’, and ‘resultBlue’ which will be used to store the blended color values for each pixel. Additionally, the function defines variables ‘fr’, ‘fg’, ‘fb’, ‘tr’, ‘tg’, and ‘tb’ for storing the individual red, green, and blue components of the starting and ending colors. The ‘std::tie’ function is used to unpack the ‘fcolor’ and ‘tcolor’ tuples into these separate variables. The function then iterates through each row of the image using a loop that iterates through the height of the image. For each row, the function calculates the percentage of the total height that the current row represents. This percentage is then used to blend the starting and ending colors for that row, by computing the weighted average of the respective color components (red, green, and blue). The resulting blended color is stored in the ‘color’ tuple using the ‘std::make_tuple’ function. Next, the function iterates through each column in the current row using a nested loop. For each pixel in the row, the ‘color’ tuple containing the blended color for the current row is assigned to the corresponding position in the ‘image’ vector using the ‘get_index’ function, which converts 2D coordinates (x, y) into a 1D index suitable for accessing the vector. By the end of the function, the ‘image’ vector will contain a representation of a linear gradient image with the specified width and height, where the colors gradually blend from the starting color at the top of the image to the ending color at the bottom.
11 Presentation, Collaboration, and Investigation
41
Step #2: 16_draw_market_size2.ppm
‘blended_two_images’ blends two images together using a specified alpha value. It takes five input parameters: two vectors of tuples representing the colors of the two images, the width and height of the images, and a float value called alpha. First, the function initializes three tuple variables ‘color1’, ‘color2’, and three float variables ‘r’, ‘g’, and ‘b’. The tuples represent colors from the first and second images, while the float variables represent the blended color components - red, green, and blue. The function then uses two nested loops to iterate over the width and height of the images. The outer loop iterates over the height of the images, and the inner loop iterates over the width. Inside the inner loop, the function extracts the colors at the current position ‘(x, y)’ from both images using the ‘get_index’ function, which computes the index in the vector based on the x, y coordinates and the width. Next, the function calculates the blended color components (red, green, and blue) using the alpha value. The blending operation is performed by multiplying each color component of the second image by the alpha value and adding the product of the color component from the first image and (1.0 - alpha). This is done for all three color components - red, green, and blue. After calculating the blended color components, the function stores the blended color as a tuple in the first vector at the same position ‘(x, y)’. This overwrites the original color in the first image with the blended color. Once the loops have finished, the first vector will contain the resulting blended image.
Step #3: 16_draw_market_size3.ppm
‘generate_radial_gradient_image’ generates a radial gradient image using two input colors. It takes four input parameters: a vector of tuples representing the colors of the output image, two tuples representing the colors to create the gradient (from-color and to-color), and the width and height of the image. First, the function initializes several variables: color for storing the current ‘color’ being computed, ‘pink’ for a predefined color, ‘mask’ for a vector of tuples to store an intermediate mask, ‘distanceFromCenter’ for calculating the distance from the center of the image, and ‘resultRed’, ‘resultGreen’, and ‘resultBlue’ for storing the resulting color components.
11 Presentation, Collaboration, and Investigation
42
The function starts with two nested loops to iterate over the width and height of the image. Inside the inner loop, the function sets the initial values of the ‘mask’ and ‘image’ vectors at the current position ‘(x, y)’ to the predefined ‘pink’ color and the ‘from-color’, respectively. The function then initializes several variables for drawing a circle using Bresenham’s circle algorithm. It declares a lambda function ‘drawline’ that is used to draw horizontal lines at the specified positions. It loops over the circle, drawing lines for each quadrant, and updates the mask vector accordingly. After completing the circle drawing, the function starts another pair of nested loops to iterate over the width and height of the image once more. Inside the inner loop, the function checks if the current position ‘(x, y)’ in the ‘mask’ vector is not equal to the predefined ‘pink’ color. If this condition is true, it calculates the distance from the center of the image and computes the resulting color components based on the distance and the input ‘from-color’ and ‘to-color’. The function then assigns the computed color to the ‘image’ vector at the current position ‘(x, y)’. If the condition is false, meaning that the ‘mask’ vector has the ‘pink’ color at the current position ‘(x, y)’, the function assigns the ‘to-color’ to the ‘image’ vector at the current position. This way, the final image vector will contain a radial gradient image generated using the input colors.
Step #4: 16_draw_market_size4.ppm
‘generate_repeating_texture_image’ generates a repeating texture image using an input color. The function takes three input parameters: a vector of tuples representing the colors of the output image, a tuple representing the color to create the texture, and the width and height of the image. First, the function initializes a tuple called ‘bgcolor’ to store a predefined background color. Then, it uses two nested loops to iterate over the width and height of the image. Inside the inner loop, the function sets the initial value of the ‘image’ vector at the current position ‘(x, y)’’ to the background color. Next, the function defines two arrays named ‘arr’ and ‘arr2’, which store the coordinates and indices of the line segments to draw the repeating texture pattern. The ‘arr’ array contains 13 pairs of integers representing the x and y coordinates, and the ‘arr2’ array contains 13 pairs of integers representing the indices of the line segments in the ‘arr’ array. The function then uses a loop to iterate over the 13 line segments, creating a tuple ‘coords’ containing the start and end coordinates of each line segment. Inside the loop, the function calls the ‘draw_line’ function, which draws the line segment on the ‘image’ vector using the input ‘color’, ‘width’, and
11 Presentation, Collaboration, and Investigation
43
‘height’. This process repeats for each line segment, resulting in a repeating texture pattern drawn on the image vector. In summary, the ‘generate_repeating_texture_image’ function creates a repeating texture image by drawing a series of line segments on an image vector using a predefined background color and an input color for the texture pattern. The function achieves this by iterating through a series of coordinates and line segment indices, drawing the texture pattern on the image vector using the ‘draw_line’ function.
Step #5: 16_draw_market_size5.ppm
‘render_repeating_texture_image’ renders a repeating texture image by tiling a smaller texture image across a larger image. The function takes five input parameters: a vector of tuples representing the colors of the texture image, the width and height of the texture image (tw and th), a vector of tuples representing the colors of the output image, and the width and height of the output image (iw and ih). The function uses two nested loops to iterate over the width and height of the output image. Inside the inner loop, the function calculates the corresponding position in the texture image using the modulo operator. The x and y coordinates of the output image are calculated as the remainder of the division of the x and y coordinates by (tw - 1) and (th - 1), respectively. This approach ensures that the texture image will be repeated across the output image, creating a tiled pattern. Once the corresponding position in the texture image is determined, the function retrieves the color at that position using the ‘get_index’ function, which computes the index in the vector based on the x, y coordinates, and the texture image width (tw). The retrieved color is then assigned to the output image vector at the current position ‘(x, y)’ using the ‘get_index’ function with the output image width (iw).
11 Presentation, Collaboration, and Investigation
44
Step #6: 16_draw_market_size6.ppm
‘do_cookie_cut’ extracts a rectangular region, referred to as a “cookie,” from a larger image. The function takes eight input parameters: a vector of tuples representing the colors of the input image, the width and height of the input image (w and h), a vector of tuples representing the colors of the output “cookie” image, the width and height of the “cookie” image (cw and ch), and the starting x and y coordinates (startx and starty) of the region to extract from the input image. The function initializes two variables, ‘cx’ and ‘cy’, which will be used to store the x and y coordinates of the “cookie” image as it is being constructed. It then uses two nested loops to iterate over the rectangular region defined by the starting coordinates (startx and starty) and the width and height of the “cookie” image (cw and ch). Inside the inner loop, the function retrieves the color from the input image at the current position ‘(x, y)’ using the ‘get_index’ function, which computes the index in the vector based on the x, y coordinates and the input image width (w). The retrieved color is then assigned to the “cookie” image vector at the position (cx, cy) using the get_index function with the “cookie” image width (cw). After each iteration of the inner loop, the function increments the x-coordinate of the “cookie” image (cx) by 1. After each iteration of the outer loop, the function increments the y-coordinate of the “cookie” image (cy) by 1 and resets the x-coordinate (cx) back to 0.
Step #7: 16_draw_market_size7.ppm
‘add_honeycomb’ overlays a texture image onto an input image using an alpha channel image to control the blending. The function takes four input parameters: a vector of tuples representing the colors of the input image, a vector of tuples representing the colors of the alpha channel image, a vector of tuples representing the colors of the texture image, and the width and height of the images
11 Presentation, Collaboration, and Investigation
45
(w and h). The width and height are assumed to be the same for all three images. First, the function initializes three color tuple variables (color1, color2, and color3) and a pink color tuple, which will be used later to determine if a pixel should be affected by the texture overlay. The function then iterates over the width and height of the input image using two nested loops. Inside the inner loop, the function checks if the current pixel in the texture image is not equal to the pink color. If it is not pink, the pixel will be affected by the texture overlay. Next, the function retrieves the colors from the input image, texture image, and alpha channel image at the current position ‘(x, y)’ using the ‘get_index’ function. The retrieved colors are assigned to the corresponding color tuple variables (color1, color2, and color3). The function then calculates the blended color by multiplying the texture color (color2) with the alpha value (color3) and adding the input image color (color1) multiplied by the inverse of the alpha value (1.0f - color3). This process is done separately for the red, green, and blue channels, resulting in the final blended color components (ir, ig, and ib). Finally, the blended color is assigned to the input image vector at the current position ‘(x, y)’ using the ‘get_index’ function. This process updates the input image with the texture overlay, effectively modifying the input image in-place.
Step #8: 16_draw_market_size8.ppm
‘stamp_cookie’ pastes a smaller image, referred to as a “cookie,” onto a larger image at a specified position. The function takes eight input parameters: a vector of tuples representing the colors of the input image, the width and height of the input image (w and h), a vector of tuples representing the colors of the “cookie” image, the width and height of the “cookie” image (cw and ch), and the starting x and y coordinates (startx and starty) of the position where the “cookie” image should be pasted onto the input image. The function initializes two variables, ‘cx’ and ‘cy’, which will be used to store the x and y coordinates of the “cookie” image as it is being pasted onto the input image. It then uses two nested loops to iterate over the rectangular region defined by the starting coordinates (startx and starty) and the width and height of the “cookie” image (cw and ch). Inside the inner loop, the function retrieves the color from the “cookie” image at the current position ‘(cx, cy)’ using the ‘get_index’ function, which computes the index in the vector based on the x, y coordinates and the “cookie” image width (cw). The retrieved color is then assigned to the input
11 Presentation, Collaboration, and Investigation
46
image vector at the position ‘(x, y)’ using the ‘get_index’ function with the input image width (w). After each iteration of the inner loop, the function increments the x-coordinate of the “cookie” image (cx) by 1. After each iteration of the outer loop, the function increments the y-coordinate of the “cookie” image (cy) by 1 and resets the x-coordinate (cx) back to 0.
Step #9: 16_draw_market_size9.ppm
The ‘draw_hexagon’ function is designed to draw a hexagon with a specified color onto an image. The function takes eight parameters: a vector of tuples representing the colors of the input image, a tuple representing the color of the hexagon, the x and y coordinates of the hexagon’s center (centerx and centery), the hexagon’s radius, the width and height of the input image, and the “circle radius” (cradius) used for drawing the lines that make up the hexagon. First, the function initializes some variables used for calculating the hexagon’s vertices. It computes the upper-left corner of a bounding box around the hexagon, given by ‘(ux, uy)’. It also initializes six points (pt1x, pt1y) to (pt6x, pt6y), all initially set to ‘(ux, uy)’. Next, the function calculates some auxiliary variables (A, B, C) using trigonometric formulas involving the given hexagon radius and a constant PI value. The purpose of these calculations is to help determine the coordinates of the hexagon’s vertices. Using the auxiliary variables, the function computes the coordinates of each vertex of the hexagon (pt1 to pt6). Each vertex’s x and y coordinates are updated based on the initial ‘(ux, uy)’ position and the auxiliary variables. The function then creates a vector of tuples called ‘points’ that will be used to store the points along the lines that form the hexagon. For each pair of adjacent vertices, the function generates the line points between them using the ‘get_line_points’ function and stores the result in the ‘points’ vector. Then, the ‘draw’ function is called to draw the line using the points, the input image, the specified color, the image dimensions, and the “circle radius” (cradius). After drawing each line, the function clears the ‘points’ vector to prepare it for the next line. The ‘flood_fill’ function is a simple recursive algorithm used to replace a specific color in an image with another color within a connected region. It takes six parameters: a vector of tuples representing the colors of the input image, the x and y coordinates of the starting point, tuples representing the old and new colors, and the width of the input image. The function begins by checking if the color at the current coordinates (x, y) in the image is equal to the old color specified. If the colors match, it replaces the old color at that position with the new color. Then, the function recursively calls itself to perform the flood fill operation in four directions: right (x+1, y), down (x, y+1), left (x-1, y), and up (x, y-1).
11 Presentation, Collaboration, and Investigation
47
This process continues until the function reaches pixels with a color different from the old color, effectively filling the connected region of pixels with the new color.
Step #10: 16_draw_market_size10.ppm
An image is being created with various hexagons drawn on it. The code starts by declaring a vector of tuples called ‘image8’, which will represent the colors in the image. The ‘image8’ vector is resized to fit the dimensions of the image, which are calculated using the ‘calc_size(width, height)’ function. Next, the ‘fcolor’ tuple is created using the ‘std::make_tuple’ function with RGB values (1.0f, 1.0f, 1.0f), representing the color white. The ‘draw_hexagon’ function is then called four times with different parameters, each time drawing a hexagon on ‘image8’ with the specified color, center coordinates, radius, image width, image height, and circle radius used for drawing the hexagon lines. The first hexagon is drawn with its center at (784, 340) and a radius of 190. The second hexagon is drawn with its center at (520, 500) and a radius of 143. The third hexagon is drawn with its center at (730, 610) and a radius of 107. Finally, the fourth hexagon is drawn with its center at (900, 680) and a radius of 76.
Step #11: 16_draw_market_size11.ppm
The ‘blend_hexagon’ function is designed to blend two images, represented by the vectors ‘blend1’ and ‘blend2’, based on a specific color condition. The function takes in four parameters: the two image vectors ‘blend1’ and ‘blend2’, and the dimensions of the images, ‘width’ and ‘height’. First, the function initializes two color tuples, ‘color1’ and ‘color2’. ‘color1’ is initialized to (0.0f, 0.0f, 0.0f), which represents black, while ‘color2’ is initialized to (1.0f, 1.0f, 1.0f), which represents white. The function also initializes three float variables, ‘r’, ‘g’, and ‘b’, and an ‘alpha’ variable with a value of 0.8f. The ‘alpha’ value will be used to blend the colors from the two images. The function then iterates through all the pixels in the images using nested for-loops. For each pixel, it checks if the color of the corresponding pixel in ‘blend2’ is equal to ‘color2’, which is white. If the condition is met, it retrieves the color of the corresponding pixel in ‘blend1’ and blends it with black (0.0f, 0.0f, 0.0f), using the ‘alpha’ value to determine the degree of blending. After blending the colors, the result is stored back in the ‘blend1’ image at the corresponding pixel
11 Presentation, Collaboration, and Investigation
48
position. The blending is performed by multiplying each color component (red, green, and blue) of the color from ‘blend1’ by the ‘alpha’ value, then adding the product of the black color component and (1.0f - ‘alpha’). The new color tuple is created using the ‘std::make_tuple’ function and is assigned to the corresponding pixel in the ‘blend1’ image.
Step #12: 16_draw_market_size12.ppm
The ‘add_white_hexagon_border’ function is designed to add a white border to an image based on the white pixels in another image. The function takes in four parameters: two image vectors, ‘image1’ and ‘image2’, and the dimensions of the images, ‘width’ and ‘height’. First, the function initializes a color tuple named ‘color’ with the values (1.0f, 1.0f, 1.0f), which represents the color white. Next, the function iterates through all the pixels in the images using nested for-loops. For each pixel, it checks if the color of the corresponding pixel in ‘image2’ is equal to ‘color’, which is white. If the condition is met, it sets the color of the corresponding pixel in ‘image1’ to white.
Step #13: 16_draw_market_size13.ppm
The ‘draw_label_lines’ function is designed to draw multiple lines on an image with a specific color and thickness. The function accepts five parameters: a reference to the image vector, a reference to the color tuple, the width and height of the image, and the thickness of the lines represented as radius. First, the function initializes a ‘coords’ tuple, which will store the coordinates of the starting and ending points of the lines to be drawn. It also initializes a ‘points’ vector, which will store the points on the line.
Step #14: 16_draw_market_size14.ppm
This code defines a function called ‘draw_labels’ that takes three arguments: a reference to a vector
11 Presentation, Collaboration, and Investigation
49
of tuples called ‘image’, a reference to a tuple called ‘color’, and two integers called ‘width’ and ‘height’. The function is designed to draw labels on an image using a specified color. The image is represented by a vector of tuples, where each tuple contains three floating-point values representing the red, green, and blue channels of a pixel. The ‘color’ tuple also contains three floating-point values that represent the red, green, and blue channels of the desired color for the labels. The ‘draw_labels’ function begins by defining a hard-coded label called ‘lable_10’, which is a series of 0s and 1s that represent the pixel values of an image. This label was created in a graphics application and converted to a binary format to be used in this function. The 0s represent transparent pixels, while the 1s represent the color specified by the ‘color’ argument. The purpose of the function is to draw this ‘lable_10’ on the ‘image’ using the specified ‘color’, ‘width’, and ‘height’. However, the provided code only defines the ‘lable_10’ data and does not include the necessary steps to draw the label on the image. The actual drawing process would involve iterating through the lable_10 data, and for each 1 encountered, setting the corresponding pixel in the ‘image’ vector to the specified ‘color’.
Step #15: 16_draw_market_size15.ppm
Pie chart in PPM, take a look in Appendix A: 16_draw_market_size.cpp
11 Presentation, Collaboration, and Investigation
50
11.7 Draw a more advanced chart 2 The code is designed to create a noise image. It is composed of two main parts: variable declarations and initialization, and a function definition. The code first defines a vector of tuples called ‘image1’ to store the noise image data. Each tuple contains three float values that represent the red, green, and blue channels of a pixel. The width and height of the image are then set to 1600 and 900 pixels, respectively. The ‘image1’ vector is resized to the appropriate size using the ‘calc_size(width, height)’ function. The ‘generate_noise_image’ function is defined to create the noise image. It takes a reference to a vector of tuples (‘image’), as well as the width and height of the image as its parameters. The function generates a random grayscale color for each pixel in the image using a random number generator (‘std::mt19937’) seeded by a random device (‘std::random_device’). The random number generator is used to create a uniform distribution of float values between 0.0 and 1.0, which represent the intensity of the grayscale color. In the nested for loop, the code iterates through each pixel of the image. It generates a random number (‘rndnum’) using the uniform distribution and the random number generator. This random number is then used to create a grayscale color by setting the red, green, and blue channels to the same value (‘rndnum’). The color is stored as a tuple and assigned to the corresponding index in the ‘image’ vector using the ‘get_index(x, y, width)’ function. This process is repeated for all the pixels in the image, resulting in a noise image.
Step #1: 16_draw_investment1.ppm
This C++ code generates a linear gradient image. It consists of two main parts: the declaration and initialization of variables, and the definition of the ‘generate_linear_gradient_image’ function. In the first part of the code, a vector of tuples called ‘image2’ is created to store the linear gradient image data. Each tuple holds three float values representing the red, green, and blue channels of a pixel. The width and height of the image are assumed to be already defined with values of 1600 and 900 pixels, respectively. The ‘image2’ vector is resized to the appropriate size using the ‘calc_size(width, height)’ function. The code then defines two tuples, ‘fcolor’ and ‘tcolor’, representing the starting and ending colors of the gradient, and calls the ‘generate_linear_gradient_image’ function. The ‘generate_linear_gradient_image’ function is defined to create the linear gradient image. It takes a reference to a vector of tuples (‘image’), references to the starting and ending colors (‘fcolor’ and ‘tcolor’), and the width and height of the image as its parameters. The function calculates the color of each pixel in the image by interpolating between the starting and ending colors based on
11 Presentation, Collaboration, and Investigation
51
the pixel’s position. First, the red, green, and blue channels of the starting and ending colors are extracted using the ‘std::tie’ function. Then, the code iterates through the height of the image using a for loop. In each iteration, it calculates the interpolation percentage based on the current vertical position of the pixel. The red, green, and blue channels of the interpolated color are calculated by linearly interpolating between the corresponding channels of the starting and ending colors using the calculated percentage. The interpolated color is then stored as a tuple. In the inner for loop, the code iterates through the width of the image. For each pixel in the current row, the code assigns the interpolated color to the corresponding index in the ‘image’ vector using the ‘get_index(x, y, width)’ function. This process is repeated for all the rows in the image, ultimately generating a linear gradient image.
Step #2: 16_draw_investment2.ppm
‘blended_two_images’ takes two input images and blends them together based on a specified alpha value. The images are passed as vectors of tuples (float, float, float), representing the red, green, and blue (RGB) color values of each pixel. The function also takes the width and height of the images and an alpha value, which is a float between 0 and 1. The function starts by initializing three tuples named ‘color1’, ‘color2’, and three float variables ‘r’, ‘g’, and ‘b’ that will be used to store the RGB values of each pixel in the two images and the resultant blended image. Two nested for loops are then used to iterate through each pixel in the input images. The outer loop iterates through the rows (height), while the inner loop iterates through the columns (width). For each pixel, the corresponding color values are retrieved from the input images using the ‘get_index’ function and stored in ‘color1’ and ‘color2’. The red, green, and blue values of the blended image are calculated by multiplying the corresponding color values from the second image by the alpha value, and the color values from the first image by (1 - alpha), then summing them. These blended RGB values are then stored in the first image vector using the ‘get_index’ function again. Once the loops have finished iterating through all the pixels, the function has effectively modified the first input image vector to store the blended image, while the second input image vector remains unchanged.
11 Presentation, Collaboration, and Investigation
52
Step #3: 16_draw_investment3.ppm
‘generate_radial_gradient_image’ creates a radial gradient image with a specified size and two colors. The radial gradient image is stored as a vector of tuples (float, float, float), representing the red, green, and blue (RGB) color values of each pixel. The function takes the following parameters: a reference to the image vector, references to two color tuples (fcolor and tcolor), and the width and height of the image. The function starts by initializing some variables: ‘color’ (a tuple to store the current pixel’s color), ‘pink’ (a pre-defined color tuple), ‘mask’ (a vector to store a circular mask), ‘distanceFromCenter’, ‘resultRed’, ‘resultGreen’, and ‘resultBlue’. The first nested for loops fill the ‘mask’ and ‘image’ vectors with the ‘pink’ color and ‘fcolor’ respectively. Then, the function calculates the midpoint of the image (x0, y0) and initializes variables ‘d’, ‘xx’, and ‘yy’ to draw a circle using Bresenham’s circle algorithm. A lambda function ‘drawline’ is defined within the function to draw horizontal lines between two points (sx, ex) at the given y-coordinate (ny) in the ‘mask’ vector. The while loop uses Bresenham’s circle algorithm to create a circular mask by drawing lines in the ‘mask’ vector using the ‘drawline’ function. Finally, another set of nested for loops iterates through each pixel in the image. For each pixel, it checks if the corresponding mask pixel is not equal to the ‘pink’ color. If so, it calculates the distance from the center of the image to the current pixel and computes the interpolated color values based on the distance and input colors (fcolor and tcolor). The resulting color tuple is assigned to the ‘image’ vector at the current position. If the mask pixel is equal to the ‘pink’ color, the ‘tcolor’ is assigned directly to the ‘image’ vector.
Step #4: 16_draw_investment4.ppm
‘generate_repeating_texture_image’ generates a repeating texture pattern with a given width, height, and color. The texture image is stored as a vector of tuples (float, float, float), representing the red, green, and blue (RGB) color values of each pixel.
11 Presentation, Collaboration, and Investigation
53
The function takes the following parameters: a reference to the image vector, a reference to the color tuple, and the width and height of the image. It begins by initializing a background color tuple named ‘bgcolor’ to be a pinkish color. Then, using two nested for loops, it fills the ‘image’ vector with this background color. The function proceeds to define two integer arrays ‘arr’ and ‘arr2’ with hardcoded coordinates and indices, representing the positions and connections of points in the pattern. The ‘coords’ tuple is initialized to store the coordinates of the line endpoints in the pattern. Next, the function iterates through the 13 hardcoded points using a for loop. During each iteration, it creates a ‘coords’ tuple using the coordinates from the ‘arr’ array based on the indices found in the ‘arr2’ array. The ‘draw_line’ function is then called with the ‘image’ vector, ‘coords’ tuple, input color, width, and height as parameters. This function draws lines between the coordinates specified by the ‘coords’ tuple in the image vector using the input color.
Step #5: 16_draw_investment5.ppm
‘render_repeating_texture_image’ takes a texture image and renders it repeatedly across a larger image. Both the texture and the larger image are stored as vectors of tuples (float, float, float), representing the red, green, and blue (RGB) color values of each pixel. The function takes the following parameters: a reference to the texture vector, the width and height of the texture (tw, th), a reference to the image vector, and the width and height of the image (iw, ih). It uses two nested for loops to iterate through each pixel in the larger image. For each pixel, the function retrieves the corresponding color value from the texture vector using the ‘get_index’ function, with the x and y coordinates of the pixel modulo (tw-1) and (th-1), respectively. This ensures that the texture is repeated across the entire image. The retrieved color value is then assigned to the corresponding pixel in the image vector.
11 Presentation, Collaboration, and Investigation
54
Step #6: 16_draw_investment6.ppm
‘do_cookie_cut’ extracts a rectangular section from a source image and stores it in another image, often referred to as ‘cookie-cutting’. Both the source image and the resulting image are stored as vectors of tuples (float, float, float), representing the red, green, and blue (RGB) color values of each pixel. The function takes the following parameters: a reference to the source image vector, the width and height of the source image (w, h), a reference to the resulting image vector, the width and height of the resulting image (cw, ch), and the starting x and y coordinates (startx, starty) of the rectangular section to be extracted from the source image. It initializes two variables, cx and cy, to keep track of the current x and y coordinates in the resulting image. The function uses two nested for loops to iterate through the rectangular section of the source image, starting from the specified starting coordinates (startx, starty) and covering the width and height of the resulting image (cw, ch). For each pixel in the section, it copies the color value from the source image to the corresponding position in the resulting image, using the ‘get_index’ function to convert the 2D coordinates to a linear index. The cx and cy variables are updated accordingly to move through the resulting image.
Step #7: 16_draw_investment7.ppm
‘add_honeycomb’ combines three images: a base image, an alpha image, and a texture image. The base image and the texture image are combined using the red channel of the alpha image as a weight. All three images are stored as vectors of tuples (float, float, float), representing the red, green, and blue (RGB) color values of each pixel. The function takes the following parameters: references to the base image vector, alpha image vector, texture image vector, and the width and height of the images (w, h). It initializes several tuple
11 Presentation, Collaboration, and Investigation
55
variables to store the color values of the three images at each pixel and a pink color tuple (1.0f, 0.0f, 1.0f) to check against the texture image. Using two nested for loops, the function iterates through each pixel of the images. If the color value of the texture image at the current pixel is not pink, the function retrieves the color values of the base, texture, and alpha images at the current pixel. It then calculates the new red, green, and blue color values for the base image by interpolating between the texture and base images’ color values using the red channel of the alpha image as a weight. The resulting color value is assigned back to the base image at the current pixel.
Step #8: 16_draw_investment8.ppm
‘stamp_cookie’ places a smaller ‘cookie’ image onto a larger base image at a specified position. The base image and the cookie image are stored as vectors of tuples (float, float, float), representing the red, green, and blue (RGB) color values of each pixel. The function takes the following parameters: references to the base image vector, its width and height (w, h), the cookie image vector, its width and height (cw, ch), and the starting x and y coordinates (startx, starty) for placing the cookie image onto the base image. The function initializes two integer variables, cx and cy, to keep track of the current position within the cookie image. It then uses two nested for loops to iterate through each pixel of the area where the cookie image will be placed on the base image. For each pixel in the specified area, the function retrieves the color value from the cookie image at the current cx and cy coordinates and assigns it to the base image at the current x and y coordinates. The cx variable is incremented for each iteration of the inner loop, while the cy variable is incremented for each iteration of the outer loop. After the inner loop is completed, the cx variable is reset to 0 to start processing the next row of the cookie image.
Step #9: 16_draw_investment9.ppm
The code creates an image of a doughnut shape with various shades of gray and white, as well as
11 Presentation, Collaboration, and Investigation
56
some additional labels. The doughnut consists of a series of filled wedges and lines drawn on a base image. The base image, ‘image7’, is initialized as a vector of tuples (float, float, float), representing the red, green, and blue (RGB) color values of each pixel. The base image is then resized to the desired width and height. The image is first cleared to a solid color (magenta) using the ‘clear_image’ function. Next, the doughnut shape is constructed by drawing filled wedges and lines with different shades of gray and white. The ‘draw_filled_wedge’ function is used to draw the filled wedges, while the ‘draw_line’ function is used to draw the lines. Each of these functions takes the image, coordinates, and color information as input. Finally, the ‘draw_labels’ function is called to add labels to the image. This function takes the image, color (white), width, and height as input.
Step #10: 16_draw_investment10.ppm
The code adds the previously created doughnut shape to the ‘image1’ by copying the non-magenta pixels from ‘image7’ to ‘image1’. The magenta color (1.0f, 0.0f, 1.0f) is used as a background color in ‘image7’ and is not copied to ‘image1’. The code first defines the magenta color as ‘fcolor’, which will be used to identify the background pixels that should not be copied. Then, two nested loops iterate over the height and width of the images. For each pixel coordinate (x, y), the code checks if the pixel in ‘image7’ is not magenta (i.e., not equal to ‘fcolor’). If the condition is true, the non-magenta pixel from ‘image7’ is copied to the corresponding position in ‘image1’. In summary, the code adds the doughnut shape from ‘image7’ to ‘image1’ by copying only the non-magenta pixels from ‘image7’ to their corresponding positions in ‘image1’.
Step #11: 16_draw_investment11.ppm
Pie chart in PPM, take a look in Appendix A: 16_draw_investment.cpp
57
11 Presentation, Collaboration, and Investigation
11.8 Collaboration We can use 2D charts and diagrams to help us understand and solve problems collaboratively. For example, we can use a chart to visualize the relationships between different elements in a problem or a diagram to explore the potential solutions to a problem. 2D charts and diagrams are also helpful for communicating our ideas to others. We can use them to explain our thinking process or present our findings clearly and concisely. When working with 2D graphs and diagrams, it is vital to know the different types of data they can represent. For instance, some charts are better suited for displaying quantitative data, while others are more effective for visualizing qualitative data. Additionally, we must be careful when interpreting data from 2D graphs and diagrams, as they can sometimes be misleading. Despite these limitations, 2D graphs and diagrams are powerful tools that can help us think more creatively and solve problems more effectively. With a bit of practice, we can learn to use them in various ways to improve our collaborative problem-solving skills. Peacock chart in PPM, take a look in Appendix A: 17_colab_peacock.cpp
peacock graph
11.9 Investigation There is a reason that businesses, law enforcement agencies, military intelligence use data visualization when investigating crimes or irregularities - it works. By taking large data sets and representing them in an easily digestible format, investigators can quickly identify patterns and relationships that would otherwise be difficult to spot. The right data can help investigators piece together what happened and who was involved. Also for a security incident a simple graph can give you some important information as when something happend and the amout of actvity. Also what kind of activity in the log.
Investigating log
12 PPM++ header-only library Hello everyone, I am pleased to announce the release of my c++ header-only library for 2D graphics. This library is designed to make it easy to create beautiful and performant 2D graphics applications using c++17. It is available on Github under the MIT license. I would love to hear your feedback! You’ll find the header-only library here: https://gist.github.com/chbtoys/cdd6a85e321593ea0a1e7141d0a09fbc … and a test.cpp here: https://gist.github.com/chbtoys/a665b52f86768b8e601c82b47ce18e49 Or clone from Github.com. 1 2 3 4
git clone https://github.com/chbtoys/ppmpp.git cd ppmpp clang++ -std=c++20 test.cpp -o test ./test
(C++20 for std::lerp when creating bezier curves)
13 In the Rearview Mirror What’s next? Maybe make a header-only .HPP framework of your own or extend the one that already exists. There are of course more 2D graphic techniques then included in the book. Please, feel free to expande the code the way you want. There are some helpfull tools that definitely is nice to have. Image Magick1 to convert PPM to PNG/JPG/GIF and FFMPEG2 to combine still images into a animation. Both are nice tools to have. Thank you for your time.
1 https://imagemagick.org/index.php 2 https://ffmpeg.org/
14 Appendix A: Source Code Listings 01_tuples.cpp - 1521 bytes. 1
// Compile: clang++ -std=c++17 01_tuples.cpp -o 01_tuples
2 3 4
#include #include
5 6 7 8 9
std::tuple get_rgb_elements_as_a_tuple(int r, int g, int b) { return std::make_tuple(r, g, b); }
10 11 12 13 14 15 16
int main() { int r1=212; int g1=24; int b1=118; int r2=0; int g2=0; int b2=0; int r3=0; int g3=0; int b3=0; std::tuple rgb;
17 18 19 20
std::cout