[c++ / SFML] [µPaint][2] Ein minimalistisches Zeichenprogramm
English version: here
Demonstrations-video: hier
Wegen der positiven Reaktionen auf den ersten Teil hier nun der Followup, in dem das Programm um die Möglichkeit zum Zeichnen von Rechtecken erweitert wurde.
Wie im ersten Teil, soll auch dieser zeigen, wie einfach es sein kann ein eigenes kleines Zeichenprogramm zu programmieren bzw. dieses zu erweitern. Implementierbare Features wären z.b. noch (nach Aufwand sortiert): Kreise, zoomen oder das Löschen des zuletzt hinzugefügten Elements durch Strg + Z .
Weiterentwicklungen des Codes, Fragen, Anregungen, etc. gerne in den Kommentare hinterlassen.
Setup
Es wurde c++ und die SimpleFastMultimediaLibrary (SFML) zum erstellen dieses Programms benutzt. Installation von SFML für VisualStudio: hier
Da steemit (noch) kein Syntax-Highlighting unterstützt, kann der unten gezeigte code zur besseren Lesbarkeit in einen Editor, wie Notepad++ kopiert werden.
Code
Wie im ersten Teil wurden auch hier wieder VertexArrays verwendet.
Wieso?
Wesentlich geringerer Speicherbedarf. Alternativ zu VertexArrays bietet SFML auch RectangleShapes. Beim betrachten der Member_Variablen in Shape.hpp (von der RectangleShape erbt) sollte klar werden, dass das RectangleShape mehr Speicherplatz belegt.
Um aber genau zu sein: (Siehe code)
Man Beachte bei der Speicherbedarfmessung, dass die Größe des RectangleShapes minimal und die Größe des VertexArrays (so wie es in meinem Code verwendet wird) maximal war.
Funktionsweise (Benutzer)
Mehrere Ansätze sind möglich. Es würde z.b. auch reichen, wenn man nur vollständig ausgefüllte Rechtecke zeichnen wollen würde, dass nur 4 vertices für jeden Eckpunkt verwendet werden (siehe sf::Quads). Mit dieser Methode würden zwei Benutzereingaben, nämlich zwei gegenüberliegende Eckpunkte zum konstruieren des Rechtecks, reichen.
Um einen beliebig großen Rand zu realisieren werden 3 Punkte benötigt:
Maus Button loslassen -> Position Punkt 1
Maus Button drücken -> Position Punkt 2
Maus Button loslassen -> Position Punkt 3 (Final)
Punkt 2 ist nur dann != Punkt 3, wenn der Maus Button gedrückt gehalten wird und die Maus vor dem Loslassen verschoben wird.
Für den Benutzer heißt das: Er klickt einmal für Position 1, dann verschiebt er seine Maus, je nach gewünschter Randgröße, klickt und hält gedrückt, verschiebt seine Maus an die Endposition und lässt los.
Hinweis: Um zwischen den Zeichenmodi Rechteck und Linie hin und her zu wechseln, müssen die Tasten "r" bzw "l" gedrückt werden.
Funktionsweise (Programmablauf)
Die Variable "R_first_MB_released" gibt an, ob der Benutzer den ersten Punkt schon festgelegt hat, oder nicht.
"R_locked" wiederum, gibt die Berechnung des Rechtecks, wenn zwei Punkte bereits definiert wurden frei. (Es wird angenommen, dass der 3. Punkt auf der aktuellen Mausposition liegt -> Preview des Rechtecks beim Zeichnen möglich)
Berechnung der 16 vertices
4 Punkte bilden ein Trapez (sf::Quads) und werden wie oben zu sehen nur durch ihre Positionen gelinkt.
Punkt 0 und Punkt 15 sind definiert durch den 1. vom Benutzer festgelegten Punkt.
Punkt 1 und 14 durch die 2.
Punkt 8 und 7 werden auf die derzeitige Maus Position gesetzt (Preview möglich).
Beachte: Maus Position - Koordinaten mapping, wie in Teil 1 beschrieben.
Alle anderen Punkte können daraus berechnet werden. Dazu betrachtet man jeweils den x und y Teil eines Punktes separat.
Z.B. Punkt 6 (7.x - (1.x - 0.x ) | 7.y - (1.y - 0.y))
Nebeneffekt des Ansatzes: Je nachdem wo man die 3 Punkte definiert, kann man neben Rechtecken auch 6 Ecke und um 45° gedrehte gefüllte Rechtecke zeichnen:
c++
#include "SFML\Graphics.hpp"
#include <time.h> // Nur für srand()
#include <iostream>
#include <vector>
int main()
{
srand(time(NULL)); // rand() mit der aktuellen Zeit initialisieren
std::vector<sf::VertexArray> vertices; // vector in dem alle vertexArrays (Linien, Rechtecke) gespeichert werden
enum class states { Line, Rectangle, Circle }; // Derzeitiger Zeichenmodus
states current_state = states::Line;
// Linien
vertices.push_back(sf::VertexArray()); // Die 1. Linie
vertices[0].setPrimitiveType(sf::LinesStrip); // Der PrimitiveType der 1. Linie: see https://www.sfml-dev.org/tutorials/2.4/graphics-vertex-array.php
// int lines_number = 0; // Index der aktuellen Linie -> fällt raus, weil nur das letzte Element erreicht werden muss und das mit vertices[vertices.size() -1] errreicht werden kann
bool L_locked = false; // (Nur im Linien Modus) Wenn ein Maus Button gedrückt wurde wird diese Variable true, wenn einer losgelassen wird false
// Rechtecke
bool R_first_MB_released = false; // (Nur im Rechtecks-Modus) Wird true, wenn zum ersten mal ein Mouse Button losgelassen wurde, false beim zurücksetzen
sf::Vector2f R_first; // Position des ersten losgelassenen Mous Buttons
sf::Vector2f R_second; // Zweite Position (Mouse Button gedrückt, nachdem R_first gesetzt wurde); Dritte Position = aktuelle Mouse_pos
bool R_locked = false;
sf::VertexArray construction(sf::Quads); // Das Rechteck, das aktuell konstruiert wird
for (int i = 0; i < 16; i++) // Vorbelegen
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; // Farbe der vertices
sf::Vector2i last_Mouse_pos(0, 0);
sf::RenderWindow window(sf::VideoMode(1280, 720), "µPaint", sf::Style::Close, sf::ContextSettings(0, 0, 0)); // Das Fenster in dem alles gezeichnet wird
window.setFramerateLimit(60);
sf::Vector2i Border_Offset(-5, -25); // Siehe Eigenheiten (Hintergrundwissen in Teil 1: https://steemit.com/de-stem/@numbo/c-sfml-paint-ein-minimalistisches-zeichenprogramm ): Rand
while (window.isOpen())
{
// Events
sf::Event event;
while (window.pollEvent(event))
{
if (event.type == sf::Event::KeyPressed) // Fenster schließen
if (event.key.code == sf::Keyboard::Key::Escape)
window.close();
if (event.type == sf::Event::Closed) // Fenster schließen
window.close();
if (event.type == sf::Event::KeyPressed) // Zwischen den Zeichenzuständen wechseln
{
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) // Nur wenn erster Punkt des Rechtecks schon definiert ist den zweiten Punkt festlegen
{
R_second = sf::Vector2f(sf::Mouse::getPosition() - window.getPosition() + Border_Offset);
R_locked = true;
}
}
else if (current_state == states::Circle)
{
// Selber implementieren
}
}
if (event.type == sf::Event::MouseButtonReleased) // Siehe "locked" Definition
{
if (current_state == states::Line)
{
// Neue Linie hinzufügen
vertices.push_back(sf::VertexArray());
vertices[vertices.size() - 1].setPrimitiveType(sf::LinesStrip);
L_locked = false; // Reset Linie
}
else if (current_state == states::Rectangle)
{
if (!R_first_MB_released) // Erste Position festgelegt
{
R_first_MB_released = true;
R_first = sf::Vector2f(sf::Mouse::getPosition() - window.getPosition() + Border_Offset);
}
if (R_first_MB_released && R_locked) // Finale Positon festegelegt -> speichern und zurücksetzen
{
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++) // Würde sonst noch mitgezeichnet werden, obwohl das eigentliche Rechteck schon in "vertices" gespeichert wurde -> Zeichnen über construction würde nicht funktionieren.
construction[i].position = sf::Vector2f(0, 0);
}
}
else if (current_state == states::Circle)
{
// Selber implementieren
}
}
} // Ende Events
//Linie konstruieren
if (L_locked) // Siehe "locked" Definition
{
if (last_Mouse_pos != sf::Mouse::getPosition()) // Nur hinzufügen, wenn sich die Maus bewegt hat (Arbeitsspeicher nicht sinnlos verschwenden)
{
//.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();
}
}
// Rechteck konstruieren
if (R_locked)
{
if (last_Mouse_pos != sf::Mouse::getPosition())
{
//Hier aktuelles Rechteck berechnen (siehe Skizze in Teil 2)
// = QuadsStrip DIY
sf::Vector2f render_mouse_pos(sf::Mouse::getPosition() - window.getPosition() + Border_Offset); // siehe Maus-Koordinaten mapping in Teil 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(); // Maus position zurücksetzen
}
}
//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); // Die Leinwand mit einener bestimmten Farbe löschen
// Alles zeichnen
for (int i = 0; i < vertices.size(); i++) // Die ersten Objekte zuerst zeichnen -> Am weitesten hinten (Überschneidungen)
{
window.draw(vertices[i]);
}
window.draw(construction); // Konstruktions-Rechteck zeichnen -> für preview
window.display();
}
return 0;
}
Being A SteemStem Member
Hallo @numbo, herzlich willkommen auf Steemit.
Wenn Du Fragen zu Steemit hast, oder Dich mit anderen deutschen „Steemians“ austauschen magst, schau einfach mal auf unserem Discord-Server https://discord.gg/g6ktN45 vorbei.
Unter dem folgenden Link findest Du einige Anleitungen, die Dir den Einstieg in das Steem-Universum deutlich erleichtern werden: Deutschsprachige Tutorials für Steemit-Neulinge: Ein Überblick