[c++ / SFML] [µPaint][2] A simple drawing program

in #steemstem7 years ago

Thumbnail3.jpg
German version: hier
Demonstration video: here
Due to the positive reactions on the first part this followup is going to show a way on how to implement a rectangle drawing feature.
Again this programm can be further developed. Features like (sorted by effort) drawing circles, zooming or deleting the last element via ctrl + z could be implemented.

I'm looking forward to answer questions, suggestions in the comments.

Setup

First of all c++ and the SimpleFastMultimediaLibrary (SFML) are used to create this minimalistic program. Install guide of SFML for Visual Studio: hier

Since steemit doesn't support syntax highlighting (yet), I suggest copying the below code to a program of your choice, that supports it. Like Notepad++.

Code

Like in the first part VertexArrays are used to construct the rectangles.
But why?
Simply less memory usage. An alternative to VertexArrays would be RectangleShapes. But when looking at the member variables in Shape.hpp (RectangleShape inherits that class) it should be clear, that RectangleShape uses more memory.
To be more precise: RectangleShapevsVertexArray.jpg (See code)
Note that here the memory usage of the RectangleShape was at its minimum and the memory usage of the VertexArray (as it is used in the code: 16 vertices) was at its maximum.

Functional description (user)

Several methods could be considered. If you just wanted to draw fully filled rectangles, 4 vertices would be enough (see sf::Quads). This method would also only require two opposing points to construct the rectangle.

In order to realize being able to adjust the border thickness 3 points are required:
Release a mouse button -> position of point 1
Press a mouse button -> position of point 2
Release a mouse button -> position of point 3 (final)

Point 2 will only not be the same as point 3, when the mouse button will be held down after pressing it and moving the mouse to the final position before releasing it.
Concrete (user):

  1. Click once to set the first point
  2. Move the mouse as far as you want the border thickness to be
  3. Click and hold
  4. Move mouse to the final position and release the button

Note: In order to switch between the line and rectangle drawing modes you need to press "r" and "l" respectively.

Functional description (behind the scenes)

The variable "R_first_MB_released" gives evidence, whether or not the 1st point is defined.
"R_locked" will allow the preview rectangle to be calculated and drawn, since it is assumed that the 3rd point is located at the current mouse position.

Calculation of the 16 vertices

Rechtecksberechnung.jpg

Basically 4 points will form a trapezoid (sf::Quads), wich are linked as shown in the diagram above via their positions.
Points 1 and 15 are defined through the 1st point the user specified.
Points 2 and 14 through the 2nd one.
Points 8 and 7 are set to the current mouse position, wich allows a preview of the rectangle.
Note the mouse_position-coordinate mapping as shown in the first part.

All the other points can be calculated, when separating the x and y portion.
Fore example point 6 (7.x - (1.x - 0.x ) | 7.y - (1.y - 0.y))

A nice side effect of this method: Depending on the arrangement of the 3 points you are able to even draw hexagons and 45° rotated filled rectangles: 6Ecke.jpg

c++


#include "SFML\Graphics.hpp"
#include <time.h> // srand()
#include <iostream>
#include <vector>


int main()
{

    srand(time(NULL)); // initialize rand() by using the current system time

    std::vector<sf::VertexArray> vertices; // vector in wich all vertexArrays (lines, rectangles) will be stored

    enum class states { Line, Rectangle, Circle }; // current drawing mode
    states current_state = states::Line;

    // Lines
    vertices.push_back(sf::VertexArray()); // 1st line
    vertices[0].setPrimitiveType(sf::LinesStrip); // PrimitiveType of the 1st line: see https://www.sfml-dev.org/tutorials/2.4/graphics-vertex-array.php
    bool L_locked = false; // (only when in line mode) 


    // Rectangles
    bool R_first_MB_released = false; // (only when in rectangle mode)
    sf::Vector2f R_first; // position of the 1st mouse button released
    sf::Vector2f R_second; // position of the 2nd mouse button released
    bool R_locked = false; 
    sf::VertexArray construction(sf::Quads); // construction rectangle (preview)
    for (int i = 0; i < 16; i++) 
        construction.append(sf::Vertex());

    //sf::RectangleShape s;
    //std::cout << "RectangleShape: " << sizeof(s) << ", VertexArray: " << sizeof(construction) << std::endl; std::cin.get(); std::cin.get(); // RectangleShape vs VertexArray
    

    sf::Color curr_col = sf::Color::Black; // color 
    sf::Vector2i last_Mouse_pos(0, 0);

    sf::RenderWindow window(sf::VideoMode(1280, 720), "µPaint", sf::Style::Close, sf::ContextSettings(0, 0, 0)); // where everythings drawn to
    window.setFramerateLimit(60);

    sf::Vector2i Border_Offset(-5, -25); // See (peculiarities in the 1st part: https://steemit.com/steemstem/@numbo/c-sfml-paint-a-simple-drawing-program )

    while (window.isOpen())
    {

        // Events
        sf::Event event;
        while (window.pollEvent(event))
        {

            if (event.type == sf::Event::KeyPressed) // closing window
                if (event.key.code == sf::Keyboard::Key::Escape)
                    window.close();
            if (event.type == sf::Event::Closed) // closing window
                window.close();

            if (event.type == sf::Event::KeyPressed) // Switch between drawing modes
            {
                if (event.key.code == sf::Keyboard::Key::L)
                    current_state = states::Line;
                else if (event.key.code == sf::Keyboard::Key::R)
                    current_state = states::Rectangle;
                else if (event.key.code == sf::Keyboard::Key::C)
                    current_state = states::Circle;
            }

            if (event.type == sf::Event::MouseButtonPressed)
            {
                if (current_state == states::Line)
                    L_locked = true;
                else if (current_state == states::Rectangle)
                {
                    if (R_first_MB_released) // Only when the 1st button is already defined, define the second one
                    {
                        R_second = sf::Vector2f(sf::Mouse::getPosition() - window.getPosition() + Border_Offset);
                        R_locked = true;
                    }
                }
                else if (current_state == states::Circle)
                {
                    // DIY
                }

            }

            if (event.type == sf::Event::MouseButtonReleased) 
            {

                if (current_state == states::Line)
                {
                    // Add a new line
                    vertices.push_back(sf::VertexArray());
                    vertices[vertices.size() - 1].setPrimitiveType(sf::LinesStrip);

                    L_locked = false; // Reset line
                }
                else if (current_state == states::Rectangle)
                {
                    if (!R_first_MB_released) // Define the 1st position
                    {
                        R_first_MB_released = true;
                        R_first = sf::Vector2f(sf::Mouse::getPosition() - window.getPosition() + Border_Offset);
                    }
                    if (R_first_MB_released && R_locked) // Set the final position -> save and reset
                    {
                        vertices.push_back(construction);

                        R_first_MB_released = false;
                        R_locked = false;

                        curr_col = sf::Color::Color(rand() % 255, rand() % 255, rand() % 255);

                        for (int i = 0; i < construction.getVertexCount(); i++)  
                            construction[i].position = sf::Vector2f(0, 0);
                    }
                }
                else if (current_state == states::Circle)
                {
                    // DIY
                }



            }
        } // End Events


        // Construction of a line
        if (L_locked) 
        {
            if (last_Mouse_pos != sf::Mouse::getPosition()) // Only add, when the mouse has moved (save memory)
            {
                //.append(Position, Farbe) : .append(MousePos - WindowPos + MouseOffset, curr_col)
                vertices[vertices.size() - 1].append(sf::Vertex(sf::Vector2f(sf::Mouse::getPosition().x - window.getPosition().x + Border_Offset.x, sf::Mouse::getPosition().y - window.getPosition().y + Border_Offset.y), curr_col));

                last_Mouse_pos = sf::Mouse::getPosition();
            }
        }

        // Construction of a rectangle
        if (R_locked)
        {
            if (last_Mouse_pos != sf::Mouse::getPosition())
            {
                //calculate (see Functional description (behind the scenes))
                // = QuadsStrip DIY

                sf::Vector2f render_mouse_pos(sf::Mouse::getPosition() - window.getPosition() + Border_Offset); // see mouse coordinate mapping in part 1
                
                construction[0] = sf::Vertex(R_first, curr_col);
                construction[1] = sf::Vertex(R_second, curr_col);
                construction[2] = sf::Vertex(sf::Vector2f(render_mouse_pos.x - (R_second.x - R_first.x), R_second.y), curr_col);
                construction[3] = sf::Vertex(sf::Vector2f(render_mouse_pos.x, R_first.y), curr_col);
                            
                construction[4] = sf::Vertex(construction[3].position, curr_col);
                construction[5] = sf::Vertex(construction[2].position, curr_col);
                construction[6] = sf::Vertex(sf::Vector2f(render_mouse_pos.x - (R_second.x - R_first.x), render_mouse_pos.y - (R_second.y - R_first.y)), curr_col);
                construction[7] = sf::Vertex(sf::Vector2f(render_mouse_pos), curr_col);
                            
                construction[8] = sf::Vertex(construction[7].position, curr_col);
                construction[9] = sf::Vertex(construction[6].position, curr_col);
                construction[10] = sf::Vertex(sf::Vector2f(construction[1].position.x, construction[6].position.y), curr_col);
                construction[11] = sf::Vertex(sf::Vector2f(construction[0].position.x, construction[7].position.y), curr_col);
                            
                construction[12] = sf::Vertex(construction[11].position, curr_col);
                construction[13] = sf::Vertex(construction[10].position, curr_col);
                construction[14] = sf::Vertex(construction[1].position, curr_col);
                construction[15] = sf::Vertex(construction[0].position, curr_col);

                last_Mouse_pos = sf::Mouse::getPosition(); // reset mouse position

            }
        }

        //curr_col = sf::Color::Color(rand() % 255, rand() % 255, rand() % 255);
        

        //std::cout << "vertices in line " << lines_number << ": " << vertices[lines_number].getVertexCount() << std::endl;

        std::cout << "V1: " << R_first.x << " | " << R_first.y << " V2: " << R_second.x << " | " << "vertices-vec: " << vertices.size() << std::endl;

        window.clear(sf::Color::White); // delete the current scene with a specific color

        // draw everything
        for (int i = 0; i < vertices.size(); i++) 
        {
            window.draw(vertices[i]);
        }

        window.draw(construction); // construction rectangle -> preview

        window.display();
    }


    return 0;
}

Sort:  

Congratulations @numbo! You have completed some achievement on Steemit and have been rewarded with new badge(s) :

Award for the number of upvotes

Click on any badge to view your own Board of Honor on SteemitBoard.

To support your work, I also upvoted your post!
For more information about SteemitBoard, click here

If you no longer want to receive notifications, reply to this comment with the word STOP

Upvote this notification to help all Steemit users. Learn why here!

Congratulations @numbo! You have completed some achievement on Steemit and have been rewarded with new badge(s) :

Award for the number of upvotes

Click on any badge to view your own Board of Honor on SteemitBoard.
For more information about SteemitBoard, click here

If you no longer want to receive notifications, reply to this comment with the word STOP

Upvote this notification to help all Steemit users. Learn why here!

Do not miss the last announcement from @steemitboard!

Congratulations @numbo! You have completed some achievement on Steemit and have been rewarded with new badge(s) :

Award for the number of upvotes

Click on any badge to view your own Board of Honor on SteemitBoard.
For more information about SteemitBoard, click here

If you no longer want to receive notifications, reply to this comment with the word STOP

Upvote this notification to help all Steemit users. Learn why here!

Congratulations @numbo! You have completed some achievement on Steemit and have been rewarded with new badge(s) :

Award for the number of upvotes

Click on the badge to view your Board of Honor.
If you no longer want to receive notifications, reply to this comment with the word STOP

Do not miss the last post from @steemitboard!


Participate in the SteemitBoard World Cup Contest!
Collect World Cup badges and win free SBD
Support the Gold Sponsors of the contest: @good-karma and @lukestokes


Do you like SteemitBoard's project? Then Vote for its witness and get one more award!

Congratulations @numbo! You have completed the following achievement on Steemit and have been rewarded with new badge(s) :

Award for the number of upvotes

Click on the badge to view your Board of Honor.
If you no longer want to receive notifications, reply to this comment with the word STOP

Do not miss the last post from @steemitboard:
SteemitBoard World Cup Contest - Final results coming soon

Do you like SteemitBoard's project? Then Vote for its witness and get one more award!

Congratulations @numbo! You have completed the following achievement on the Steem blockchain and have been rewarded with new badge(s) :

You made more than 1500 upvotes. Your next target is to reach 1750 upvotes.

Click here to view your Board of Honor
If you no longer want to receive notifications, reply to this comment with the word STOP

Do not miss the last post from @steemitboard:

SteemFest3 and SteemitBoard - Meet the Steemians Contest

Support SteemitBoard's project! Vote for its witness and get one more award!

Congratulations @numbo! You have completed the following achievement on the Steem blockchain and have been rewarded with new badge(s) :

You made more than 1750 upvotes. Your next target is to reach 2000 upvotes.

Click here to view your Board
If you no longer want to receive notifications, reply to this comment with the word STOP

Support SteemitBoard's project! Vote for its witness and get one more award!

Congratulations @numbo! You received a personal award!

Happy Birthday! - You are on the Steem blockchain for 1 year!

Click here to view your Board

Support SteemitBoard's project! Vote for its witness and get one more award!

Congratulations @numbo! You received a personal award!

Happy Birthday! - You are on the Steem blockchain for 2 years!

You can view your badges on your Steem Board and compare to others on the Steem Ranking

Vote for @Steemitboard as a witness to get one more award and increased upvotes!