Уроки, алгоритмы, программы, примеры

Вход на сайт

Материалы по разделам

Построения
на плоскости (2D)
Графика
в пространстве (3D)
Вычислительная
геометрия
Физическое
моделирование
Фрактальная
графика

Новые комментарии

Спасибо за реализацию, она действительно быстрая. Но не все линии отрисовывает в нужную сторону... Необходимо добавить проверку для случая X-линии if(y1 "<" y0) grad=-grad; и аналогично для Y-линии if(x1 "<" x0) grad=-grad; P.S. На...
Отличные уроки(учу GL по ним), только в renderScene нужно добавить очистку буфера цвета и буфера глубины. При изменении размеров треугольники размножаются)
как исправить это , сделал все по инструкции
Timer1 - выдает ошибку. Использовал IdleTimer1, работает! unit Unit1; {$mode objfpc}{$H+} interface uses Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls, ExtCtrls, OpenGLContext, GL, GLU; type { TForm1 } TForm1 =...
в коде присутствуют ошибки! // Считываем координаты procedure TForm1.getCoords(Sender: TObject); var j1:longint; begin n:= StrToInt(Edit2.Text); //число точек s1:=Edit1.Text; s2:=''; i := 1; j:=1; k:=0...

Счетчики и рейтинг

Яндекс.Метрика Рейтинг@Mail.ru
Среда программирования: 
Qt

На данном ресурсе совсем недавно появилось несколько довольно интересных статей по введению в программирование графики на Qt (см. "Отрисовка графических примитивов на Qt/C++", "Создание простой анимации с использованием QML"). К сожалению, ни в одной из них не рассматривается прикладной характер использования программирования графики. Под "прикладным характером" я подразумеваю возможность реального использования и быстроты разработки. В Qt реализован отличный механизм UI, который позволяет создавать окна буквально в два клика, используя технологию drag&drop. По сути, сама форма является лишь xml файлом, в котором обозначены расположения всех объектов. Использование данного механизма позволяет разработчику тратить меньше времени на реализацию интерфейса, и сосредоточиться на разработке самой логики программы.

В данном уроке я объясню вам как использовать данную технологию для создания программы отрисовки графиков, что будет являться отличным примером прикладного программирования графики на Qt.

Начать следует с создания проекта. В меню создания вам предложат создать форму, и если в других уроках рекомендовалось её не создавать, то в этом её следует создать.
При окончании создания проекта у вас будет четыре файла:
1) main.cpp -- главный файл, который должен присутствовать во всех проектах (за редким исключением)
2) MainWindow.h -- заголовочный файл окна, в котором будут описаны необходимые нам методы.
3) MainWindow.cpp -- файл, в котором будут реализованы методы, объявленные в mainwindow.h
4) MainWindow.ui -- файл формы.

Хочется заметить, что последние три файла могут у вас называться по-другому, если вы решили назвать главный класс "не по дефолту".

Кликнув два раза по файлу mainwindow.ui в Qt Creator, вы перейдете в меню создания интерфейса. С помощью layout'ов (как и в других средах программирования) зададим приблизительную разметку интерфейса и поместим на форму нужные нам кнопки и поля.
Объект label будет использоваться для отображения картинки.

Вот что у меня получилось:

Если есть желание -- можете поменять названия объектов кнопок. Чтобы определить методы нажатия на кнопки, достаточно кликнуть по ней (кнопке) правой кнопкой мыши, перейти к пункту меню "перейти к слоту" и выбрать on_click().

Теперь перейдем к коду. Я сразу приведу код файлов mainwindow.h и mainwindow.cpp с комментариями.

// mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
 
#include <QMainWindow>
#include <QtGui>
 
namespace Ui {
class MainWindow;
}
 
class MainWindow : public QMainWindow
{
    Q_OBJECT
 
public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();
    void drawGraph(bool notEmpty = 0); // функция, отвечающая за отрисовку графика
    void recountPixels(); // в зависимости от точности (об этом позже) считаем количество пикселей на один условный сантиметр
    void getData(); // получаем значения границ графика и точности
    double f(double x); // первая функция
    double f1(double x); // вторая функция
    double f2(double x); // третья функция
    double f3(double x); // четвертая функция
 
private slots:
    void on_exit_clicked(); // что будет если кликнуть на кнопку выхода
 
    void on_clear_clicked(); // ... на кнопку очистки
 
    void on_draw_clicked(); // ... на кнопку отрисовки
 
    void on_save_clicked(); // ... на кнопку сохранения
 
private:
    Ui::MainWindow *ui; // форма
    double leftX,rightX; // границы по х
    double leftY,rightY; // границы по у
    int pictWidth,pictHeight; // ширина и высота картинки
    double step; // шаг (точность)
    double onePixelX,onePixelY; // количество пикселей на шаг
    double Ox,Oy; // координаты центра
};
 
#endif // MAINWINDOW_H

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <cmath>
#include <QDebug>
using namespace std;
 
MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this); // означает, что будем использовать форму
    pictHeight = 370; // задаем высоту картинки
    pictWidth = 540; // и её ширину
    step = 0.1; // задаем начальный шаг
    leftX = -100; rightX = 100; // и начальные значения границ
    leftY = -100; rightY = 100;
    drawGraph(); // сразу же отрисовываем пустой график
}
 
MainWindow::~MainWindow()
{
    delete ui; // стандартный деструктор
}
 
// следующие 4 метода -- это лишь задания функций, которые мы хотим отрисовывать
double MainWindow::f(double x)
{
    return log(x)*(-1);
}
 
double MainWindow::f1(double x)
{
    return sin(x)*(-1);
}
 
double MainWindow::f2(double x)
{
    return cos(x)*(-1);
}
 
double MainWindow::f3(double x)
{
    return sin(1.0/x)*(-1);
}
 
// метод вычисляет середину экрана и пересчитывает количество пикселей на шаг
void MainWindow::recountPixels()
{
    onePixelX = 540.0/(rightX-leftX);
    onePixelY = 370.0/(rightY-leftY);
    Ox = fabs(leftX); Oy = rightY;
}
 
void MainWindow::getData()
{
    // ui->name->method() означает, что мы обращаемся к объекту name, который помещен на форме ui
    leftX = ui->inputLeftX->text().toDouble(); // узнаем границы
    rightX = ui->inputRightX->text().toDouble();
    leftY = ui->inputLeftY->text().toDouble();
    rightY = ui->inputRightY->text().toDouble();
    step = 1.0/ui->inputAccuracy->text().toDouble(); // и шаг
}
 
void MainWindow::drawGraph(bool notEmpty)
{
    QPixmap graph(540,370); // создаем саму картинку
    QPainter paint; // и пэинтер
    paint.begin(&graph); // запускаем отрисовку
    paint.eraseRect(0,0,540,370); // очищаем рисунок
    paint.drawLine(Ox*onePixelX,0,Ox*onePixelX,pictHeight); // и рисуем координатные оси
    paint.drawLine(0,Oy*onePixelY,pictWidth,Oy*onePixelY);
 
    paint.setPen(QPen(Qt::black,3)); // устанавливаем цвет и толщину "пера"
    for(double i = leftX;i<=rightX;i+=10.0) // рисуем черточки на координатой оси
        paint.drawPoint((i+Ox)*onePixelX,Oy*onePixelY);
    for(double i = leftY;i<=rightY;i+=10.0)
        paint.drawPoint(Ox*onePixelX,(i+Oy)*onePixelY);
 
    // если мы не рисуем график, то отображаем координатную ось и выключаемся
    if(!notEmpty) {
        paint.end();
        ui->outputGraph->setPixmap(graph);
        return;
    }
 
    paint.setPen(QPen(Qt::green,1,Qt::SolidLine)); // снова задаем цвет и тип линии
    paint.setRenderHint(QPainter::Antialiasing, true); // задаем параметры рендеринга
    QPainterPath path,p[3]; // QPainterPath означаем, что мы вначале занесем все необходимые точки, а затем соединим их
    bool first[4] = {1,1,1,1}; // узнаем первая ли точка, или надо сдвигаться
 
    // последовательно проходимся по всем точкам графика, проверяем, существует ли функция в данной точке, и если существует -- заносим точку в массив отрисовки
 
    for(double i = (double)leftX+step;i<=(double)rightX;i+=step) {
        if(!isnan(f(i))) {
            if(first[0]) {
                path.moveTo((i+Ox)*onePixelX,(f(i)+Oy)*onePixelY);
                first[0] = false;
            }
            else
                path.lineTo((i+Ox)*onePixelX,(f(i)+Oy)*onePixelY);
        }
        if(!isnan(f1(i))) {
            if(first[1]) {
                p[0].moveTo((i+Ox)*onePixelX,(f1(i)+Oy)*onePixelY);
                first[1] = false;
            }
            else
                p[0].lineTo((i+Ox)*onePixelX,(f1(i)+Oy)*onePixelY);
        }
        if(!isnan(f2(i))) {
            if(first[2]) {
                p[1].moveTo((i+Ox)*onePixelX,(f2(i)+Oy)*onePixelY);
                first[2] = false;
            }
            else
                p[1].lineTo((i+Ox)*onePixelX,(f2(i)+Oy)*onePixelY);
        }
        if(!isnan(f3(i))) {
            if(first[3]) {
                p[2].moveTo((i+Ox)*onePixelX,(f3(i)+Oy)*onePixelY);
                first[3] = false;
            }
            else
                p[2].lineTo((i+Ox)*onePixelX,(f3(i)+Oy)*onePixelY);
        }
    }
 
    // проверяем, если в CheckBox отмечено, что график надо отрисовывать -- задаем нужный цвет и отрисовываем с помощью функции drawPath()
    if(ui->main->isChecked()) {
        paint.setPen(QPen(Qt::blue,1,Qt::SolidLine));
        paint.drawPath(path);
    }
    if(ui->sin->isChecked()) {
        paint.setPen(QPen(Qt::green,1,Qt::SolidLine));
        paint.drawPath(p[0]);
    }
    if(ui->cos->isChecked()) {
        paint.setPen(QPen(Qt::red,1,Qt::SolidLine));
        paint.drawPath(p[1]);
    }
    if(ui->tg->isChecked()) {
        paint.setPen(QPen(Qt::darkMagenta,1,Qt::SolidLine));
        paint.drawPath(p[2]);
    }
    paint.end(); // заканчиваем рисование
    ui->outputGraph->setPixmap(graph); // и помещаем рисунок на форму
    return;
}
 
void MainWindow::on_exit_clicked()
{
    this->close(); // при нажатии на кнопку выхода, закрываем окно
}
 
void MainWindow::on_clear_clicked()
{
    // при нажатии на кнопку очистки, пересчитываем пиксели и рисуем координатную ось
    recountPixels();
    drawGraph();
}
 
void MainWindow::on_draw_clicked()
{
    // при нажатии на кнопку отрисовки, получаем границы и точность, пересчитываем значение шага и отрисовываем график
    getData();
    recountPixels();
    drawGraph(1);
}
 
void MainWindow::on_save_clicked()
{
    // данную функцию я не буду подробно рассматривать, так как она не имеет прямого отношения к уроку, но в двух словах -- мы задаем имя как текущую дату + время и сохраняем в папку с программой
    // в зависимости от результата сохранения выводим соответствующее окно
    QTime time = QTime::currentTime();
    QDate date = QDate::currentDate();
    QString name;
   if(date.day()<10)
        name += "0";
    name += QString::number(date.day())+".";
    if(date.month()<10)
        name += "0";
    name += QString::number(date.month())+".";
    name += QString::number(date.year())+"_";
    if(time.hour()<10)
        name += "0";
    name += QString::number(time.hour())+"-";
    if(time.minute()<10)
        name += "0";
    name += QString::number(time.minute())+"-";
    if(time.second()<10)
        name += "0";
    name += QString::number(time.second());
    QFile file(name+".png");
    qDebug() << name;
    file.open(QIODevice::WriteOnly);
    QMessageBox msgBox;
    msgBox.setStandardButtons(QMessageBox::Ok);
    if(ui->outputGraph->pixmap()->save(&file,"PNG")) {
        msgBox.setText("Saved to program folder with name: "+name+".png");
        msgBox.setWindowTitle("Saved!");
    }
    else {
        msgBox.setText("Error saving.");
        msgBox.setWindowTitle("Error!");
    }
    msgBox.exec();
}

В итоге у нас получается что-то вроде такого:

Как вы видите, использование ui очень упрощает работу программисту, так как ему не надо так много времени тратить на программирование интерфейса.

Прикрепленный файлРазмер
Graphs.zip4.08 кб

Комментарии

Антон аватар
Опубликовано Антон (не проверено) в 30. Ноябрь 2015 - 17:13.

Вдруг кто то будет пользоваться:)
Использовал статейку, но заметил одну ошибку: графики рисуются отражёнными относительно оси Ox (т.е. перевёрнутые). Для правильного отображения нужно в строках присвоения значения точкам графика вместо (f(i)+Oy) писать (Oy-f(i)) и так же где на оси ставим отметочки не (i+Oy), а (Oy-i)