qt rysowanie krzywej beziera - problem ze zrozumieniem przesuwania pikseli

0

Na zajeciach z programowania przerabialismy kod z rysowania krzywej beziera niestety mnie na tych zajeciach nie było i staram się nadrobic zaleglosci niestety sam nie potrafie sobie poradzic a przegladajac internet nie potrafie znalezc satysfakcjonujacych mnie odpowiedzi. Głownie chodzi mi o dwie funkcje mianowicie:

void MyWindow::mousePressEvent(QMouseEvent *event)
void MyWindow::mouseMoveEvent(QMouseEvent *event)

Czy ktos byłby chętny pomoc w zrozumieniu kodu ?


#include "mywindow.h"
#include "ui_mywindow.h"
#include <qmath.h>
 
MyWindow::MyWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MyWindow)
{
    ui->setupUi(this);
 
    szer = ui->rysujFrame->width();
    wys = ui->rysujFrame->height();
    poczX = ui->rysujFrame->x();
    poczY = ui->rysujFrame->y();
 
    img = new QImage(szer,wys,QImage::Format_RGB32);
 
    punktprzesun = -1;
}
 
MyWindow::~MyWindow()
{
    delete ui;
}
 
void MyWindow::on_exitButton_clicked()
{
    qApp->quit();
}
 
void MyWindow::paintEvent(QPaintEvent*)
{
    QPainter p(this);
 
    p.drawImage(poczX,poczY,*img);
 
    for(int i=0; i<wektor.size(); i++){
        p.drawImage(wektor[i].x(),wektor[i].y(),QImage(15,15,QImage::Format_RGB32));
    }
}
 
void MyWindow::on_cleanButton_clicked()
{
    czysc();
    update();
    wektor.resize(0);
    licz_klikniecia = 0;
}
 
void MyWindow::czysc()
{
    unsigned char *ptr;
 
    ptr = img->bits();
 
    int i,j;
 
    for(i=0; i<wys; i++)
    {
        for(j=0; j<szer; j++)
        {
            ptr[szer*4*i + 4*j]=255;
            ptr[szer*4*i + 4*j + 1] = 255;
            ptr[szer*4*i + 4*j + 2] = 255;
        }
    }
}
 
void MyWindow::zapalPiksel(int x, int y)
{
    unsigned char *ptr;
    ptr = img->bits();
 
    if ((x>=0)&&(y>=0)&&(x<szer)&&(y<wys))
    {
        ptr[szer*4*y + 4*x] = 0;
        ptr[szer*4*y + 4*x + 1] = 0;
        ptr[szer*4*y + 4*x + 2] = 0;
    }
 
    update();
}
 
void MyWindow::rysujBeziera(QPoint p0, QPoint p1, QPoint p2, QPoint p3){
    QPoint punkt;
 
    for(double t = 0.0; t<=1.0; t+=0.001){
        punkt = pow((1-t),3) * p0 + 3 * pow((1-t),2) * t * p1 + 3 * (1-t) * pow(t,2) * p2 + pow(t,3) * p3;
        zapalPiksel(punkt.x(), punkt.y());
    }
}
 
void MyWindow::mousePressEvent(QMouseEvent *event)
{
    x0 = event->x();
    y0 = event->y();
    x0 -= poczX;
    y0 -= poczY;
 
    if(event->buttons() == Qt::RightButton){
        QPoint punkt;
 
        punkt.setX(x0);
        punkt.setY(y0);
 
        wektor.push_back(punkt);
        licz_klikniecia++;
 
 
        if(licz_klikniecia==4){
            rysujBeziera(wektor[0],wektor[1],wektor[2], wektor[3]);
        }
        if(licz_klikniecia > 4 && ((licz_klikniecia-1)%3)==0){
            rysujBeziera(wektor[licz_klikniecia-4],wektor[licz_klikniecia-3],wektor[licz_klikniecia-2], wektor[licz_klikniecia-1]);
        }
    }
    else if(event->buttons() == Qt::LeftButton){
        for(int i=0; i<wektor.size(); i++){
            if(abs(x0 - wektor[i].x()) < 15 && abs(y0 - wektor[i].y()) < 15){
                punktprzesun = i;
            }
        }
    }
 
    qDebug() << wektor;
    update();
}
 
void MyWindow::mouseMoveEvent(QMouseEvent *event)
{
    x0 = event->x();
    y0 = event->y();
    x0 -= poczX;
    y0 -= poczY;
    czysc();
    if(punktprzesun != -1){
        wektor[punktprzesun] = QPoint(x0, y0);
    }
    if(wektor.size()>=4){
        for(int i=3; i<wektor.size(); i+=3){
            rysujBeziera(wektor[i-3],wektor[i-2],wektor[i-1], wektor[i]);
        }
    }
    update();
}
 
void MyWindow::mouseReleaseEvent(QMouseEvent *event)
{
    punktprzesun = -1;
}
 
 
void MyWindow::rysujOdcinek(int x0, int y0, int x1, int y1){
    float a, b;
 
    a = (float)(y1 - y0)/(x1 -x0);
    b = (float)(y0 - a * x0);
    if(x0 == x1)
    {
        if(y0>y1){
            int temp;
            temp = y0;
            y0 = y1;
            y1 = temp;
 
        }
 
        for(int i = y0; i <= y1; i++){
            zapalPiksel(x0,i);
        }
    }
 
    else if(x0 < x1)
    {
        if(abs(a) < 1)
        {
            for(int x = x0; x <=x1; x++)
            {
                int y = a * x + b;
                zapalPiksel(x,y);
            }
        }
        else if(abs(a) >= 1 && y0 < y1 )
        {
            for(int y = y0; y <=y1; y++)
            {
                int x = (y - b)/a;
                zapalPiksel(x,y);
            }
        }
        else
        {
            for(int y = y1; y <=y0; y++)
            {
                int x = (y - b)/a;
                zapalPiksel(x,y);
            }
        }
    }
    else
    {
        if(abs(a) < 1)
        {
            for(int x = x1; x <=x0; x++)
            {
                int y = a * x + b;
                zapalPiksel(x,y);
            }
        }
        else if(abs(a) >= 1 && y1 < y0)
        {
            for(int y = y1; y <=y0; y++)
            {
                int x = (y - b)/a;
                zapalPiksel(x,y);
            }
        }
        else
        {
            for(int y = y0; y <=y1; y++)
            {
                int x = (y - b)/a;
                zapalPiksel(x,y);
            }
        }
    }
}


2

Ogarniasz coś z obsługi zdarzeń?

No więc tutaj masz metodę void MyWindow::mousePressEvent(QMouseEvent *event), do której przekazujesz zdarzenie kliknięcia myszą. Przypisujesz do x0 i y0 współrzędne tego zdarzenia, po czym odejmujesz współrzędne początku okna, w którym będziesz rysować krzywą - da nam to współrzędne względem owego okna.
Następnie tworzysz zmienną, która przechowa Ci te wspołrzędne i wpisujesz je do tablicy. Zwiększasz również licznik kliknięć o 4. Do narysowania krzywej Beziera potrzebujesz 4 punktów, więc kiedy licznik kliknięć osiągnie 4 bądź wielokrotność 4 (tutaj są to 2 osobne warunki), to wywołuje metodę rysujBeziera z czterema ostatnimi elementami tablicy jako parametrami (a każdy z tych parametrów to punkt o określonych współrzędnych).

0

dziękuję za pomoc wiele mi to rozjasniło :) , rozumiesz też moze ten fragment kodu?

 else if(event->buttons() == Qt::LeftButton){
        for(int i=0; i<wektor.size(); i++){
            if(abs(x0 - wektor[i].x()) < 15 && abs(y0 - wektor[i].y()) < 15){
                punktprzesun = i;
            }
0
Crazy_Rockman napisał(a):

Do narysowania krzywej Beziera potrzebujesz 4 punktów.

A nie wystarczą trzy?

A quadratic Bézier curve is the path traced by the function B(t), given points P0, P1, and P2.

https://en.wikipedia.org/wiki/B%C3%A9zier_curve#Quadratic_B%C3%A9zier_curves

0

Aby narysować pierwszą krzywą potrzebne są 4 punkty gdy krzywa jest juz narysowana "wyklikuje" 3 punkty i dorysowuje się kolejna krzywa, "Jak go usuniesz, to jak to wpłynie na działanie aplikacji?"
nie mogę przesuwać punktów

1
dcielak napisał(a):

rozumiesz też moze ten fragment kodu?

Sprawdzasz, który kwadracik został kliknięty i zapamiętujesz jego indeks.
Żeby przy ruchu myszką, program wiedział, który punkt przesuwasz.

0

dziękuję ideę rozumiem, ale i tak mam problem ze zrozumieniem tego warunku " if(abs(x0 - wektor[i].x()) < 15 && abs(y0 - wektor[i].y()) < 15)" tu chodzi o wielkosc klikniecia tego kwadracika?

"

1

Tak.
Wartość bezwzględna jest zastosowana po to, żeby nie pisać podwójnych warunków w stylu: (x0 - wektor[i].x() > -15) && (x0 - wektor[i].x() < 15).

Pozycja x kliknięcia nie może być bardziej odległa niż 15 w lewo lub prawo, względem sprawdzanego punktu wektor[i].
To samo dla y. I to sprawia, że mamy klikalny kwadracik wokół punktu. Inaczej trzeba by klikać z dokładnością co do piksela ;)

3

offtopic:
Jak widzę paintEvent i/lub mousePressEvent w klasie dziedziczącej po QMainWindow to mi się płakać chce.
QMainWindow pełni rolę kontrolera. Ma siebie wbudowane, obsługę menu, status bar, dokowania itp.
Te cuda z rysowaniem powinno się robić w klasie dziedziczącej po QWidget, której instancja jest ustawiona jako centralWidget w pochodnej QMainWindow.
Tak to zaprojektowali ludzie od Qt-ka.

0

dziękuję za odpowiedz, pomógłbyś mi jeszcze w zrozumieniu
void MyWindow::mouseMoveEvent(QMouseEvent *event) ?
I mam dodatkowe pytanie dlaczego punktprzesun jest ustawiony na -1 ? Wiesz moze?

void MyWindow::mouseMoveEvent(QMouseEvent *event)
{
    x0 = event->x();
    y0 = event->y();
    x0 -= poczX;
    y0 -= poczY;
    czysc();
    if(punktprzesun != -1){
        wektor[punktprzesun] = QPoint(x0, y0);
    }
    if(wektor.size()>=4){
        for(int i=3; i<wektor.size(); i+=3){
            rysujBeziera(wektor[i-3],wektor[i-2],wektor[i-1], wektor[i]);
        }
    }
    update();
}

void MyWindow::mouseReleaseEvent(QMouseEvent *event)
{
    punktprzesun = -1;
}

0

Jest -1, bo po puszczeniu punktu, nie chcemy, żeby myszka już cokolwiek przestawiała...

0

a dlaczego akurat "-1" ??

1

Jeśli Cię to zadowoli, to możesz dać nawet -54334 i w warunku >= 0 zamiast != -1.

Po prostu najmniejszy możliwy indeks tablicy to 0, więc liczba ujemna jest ok dla zmiennej przechowującej indeks, żeby ostrzec inną część programu, by nic nie robić z tą tablicą.

2

Pomijając ręczne licznie punktów beizera to może to wyglądać tak:

#ifndef MANBEZEIRWIDGET_H
#define MANBEZEIRWIDGET_H

#include <QWidget>

class ManBezeirWidget : public QWidget
{
    Q_OBJECT
public:
    explicit ManBezeirWidget(QWidget *parent = nullptr);

    void mousePressEvent(QMouseEvent *event);
    void mouseReleaseEvent(QMouseEvent *event);
    void mouseMoveEvent(QMouseEvent *event);

    void paintEvent(QPaintEvent *event);

signals:

public slots:

private:
    void paintBezeirCurve(QPainter *painter);
    void paintControlPoints(QPainter *painter);
    void paintHelperLines(QPainter *painter);
    bool activePointHit(QPoint p);
    int findPoint(QPoint p) const;
private:
    QVector<QPoint> mPoints;
    int mActivePoint = -1;
};

#endif // MANBEZEIRWIDGET_H

cpp

#include "manbezeirwidget.h"
#include <QPainter>
#include <QMouseEvent>

namespace {
const int PointVisualRadius = 3;
const int PointMouseRadius = 5;
}

ManBezeirWidget::ManBezeirWidget(QWidget *parent)
    : QWidget(parent)
    , mPoints{ { 10, 10 }, { 200, 10 }, { 200, 200 }, { 10, 200 } }
{
}

void ManBezeirWidget::mousePressEvent(QMouseEvent *event)
{
    if (event->button() != Qt::LeftButton) {
        QWidget::mousePressEvent(event);
        return;
    }
    auto p = event->pos();
    if (activePointHit(p)) {
        event->accept();
        return;
    }
    mActivePoint = findPoint(p);
}

void ManBezeirWidget::mouseReleaseEvent(QMouseEvent *event)
{
    if (mActivePoint < 0) return;
    mPoints[mActivePoint] = event->pos();
    update();
}

void ManBezeirWidget::mouseMoveEvent(QMouseEvent *event)
{
    if (mActivePoint < 0) return;
    if (!event->buttons().testFlag(Qt::LeftButton)) return;
    mPoints[mActivePoint] = event->pos();
    update();
}

void ManBezeirWidget::paintEvent(QPaintEvent *)
{
    QPainter painter { this };
    paintHelperLines(&painter);
    paintBezeirCurve(&painter);
    paintControlPoints(&painter);
}

void ManBezeirWidget::paintBezeirCurve(QPainter *painter)
{
    QPainterPath path;
    path.moveTo(mPoints.front());
    path.cubicTo(mPoints[1], mPoints[2], mPoints[3]);
    painter->setBrush({});
    painter->setPen(Qt::green);
    painter->drawPath(path);
}

void ManBezeirWidget::paintControlPoints(QPainter *painter)
{
    for (int i = 0; i < mPoints.size(); ++i) {
        if (i == mActivePoint) {
            painter->setPen(Qt::red);
            painter->setBrush(Qt::magenta);
        } else {
            painter->setPen(Qt::darkCyan);
            painter->setBrush(Qt::darkRed);
        }
        painter->drawEllipse(mPoints[i], PointVisualRadius, PointVisualRadius);
    }
}

void ManBezeirWidget::paintHelperLines(QPainter *painter)
{
    QPen pen{Qt::gray, 1, Qt::DashLine};
    painter->setPen(pen);
    QPainterPath path;
    path.moveTo(mPoints.front());
    for (int i = 1; i < mPoints.size(); ++i) {
        path.lineTo(mPoints[i]);
    }
    painter->drawPath(path);
}

bool ManBezeirWidget::activePointHit(QPoint p)
{
    if (mActivePoint >= 0) {
        auto diff = mPoints[mActivePoint] - p;
        if (QPoint::dotProduct(diff, diff) <= PointMouseRadius*PointMouseRadius) {
            return true;
        }
    }
    return false;
}

int ManBezeirWidget::findPoint(QPoint p) const
{
    for (int i = 0; i < mPoints.size(); ++i) {
        auto diff = mPoints[i] - p;
        if (QPoint::dotProduct(diff, diff) <= PointMouseRadius*PointMouseRadius) {
            return i;
        }
    }
    return -1;
}

MainWindow:

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    setCentralWidget(new ManBezeirWidget(this));
}

screenshot-20190404005734.png

0

Powracam do tematu gdyz dla mnie jeszcze nie do konca jest wszystko jasne mianowicie
Chciałbym zrozumiec jak działa przesuwanie a nastepnie rysowanie krzywej, probóję to swoimi słowami opisac ale tak srednio jestem pewny tego co piszę, proszę o pomoc..

void MyWindow::mouseMoveEvent(QMouseEvent *event)
{
    x0 = event->x();
    y0 = event->y();
    x0 -= poczX;
    y0 -= poczY;
    czysc();
    if(punktprzesun != -1){
        wektor[punktprzesun] = QPoint(x0, y0);
    }
    if(wektor.size()>=4){
        for(int i=3; i<wektor.size(); i+=3){
            rysujBeziera(wektor[i-3],wektor[i-2],wektor[i-1], wektor[i]);
        }
    }
    update();
}

wchodzę do funkcji gdzie wywoływana jest metoda przemieszczania myszy
gdy nacisnę na dany kwadracik i przesunę go (bez puszczania lewego przycisku myszy) do tablicy wektor zostaja przypisane współrzedne puszczenia myszy?
No i gdy tych kwadracikow jest wiecej niz 4

 for(int i=3; i<wektor.size(); i+=3){
            rysujBeziera(wektor[i-3],wektor[i-2],wektor[i-1], wektor[i]);

tego nie rozumiem.. Prosiłbym o wytłumaczenie funckcji

1

Napisałem ci, że ten kod ma błędy techniczne!
Dałem przykład jak to powinno wyglądać:

  • akcja myszki -> aktualizuj dane poproś o odświeżenie ekranu (update();)
  • odświeżenie ekranu (paintEvent) -. narysuj wszystko korzystając z bieżących danych

A ten kod co pokazałeś rysuje po bitmapie, a potem w paintEvent rysowana jest ta bitmapa.

Krzywe Beziera można łączyć i to właśnie robi ta pętla. Co trzeci punkt jest wspólny dla sąsiednich segmentów krzywej. Pierwszy segment używa punktów kontrolnych 0 1 2 3 następny segment 3 4 5 6, następny 6 7 8 9 itd.
Im więcej masz punktów, tym więcej segmentów qubic Beizer jest rysowanych.

1 użytkowników online, w tym zalogowanych: 0, gości: 1