Search code examples
c++sfml

Using SFML to draw stuff


I'm trying to create a scribble clone for a uni project.

My lines are basically a lot of dots. If I draw too quick, the line breaks and I have single dots. Also, I can't figure out how to draw lines ONLY when a mouse button is pressed. I tried to put mouseMoved into a while loop until mouseButton is released but that didn't seem to work. I ended up in an infinite loop.

Here is my code so far:

    while (window.isOpen())
    {

        while (window.pollEvent(event))
        {

            int mouseButtonX = event.mouseButton.x;
            int mouseButtonY = event.mouseButton.y;

            int mouseMoveX = event.mouseMove.x;
            int mouseMoveY = event.mouseMove.y;

            setBrushSize(5);

            brush.setRadius(brushSize);
            brush.setPosition(mouseMoveX - brushSize, mouseMoveY - brushSize);
            brush.setFillColor(sf::Color::Transparent);
            brush.setOutlineColor(sf::Color::Green);
            brush.setOutlineThickness(2);

            switch (event.type) {
            case (sf::Event::Closed):
                window.close();
                break;

            case (sf::Event::KeyPressed):
                if (event.key.control && event.key.code == sf::Keyboard::X) {
                    cout << "closing";

                    window.close();
                }

                if (event.key.code == sf::Keyboard::R) {
                    cout << "printed";
                    brushColor = setBrushColor(255, 0, 0);
                }
                if (event.key.code == sf::Keyboard::G) {
                    brushColor = setBrushColor(0, 255, 0);
                }
                if (event.key.code == sf::Keyboard::B) {
                    brushColor = setBrushColor(0, 0, 255);
                }
                if (event.key.code == sf::Keyboard::C) {
                    for (int i = 0; i < points.size(); i++) {
                        points.clear();
                    }
                    it = 0;
                }
                break;

            case(sf::Event::MouseButtonPressed):
                points.push_back(point);
                points[it].setRadius(brushSize);
                points[it].setFillColor(brushColor);
                points[it].setPosition(mouseButtonX - brushSize, mouseButtonY - brushSize);
                it++;
                cout << "drawPoint: Pressed X = " << mouseButtonX << " Y = " << mouseButtonY << endl;

                break;

            case(sf::Event::MouseMoved):
                points.push_back(point);
                points[it].setRadius(brushSize);
                points[it].setFillColor(brushColor);
                points[it].setPosition(mouseMoveX - brushSize, mouseMoveY - brushSize);
                it++;
                cout << "drawPoint: Moved X = " << mouseMoveX << " Y = " << mouseMoveY << endl;
                break;
            }
        }


        window.clear(sf::Color(255, 247, 204));
        window.draw(SkechyT);
        window.draw(close);
        window.draw(brush);
        window.draw(color);
        window.draw(clear);


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

        //window.draw(point);

        window.display();
    }
}


int getBrushSize() {
    return brushSize;
}

void setBrushSize(int num) {
    brushSize = num;

}

sf::Color setBrushColor(int r, int g, int b) {
    return sf::Color(r, g, b);
}

~Visualizer();

};


Solution

  • While you can modify a sf::VertexArray on the fly (basically building a vector drawing app), you can also use a sf::RenderTexture as an actual drawing canvas.

    Considering you tried drawing lots of small points, I'd assume you're aiming for the latter. What's important here is the fact that you don't necessarily have to clear render textures between drawing calls and can therefore preserve whatever has been drawn before.

    Combined with the original goal – drawing – this becomes very easy.

    All you have to do is draw the changes (e.g. when moving the cursor), finalize the render texture (by calling display()), and then presenting it using any drawable (like sf::Sprite.

    I've quickly scribbled together the following example, which should show the concept rather well (and you won't run into an endless loop other than the actual main loop):

    #include <SFML/Graphics.hpp>
    #include <vector>
    
    int main(int argc, char **argv) {
        sf::RenderWindow window(sf::VideoMode(800, 600), L"SFML Drawing – C to clear, PageUp/PageDown to pick colors", sf::Style::Default);
        // Set a specific frame rate, since we don't want to
        // worry about vsync or the time between drawing iterations
        window.setVerticalSyncEnabled(false);
        window.setFramerateLimit(100);
    
        // First we'll use a canvas to basically store our image
        sf::RenderTexture canvas;
        canvas.create(800, 600);
        canvas.clear(sf::Color::White);
    
        // Next we'll need a sprite as a helper to draw our canvas
        sf::Sprite sprite;
        sprite.setTexture(canvas.getTexture(), true);
    
        // Define some colors to use
        // These are all with very low alpha so we
        // can (over-)draw based on how fast we move the cursor
        const std::vector<sf::Color> colors = {
            sf::Color(255, 0, 0, 8),
            sf::Color(255, 255, 0, 8),
            sf::Color(0, 255, 0, 8),
            sf::Color(0, 255, 255, 8),
            sf::Color(0, 0, 255, 8),
            sf::Color(255, 0, 255, 8)
        };
    
        // We'll need something to actually draw
        // For simplicity, I'm just drawing a circle shape
        // but you could also draw a line, rectangle, or something more complex
        const float brush_size = 25;
        sf::CircleShape brush(brush_size, 24);
        brush.setOrigin(brush_size, brush_size); // Center on the circle's center
    
        sf::Vector2f lastPos;
        bool isDrawing = false;
        unsigned int color = 0;
    
        // Apply some default color
        brush.setFillColor(colors[color]);
    
        while (window.isOpen()) {
            sf::Event event;
            while (window.pollEvent(event)) {
                switch (event.type) {
                case sf::Event::Closed:
                    window.close();
                    break;
                case sf::Event::KeyPressed:
                    switch (event.key.code) {
                        case sf::Keyboard::C:
                            // Clear our canvas
                            canvas.clear(sf::Color::White);
                            canvas.display();
                            break;
                        case sf::Keyboard::PageUp:
                            // Get next color
                            color = (color + 1) % colors.size();
                            // Apply it
                            brush.setFillColor(colors[color]);
                            break;
                        case sf::Keyboard::PageDown:
                            // Get previous color
                            color = (color - 1) % colors.size();
                            // Apply it
                            brush.setFillColor(colors[color]);
                            break;
                    }
                    break;
                case sf::Event::Resized:
                    {
                        // Window got resized, update the view to the new size
                        sf::View view(window.getView());
                        const sf::Vector2f size(window.getSize().x, window.getSize().y);
                        view.setSize(size); // Set the size
                        view.setCenter(size / 2.f); // Set the center, moving our drawing to the top left
                        window.setView(view); // Apply the view
                        break;
                    }
                case sf::Event::MouseButtonPressed:
                    // Only care for the left button
                    if (event.mouseButton.button == sf::Mouse::Left) {
                        isDrawing = true;
                        // Store the cursor position relative to the canvas
                        lastPos = window.mapPixelToCoords({event.mouseButton.x, event.mouseButton.y});
    
                        // Now let's draw our brush once, so we can
                        // draw dots without actually draging the mouse
                        brush.setPosition(lastPos);
    
                        // Draw our "brush"
                        canvas.draw(brush);
    
                        // Finalize the texture
                        canvas.display();
                    }
                    break;
                case sf::Event::MouseButtonReleased:
                    // Only care for the left button
                    if (event.mouseButton.button == sf::Mouse::Left)
                        isDrawing = false;
                    break;
                case sf::Event::MouseMoved:
                    if (isDrawing)
                    {
                        // Calculate the cursor position relative to the canvas
                        const sf::Vector2f newPos(window.mapPixelToCoords(sf::Vector2i(event.mouseMove.x, event.mouseMove.y)));
    
                        // I'm only using the new position here
                        // but you could also use `lastPos` to draw a
                        // line or rectangle instead
                        brush.setPosition(newPos);
    
                        // Draw our "brush"
                        canvas.draw(brush);
    
                        // Finalize the texture
                        canvas.display();
                        break;
                    }
                }
            }
    
            // Clear the window
            window.clear(sf::Color(64, 64, 64));
    
            // Draw our canvas
            window.draw(sprite);
    
            // Show the window
            window.display();
        }
    
        return 0;
    }
    

    Once running, you can start drawing using the left mouse button. C will clear the canvas and Page Up and Page Down allow you to pick a different color:

    Screenshot of the example running

    Edit: And just to mention it, in the example above, rather than drawing a circle, you can just draw a sf::VertexArray with sf::Lines and two vertices: lastPos and newPos. This way you'll always draw a continuous line. (But you'd obviously have to save/update lastPos with the value of newPos once you're done.)