Ana SayfaAlgoritma ve ProgramlamaQt C++ Kütüphanesi ile GAP - İplik Kullanımı (Mandelbrot Kümesi Örneği)

Qt C++ Kütüphanesi ile GAP – İplik Kullanımı (Mandelbrot Kümesi Örneği)

Bir süredir ara verdiğimiz Qt C++ Kütüphanesi ile Grafik Arayüz Programlama (GAP) derslerimize yeni bir uygulama ile devam ediyoruz. Geçen dersimizde arayüz uygulamalarında olmazsa olmazlardan bir tanesinin iplikleri kullanmak olduğunu söylemiştik. Bu dersimizde yine iplikleri kullanarak bir uygulama gerçekleştireceğiz.

Uygulamamızda ünlü Mandelbrot Kümesini görselleştireceğiz.

Mandelbrot Kümesi

Mandelbrot kümesi karmaşık sayılardan oluşan bir kümedir. Bu karmaşık sayı kümesi

Mandelbrot Kümesi fonksiyon

fonksiyonu ile belirlenir. Bu fonksiyon iteratif olarak z=0’dan başlanarak her c değeri için hesaplanır ve yakınsak c değerleri Mandelbrot kümesini oluşturur. Yukarıdaki görselde siyah olarak gösterilen alan bu kümenin elemanlarını göstermektedir.

Bu uygulamamız Qt kütüphanesinin dokümanlarındaki örneklerden alınmıştır. Biz burada bu örneği biraz değiştireceğiz ve uygulamayı daha detaylı olarak inceleyeceğiz. Bu uygulamamız iki açıdan önemli: birincisi uygulamamızda riyazî bir iifadenin kodlanmasını göreceğiz, ikincisi ise grafik arayüzlerde iplik kullanımının yeni bir örneğini görmüş olacağız.

İlk olarak uygulamamızın main fonksiyonunu inceleyelim:

#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}

Uygulamamızın main fonksiyonu oldukça basit. Daha önceden gördüğümüz QApplication sınıfından bir nesne tanımlanmıştır. MainWindow objesi arayüzümüzü tanımlayan asıl sınıftır. Aşağıda verilen kodda MainWindow sınıfı gösterilmiştir.

#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include "mandelbrotwidget.h"

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }

QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:
    Ui::MainWindow *ui;
    MandelbrotWidget *mWidget;
};
#endif // MAINWINDOW_H

MainWindow sınıfı QMainWindow objesinden kalıtım almakta ve arayüz objesine gösterici (*ui) ile MandelbrotWidget sınıfına bir gösterici (*mWidget) bulundurmaktadır. Arayüzün tanımlandığı xml dosyası da oldukaç sadedir. Çünkü arayüze bir tane merkezi bileşen yerleştireceğiz ve geri kalan işlemleri bu merkezi bileşenin fonksiyonlarını kullanarak halledeceğiz. Aşağıda arayüzü oluşturan xml dosyamız verilmiştir.

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
    <class>MainWindow</class>
    <widget class="QMainWindow" name="MainWindow">
        <property name="geometry">
            <rect>
                <x>0</x>
                <y>0</y>
                <width>800</width>
                <height>600</height>
            </rect>
        </property>
        <property name="windowTitle">
            <string>MainWindow</string>
        </property>
        <widget class="QWidget" name="centralwidget"/>
        <widget class="QMenuBar" name="menubar"/>
        <widget class="QStatusBar" name="statusbar"/>
    </widget>
<resources/>
<connections/>
</ui>

MainWindow sınıfının fonksiyonları aşağıdaki şekilde gerçeklenmiştir:

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "mandelbrotwidget.h"

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

MainWindow::~MainWindow()
{
   delete ui;
}

Yapıcı fonksiyonda arayüzü oluşturan birleşenlerin tanımlandığı arayüzün setupUi fonksiyonu ve MandelbrotWidget sınıfından bir nesne tanımlanmış ve ana pencerenin merkezi birleşeni olarak atanmıştır.

Mandelbrot kümesi MandelbrotWidget sınıfı fonksiyonları kullanılarak bu birleşen üzerinde gerçekleştirilecektir. MandelbrotWidget sınıfı QWidget sınıfından kalıtım almaktadır. QWidget sınıfından miras alınan pek çok fonksiyon yeniden tanımlanmıştır. Aşağıda verilen kodda bu fonksiyonları inceleyebilirsiniz. Bu sınıfta tanımlanmış olan QPixmap sınıfından pixmap nesnesi Mandelbrot kümesinin görüntüsünü tutacaktır. Bu değişken QWidget bileşenin üzerine çizilecektir.

#ifndef MANDELBROTWIDGET_H
#define MANDELBROTWIDGET_H
#include <QWidget>
#include "RenderThread.h"

class MandelbrotWidget : public QWidget
{
    Q_OBJECT

public:
    MandelbrotWidget(QWidget *parent = nullptr);
protected:
    void paintEvent(QPaintEvent *event) override;
    void resizeEvent(QResizeEvent *event) override;
    void keyPressEvent(QKeyEvent *event) override;

#if QT_CONFIG(wheelevent)
    void wheelEvent(QWheelEvent *event) override;
#endif

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

private slots:
    void updatePixmap(const QImage &image, double scaleFactor);
    void zoom(double zoomFactor);

private:
    void scroll(int detlaX, int deltaY);
    RenderThread thread;
    QPixmap pixmap;
    QPoint pixmapOffset;
    QPoint lastDragPos;

    double centerX;
    double centerY;
    double pixmapScale;
    double curScale;
};

#endif // MANDELBROTWIDGET_H

Şimdi bu sınıftaki fonksiyonların nasıl tanımlandığını tek tek inceleyeleyeceğiz. Ondan önce uygulamada kullanılan sabit başlangıç değerlerinden de bahsetmek istiyorum. Uygulamamızda kullandığımız görüntü sanal düzlemi temsil etmektedir. Sanal düzlemin sanal bileşeni yükseklikle, gerçek bileşeni ise genişlikle temsil edilecektir. Ekranımızın çözünürlüğü sonsuz olmadığı için teorik olarak sonsuz noktadan oluşan sanal düzlemi belli bir ölçeğe haritalamamız gerekiyor. Bu amaçla DefaultScale değişkeni kullanılacaktır. Örneğin: koordinatları (1, 1) olan pikselimiz aslında sanal düzlemde (0.00403897, 0.00403897) noktasına denk düşecektir.

#include "mandelbrotwidget.h"
#include <QPainter>
#include <QKeyEvent>
#include <math.h>

const double DefaultCenterX = -0.637011f;
const double DefaultCenterY = -0.0395159f;
const double DefaultScale = 0.00403897f;
const double ZoomInFactor = 0.8f;
const double ZoomOutFactor = 1 / ZoomInFactor;
const int ScrollStep = 20;

MandelbrotWidget::MandelbrotWidget(QWidget *parent) : QWidget(parent)
{
    centerX = DefaultCenterX;
    centerY = DefaultCenterY;
    pixmapScale = DefaultScale;
    curScale = DefaultScale;

    connect(&thread, &RenderThread::renderedImage,
                  this, &MandelbrotWidget::updatePixmap);    

    setWindowTitle(tr("Mandelbrot"));
#ifndef QT_NO_CURSOR
    setCursor(Qt::CrossCursor);
#endif    
    resize(550, 400);
}

MandelbrotWidget sınıfının yapıcı fonksiyonu kümeyi çizmek için gerekli olan orijin noktasının konumunu ve çizileceği ölçeği başlangıç değerlerine atar. RenderThread iplik sınıfının renderedImage sinyali ile MandelbrotWidget sınıfının updatePixmap slotunu bağlar ve en son QWidget sınıfından kalıtımla alınan resize fonksiyonu ile bu bileşenin ilk boyutunu belirler.

QWidget‘e ait olan paintEvent fonksiyonu yeniden tanımlanmıştır. Bu fonksiyon pixmap değişkenini bileşenin üzerine çizmektedir.

void MandelbrotWidget::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    painter.fillRect(rect(), Qt::black);
    if(pixmap.isNull()){
        painter.setPen(Qt::white);
        painter.drawText(rect(), Qt::AlignCenter, tr("Rendering initial image, please wait..."));
        return;
    }
    if (curScale == pixmapScale) {
        painter.drawPixmap(pixmapOffset, pixmap);
    }
    else {
        double scaleFactor = pixmapScale / curScale;
        int newWidth = int(pixmap.width() * scaleFactor);
        int newHeight = int(pixmap.height() * scaleFactor);
        int newX = pixmapOffset.x() + (pixmap.width() - newWidth) / 2;
        int newY = pixmapOffset.y() + (pixmap.height() - newHeight) / 2;

        painter.save();
        painter.translate(newX, newY);
        painter.scale(scaleFactor, scaleFactor);

        QRectF exposed = painter.transform().inverted().mapRect(rect()).adjusted(-1, -1, 1, 1);
        painter.drawPixmap(exposed, pixmap, exposed);
        painter.restore();
    }

    QString text = tr("Use mouse wheel or the '+' and '-' keys to zoom. "
                            "Press and hold left mouse button to scroll.");
    QFontMetrics metrics = painter.fontMetrics();
    int textWidth = metrics.horizontalAdvance(text);

    painter.setPen(Qt::NoPen);
    painter.setBrush(QColor(0, 0, 0, 127));
    painter.drawRect((width() - textWidth) / 2 - 5, 0, textWidth + 10, metrics.lineSpacing() + 5);
    painter.setPen(Qt::white);
    painter.drawText((width() - textWidth) / 2, metrics.leading() + metrics.ascent(), text);
}

İlk olarak bileşenin tamamı painter.fillRect(rect(), Qt::black) ile siyaha boyanır daha sonra ölçekte bir değişiklik olup olmadığı kontrol edilerek iplik tarafından belirlenen Mandelbrot kümesi pixmap ile birleşen üzerine yerleştirilir. Ölçekte değişiklik olduğu durumda yeni merkezler hesaplanır ve ona göre çizilen pixmap yerleştirilir.

Uygulamanın boyutu değiştiğinde tekrar resizeEvent  fonksiyonu çağrılarak yeni Mandelbrot kümesi tanımlanmaktadır. Oluşturulan yeni görüntü renderedImage sinyali ile tekrar MandelbrotWidget bileşenine gönderilmektedir.

void MandelbrotWidget::resizeEvent(QResizeEvent * /* event */)
{
    thread.render(centerX, centerY, curScale, size());
}

Aşağıda uygulamanın tuşlarla kontrol edilmesi için gerekli keyPressEvent fonksiyonu gerçeklenmiştir. Bu fonksiyonda switch-case yapısı kullanarak ilgili tuşa basıldığında zoom, scroll gibi fonksiyonlar çağrılmaktadır.

void MandelbrotWidget::keyPressEvent(QKeyEvent *event)
{
     switch (event->key()) {
         case Qt::Key_Plus:
         zoom(ZoomInFactor);
         break;
     case Qt::Key_Minus:
         zoom(ZoomOutFactor);
         break;
     case Qt::Key_Left:
         scroll(-ScrollStep, 0);
         break;
     case Qt::Key_Right:
         scroll(+ScrollStep, 0);
         break;
     case Qt::Key_Down:
         scroll(0, -ScrollStep);
         break;
     case Qt::Key_Up:
         scroll(0, +ScrollStep);
         break;
     default:
         QWidget::keyPressEvent(event);
     }
}

wheelEvent fonksiyonu aşağıdaki şekilde gerçeklenmiştir. Bu şekilde fare tekerliğinin dönme açısı alınarak zoom fonksiyonu çağrılmıltır.

void MandelbrotWidget::wheelEvent(QWheelEvent *event)
{      
      int numDegrees = event->angleDelta().y() / 8;
      double numSteps = numDegrees / 15.0f;
      zoom(pow(ZoomInFactor, numSteps));
}

zoom fonksiyonu aşağıdaki şekilde tanımlanmıştır. Bu fonksiyon keyPressEvent içersinde kullanılmaktadır.

void MandelbrotWidget::zoom(double zoomFactor)
{
     curScale *= zoomFactor;
     update();
     thread.render(centerX, centerY, curScale, size());
 }

Aşağıdaki updatePixmap fonksiyonu ise iplik tarafından yayılan sinyali yakalayan slottur. Bu fonksiyon pixmap değişkenini MandelbrotWidget bileşkenini boyamada kullanılması için ayarlar.

void MandelbrotWidget::updatePixmap(const QImage &image, double scaleFactor)
{
     if (!lastDragPos.isNull())
         return;
     pixmap = QPixmap::fromImage(image);
     pixmapOffset = QPoint();
     lastDragPos = QPoint();
     pixmapScale = scaleFactor;
     update();
}

Aşağıdaki fonksiyonlarda ise mousePressEvent, mouseMoveEvent, mouseReleaseEvent ve scroll fonksiyonları gerçeklenmiştir. mousePressEvent fonksiyonunda farenin sol butonuna tıklandığında farenin konumu alınır ve mouseReleaseEvent ise farenin butonunun bırakıldığı konumu alıp görüntünün ne kadar kaydırılacağını hesaplar. mouseMoveEvent fonksiyonu ise görüntü kaydırıldıkça kümeyi günceller.

void MandelbrotWidget::mousePressEvent(QMouseEvent *event)
{
    if (event->button() == Qt::LeftButton)
        lastDragPos = event->pos();
}

void MandelbrotWidget::mouseMoveEvent(QMouseEvent *event)
{
      if (event->buttons() & Qt::LeftButton) {
          pixmapOffset += event->pos() - lastDragPos;
          lastDragPos = event->pos();
          update();
      }
}

void MandelbrotWidget::mouseReleaseEvent(QMouseEvent *event)
 {
     if (event->button() == Qt::LeftButton) {
         pixmapOffset += event->pos() - lastDragPos;
         lastDragPos = QPoint();
         int deltaX = (width() - pixmap.width()) / 2 - pixmapOffset.x();
         int deltaY = (height() - pixmap.height()) / 2 - pixmapOffset.y();
         scroll(deltaX, deltaY);
     }
 }

void MandelbrotWidget::scroll(int deltaX, int deltaY)
{
    centerX += deltaX * curScale;
    centerY += deltaY * curScale;
    update();
    thread.render(centerX, centerY, curScale, size());
}

Son olarak inceleyeceğimiz sınıf Mandelbrot kümesini belirleyen ipliklerdir. Sınıf aşağıda gösterilmiştir. Bildiğiniz gibi iplikleri, ana iplikte kullanıcı işlem yaparken ekranın donmasını önlemek için kullanıyoruz. Bu uygulamada da örneğin kullanıcı Mandelbrot kümesine daha yakından bakmak istediğinde Mandelbrot kümesine yakınlaşmaya çalışacaktır. Bu nedenle Mandelbrot kümesinin yakınlaşmış görüntüsünü yeniden hesaplayıp oluştururken kullanıcı arayüzün donmaması için iplikleri kullanacağız. Kullanıcı yakınlaşırken ya da küme üzerinde gezinirken bu iplik çağrılacak ve yeni görüntü oluşturulup MandelbrotWidget bileşenine gönderilecektir.

#ifndef RENDERTHREAD_H
#define RENDERTHREAD_H
#include <QThread>
#include <QMutex>
#include <QWaitCondition>
#include <QSize>

QT_BEGIN_NAMESPACE

class QImage;

QT_END_NAMESPACE

class RenderThread : public QThread
{
    Q_OBJECT

public:
    RenderThread(QObject *parent=nullptr);
    ~RenderThread();
    void render(double centerX, double centerY, 
                        double scaleFactor, QSize resultSize);
signals:
    void renderedImage(const QImage &image, double scaleFactor);

protected:
    void run() override;

private:
    uint rgbFromWaveLength(double wave);
    QMutex mutex;
    QWaitCondition condition;
    double centerX;
    double centerY;
    double scaleFactor;
    QSize resultSize;
    bool restart;
    bool abort;
    enum { ColormapSize = 512 };
    uint colormap[ColormapSize];
};
#endif // RENDERTHREAD_H

Bu sınıfımız bir iplik olduğundan dolayı QThread sınıfından miras almıştır. renderedImage isminde bir sinyal içermektedir. Bu sinyali MandelbrotWidget sınıfının yapıcısını incelerken de görmüştük. renderedImage sinyali run fonksiyonu çalıştıktan sonra oluşturulan küme imajını MandelbrotWidget bileşenine göndermek için kullanılmaktadır.

#include "RenderThread.h"
#include <cmath>
#include <QImage>

RenderThread::RenderThread(QObject *parent) : QThread(parent)
{
    restart = false;
    abort = false;
    for(int i = 0; i < ColormapSize; ++i) {
        colormap[i] = rgbFromWaveLength(380. + (i * 400 / ColormapSize));
    }
}

RenderThread yapıcı fonksiyonu colormap dizisine Mandelbrot kümesini göstermek için kullanacağı renkleri atıyor. rgbFromWaveLength fonksiyonu dalga uzunluklarının RGB karşılığını elde etmek için kullanılan literatürde bulunan hesaplamayı kullanmaktadır.

uint RenderThread::rgbFromWaveLength(double wave)
{
    double r = 0.0;
    double g = 0.0;
    double b = 0.0;

    if (wave >= 380.0 && wave <= 440.0) {
        r = -1.0 * (wave - 440.0) / (440.0 - 380.0);
        b = 1.0;
    } else if (wave >= 440.0 && wave <= 490.0) {
        g = (wave - 440.0) / (490.0 - 440.0);
        b = 1.0;
    } else if (wave >= 490.0 && wave <= 510.0) {
        g = 1.0;
        b = -1.0 * (wave - 510.0) / (510.0 - 490.0);
    } else if (wave >= 510.0 && wave <= 580.0) {
        r = (wave - 510.0) / (580.0 - 510.0);
        g = 1.0;
    } else if (wave >= 580.0 && wave <= 645.0) {
        r = 1.0;
        g = -1.0 * (wave - 645.0) / (645.0 - 580.0);
    } else if (wave >= 645.0 && wave <= 780.0) {
        r = 1.0;
    }

    double s = 1.0;
    if (wave > 700.0)
        s = 0.3 + 0.7 * (780.0 - wave) / (780.0 - 700.0);
    else if (wave <  420.0)
        s = 0.3 + 0.7 * (wave - 380.0) / (420.0 - 380.0);
    r = std::pow(r * s, 0.8);
    g = std::pow(g * s, 0.8);
    b = std::pow(b * s, 0.8);
    return qRgb(int(r * 255), int(g * 255), int(b * 255));
}

render fonksiyonu uygulama boyutları değiştirildiğinde, kümeye yaklaşıldığında ya da kümenin farklı bölgeleri görülmek üzere kaydırma yapıldığında MandelbrotWidget bileşeni tarafından yeniden çağrılır.

void RenderThread::render(double centerX, double centerY, 
                               double scaleFactor, QSize resultSize)
{
    QMutexLocker locker(&mutex);
    this->centerX = centerX;
    this->centerY = centerY;
    this->scaleFactor = scaleFactor;
    this->resultSize = resultSize;

    if(!isRunning()) {
        start(LowPriority);
    } else {
        restart = true;
        condition.wakeOne();
    }
}

Bu fonksiyon QMutexLocker sınıfını kullanarak muteks’i kilitlemekte ve gerekli değişken değerlerinin atamasını yaparak ipliği başlatmaktadır.

Aşağıda ipliğin run fonksiyonu gösterilmiştir. Bu fonksiyon sanal düzlemdeki her noktanın yakınsayıp yakınsamadığını belirleyen ve iteretasyon sayısına bağlı olarak o noktadaki renk atanıyor. Aşağıdaki kod biraz uzun ve karmaşık gözükse de aslında yapılan işlemler çok basit. Aşağıda bu fonksiyonu parçalar halinde açıklayacağız.

void RenderThread::run()
{
    forever {
        mutex.lock();
        QSize resultSize = this->resultSize;
        double scaleFactor = this->scaleFactor;
        double centerX = this->centerX;
        double centerY = this->centerY;
        mutex.unlock();

        int halfWidth = resultSize.width() / 2;
        int halfHeight = resultSize.height() / 2;

        QImage image(resultSize, QImage::Format_RGB32)
        const int NumPasses = 8;
        int pass = 0;
        while(pass < NumPasses) {
            const int MaxIterations = (1 << (2 * pass + 6 )) + 32;
            const int Limit = 4;
            bool allBlack = 4;
            for (int y = -halfHeight; y < halfHeight; ++y) {
                if (restart)
                    break;
                if (abort)
                    return;

                uint *scanLine = reinterpret_cast<uint *>(image.scanLine(y + halfHeight));
                double ay = centerY + (y * scaleFactor);

                for (int x = -halfWidth; x < halfWidth; ++x){
                    double ax = centerX + (x * scaleFactor);
                    double a1 = ax;
                    double b1 = ay;

                    int numIterations = 0;

                    do {
                        ++numIterations;
                        double a2 = (a1 * a1) - (b1 * b1) + ax;
                        double b2 = (2 * a1 * b1) + ay;
                        if ((a2 * a2) + (b2 * b2) > Limit)
                            break;
                        ++numIterations;

                        a1 = (a2 * a2) - (b2 * b2) + ax;
                        b1 = (2 * a2 * b2) + ay;
                        if ((a1 * a1) + (b1 * b1) > Limit)
                            break;
                    } while(numIterations < MaxIterations);

                    if (numIterations < MaxIterations) 
                        *scanLine++ = colormap[numIterations % ColormapSize];
                        allBlack = false;
                    } else {
                        *scanLine++ = qRgb(0, 0, 0);
                    }
                }
            }

            if (allBlack && pass == 0) {
                pass = 4;
            } else {
                if (!restart)
                    emit renderedImage(image, scaleFactor);
                ++pass;
            }
        }

        mutex.lock();

        if (!restart)
            condition.wait(&mutex);
        restart = false;
        mutex.unlock();
    }
}

İlk olarak muteks kullanılarak hesaplamada kullanılacak ve diğer proseslerin değiştirmesini istemediğimiz değişkenler atanıyor.

        mutex.lock();
        QSize resultSize = this->resultSize;
        double scaleFactor = this->scaleFactor;
        double centerX = this->centerX;
        double centerY = this->centerY;
        mutex.unlock();

Daha sonra hesaplamada kullanılacak olan değişkenler tanımlanıyor: Görüntünün orta noktası, üzerinde renkler gösterilecek imaj ve kullanılacak maksimum döngü sayısı ve döngü sayısının tutulacağı pass değişkeni.

        int halfWidth = resultSize.width() / 2;
        int halfHeight = resultSize.height() / 2;
        QImage image(resultSize, QImage::Format_RGB32);
        const int NumPasses = 8;
        int pass = 0;

İteratif bir fonksiyonu hesaplarken önceden kaç kere iterasyon yapmanız gerektiğini bilmediğiniz için iterasyonu sonlandırmak için bir kısıt koymanız gerekmektedir. Bu uygulamamızda kısıt olarak iterasyon sayısı kullanılmaktadır. Fakat gereksiz iterasyondan kaçınmak için iterasyon sayısı başlangıçta kısıtlanmış ve bazı şartlar altında artırılacak şekilde ayarlanmıştır. Bu şartları inceleyeceğiz.

Aşağıdaki kodda maksimum iterasyon sayısı pass değişkenine göre belirlenmiştir. Bilindiği gibi Mandelbrot Kümesi hesaplanırken kullanılan fonksiyon değeri eğer 2’nin üstüne çıkarsa fonksiyon mutlaka ıraksak olacaktır. Bu nedenle Limit değişkeninin değeri 2’nin karesi 4 olarak belirlenmiştir.

      while(pass < NumPasses) {
            const int MaxIterations = (1 << (2 * pass + 6 )) + 32;
            const int Limit = 4;
            bool allBlack = true;

Mandelbrot Kümesi için her pikselin değeri tek tek belirleneceği için görüntü soldan sağa ve yukarıdan aşağıya gezilir. Bu sırada restart değişkeni ile küme görüntüsü üzerinde yeni bir çağrı gelip gelmediği kontrol edilir. abort değişkeni ise uygulamanın sonlanıp sonlanmadığını kontrol eder. scaleFactor değişkeni piksel konumları ile çarpılarak görüntünün ölçeği belirlenir. Tabi ki burada ölçek teorik olarak sonsuz küçük bir değer verilebilir fakat pratikte bilgisayarda yaptığımız işlemlerde temsil edilebilecek değişkenlerin değeri alttan ve üstten sınırlıdır. Bu nedenle Mandelbrot Kümesini incelerken çözünürlüğün maksimum ne kadar olacağı bilgisayarınızın temsil  gücüyle ilgilidir.

          for (int y = -halfHeight; y < halfHeight; ++y) {
                if (restart)
                    break;
                if (abort)
                    return;
                uint *scanLine = reinterpret_cast<uint *>(image.scanLine(y + halfHeight));
                double ay = centerY + (y * scaleFactor);

                for (int x = -halfWidth; x < halfWidth; ++x){
                    double ax = centerX + (x * scaleFactor);
                    double a1 = ax;
                    double b1 = ay;

Her piksel konumu için iterasyon yapılır. Burada a1 ve b1 değişkenleri hesap sırasında geçici değişkenler olarak kullanılmıştır. Limit değişkenin değeri aşıldığında iterasyon sonlandırılır.

                    int numIterations = 0;

                    do {
                        ++numIterations;
                        double a2 = (a1 * a1) - (b1 * b1) + ax;
                        double b2 = (2 * a1 * b1) + ay;
                        if ((a2 * a2) + (b2 * b2) > Limit)
                            break;
                        ++numIterations;
                        a1 = (a2 * a2) - (b2 * b2) + ax;
                        b1 = (2 * a2 * b2) + ay;
                        if ((a1 * a1) + (b1 * b1) > Limit)
                            break;
                    } while(numIterations < MaxIterations);

Fonksiyonun hesaplamasında kullanılan iterasyon sayısına göre konumun rengi atanır. allBlack değişkeni maksimum iterasyon sayısını belirlemede kullanılacak.

                    if (numIterations < MaxIterations) {
                        *scanLine++ = colormap[numIterations % ColormapSize];
                        allBlack = false;
                    } else {
                        *scanLine++ = qRgb(0, 0, 0);
                    }
                }
            }

Görüntünün ilk gezilmesi sırasında bütün görüntü siyah ise maksimum iterasyon sayısı hızlı şekilde artırılır bunun için pass değişkeni 4 olarak atanır. Eğer bütün görüntü siyah değilse görüntüyü göstermek üzere renderedImage fonksiyonu çağrılır. Kod aşağıda gösterilmiştir.

            if (allBlack && pass == 0) {
                pass = 4;
            } else {
                if (!restart)
                    emit renderedImage(image, scaleFactor);
                ++pass;
            }
        }
        mutex.lock();
        if (!restart)
            condition.wait(&mutex);
        restart = false;
        mutex.unlock();
    }
}

Program sonlandığında abort değişkeni true olarak değiştirilir. wakeOne fonksiyonu çağrılarak eğer varsa bekleyen bir iplik aktif edilir.

RenderThread::~RenderThread()
{
    mutex.lock();
    abort = true;
    condition.wakeOne();
    mutex.unlock();
    wait();
}

Bu uygulamamızı da burada sonlandırmış olduk. Umarım hepiniz için faydalı olmuştur. İpliklerin kullanımının GAP için çok önemli olduğunu bir kez daha görmüş olduk.

Subscribe
Bildir
guest
0 Yorum
Inline Feedbacks
Tüm yorumları göster
Arıcılık Malzemeleri

Yeni Yazılar

Mühendislik Maaşları

Bunları Gördünüz mü?