|  | Home · Examples

# Code for Mandelbrot Example

    /****************************************************************************
     **
     ** Copyright (C) 1992-2008 Nokia. All rights reserved.
     **
     ** This file is part of Qt Jambi.
     **
     ** * Commercial Usage
    * Licensees holding valid Qt Commercial licenses may use this file in
    * accordance with the Qt Commercial License Agreement provided with the
    * Software or, alternatively, in accordance with the terms contained in
    * a written agreement between you and Nokia.
    *
    *
    * GNU General Public License Usage
    * Alternatively, this file may be used under the terms of the GNU
    * General Public License versions 2.0 or 3.0 as published by the Free
    * Software Foundation and appearing in the file LICENSE.GPL included in
    * the packaging of this file.  Please review the following information
    * to ensure GNU General Public Licensing requirements will be met:
    * http://www.fsf.org/licensing/licenses/info/GPLv2.html and
    * http://www.gnu.org/copyleft/gpl.html.  In addition, as a special
    * exception, Nokia gives you certain additional rights. These rights
    * are described in the Nokia Qt GPL Exception version 1.2, included in
    * the file GPL_EXCEPTION.txt in this package.
    *
    * Qt for Windows(R) Licensees
    * As a special exception, Nokia, as the sole copyright holder for Qt
    * Designer, grants users of the Qt/Eclipse Integration plug-in the
    * right for the Qt/Eclipse Integration to link to functionality
    * provided by Qt Designer and its related libraries.
    *
    *
    * If you are unsure which license is appropriate for your use, please
    * contact the sales department at qt-sales@nokia.com.

     **
     ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
     ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
     **
     ****************************************************************************/

    package com.trolltech.examples;

    import com.trolltech.qt.core.*;
    import com.trolltech.qt.gui.*;
    import com.trolltech.qt.gui.QPainter.RenderHint;

    @QtJambiExample(name = "Mandelbrot")
    public class Mandelbrot extends QWidget {

        private RenderThread thread = new RenderThread();
        private QImage pixmap = new QImage();
        private QPoint pixmapOffset = new QPoint();
        private QPoint lastDragPosition = new QPoint();
        private double centerX;
        private double centerY;
        private double pixmapScale;
        private double currentScale;

        private boolean abort = false;

        final double DefaultCenterX = -0.637011f;
        final double DefaultCenterY = -0.0395159f;
        final double DefaultScale = 0.00403897f;

        final double ZoomInFactor = 0.8f;
        final double ZoomOutFactor = 1 / ZoomInFactor;
        final int ScrollStep = 20;

        private Signal2<QImage, Double> renderedImage =
                new Signal2<QImage, Double>();

        public static void main(String args[]) {
            QApplication.initialize(args);
            Mandelbrot mainWindow = new Mandelbrot();
            mainWindow.show();
            QApplication.exec();
        }

        public Mandelbrot() {
            this(null);
        }

        @Override
        protected void disposed() {
            synchronized (thread) {
                abort = true;
                try {
                    thread.join();
                } catch (InterruptedException e) { }
            }
        }

        public Mandelbrot(QWidget widget) {
            super(widget);
            centerX = DefaultCenterX;
            centerY = DefaultCenterY;
            pixmapScale = DefaultScale;
            currentScale = DefaultScale;

            renderedImage.connect(this, "updatePixmap(QImage, Double)");

            setWindowTitle(tr("Mandelbrot"));
            setWindowIcon(new QIcon("classpath:com/trolltech/images/qt-logo.png"));
            setCursor(new QCursor(Qt.CursorShape.CrossCursor));
            resize(550, 400);
        }

        @Override
        public void paintEvent(QPaintEvent event) {
            QPainter painter = new QPainter();
            painter.begin(this);
            painter.setRenderHint(RenderHint.SmoothPixmapTransform);
            painter.fillRect(rect(), new QBrush(QColor.black));

            if (pixmap.isNull()) {
                String message = tr("Rendering initial image, please wait...");
                painter.setPen(QColor.white);
                painter.drawText(rect(), Qt.AlignmentFlag.AlignCenter.value(),
                                 message);
                painter.end();

                return;
            }
            if (currentScale == pixmapScale) {
                painter.drawImage(pixmapOffset, pixmap);
            } else {
                double scaleFactor = pixmapScale / currentScale;
                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);

                QMatrix invertedMatrix = painter.worldMatrix().inverted();
                QRect exposed = invertedMatrix.mapRect(rect());
                exposed = exposed.adjusted(-1, -1, 1, 1);

                if(scaleFactor>=0)
                    painter.drawImage(pixmapOffset, pixmap);
                else
                    painter.drawImage(exposed, pixmap, exposed);

                painter.restore();
            }

            String text = tr("Use mouse wheel to zoom.")
                          + tr("Press and hold left mouse button to scroll.");
            QFontMetrics metrics = painter.fontMetrics();
            int textWidth = metrics.width(text);
            int offset = (width() - textWidth) / 2;

            painter.setPen(QPen.NoPen);
            painter.setBrush(new QColor(0, 0, 0, 127));
            painter.drawRect(offset - 5, 0, textWidth + 10, metrics.lineSpacing() + 5);
            painter.setPen(QColor.white);
            painter.drawText(offset, metrics.leading() + metrics.ascent(), text);
            painter.end();
        }

        @Override
        public void resizeEvent(QResizeEvent event) {
            thread.render(centerX, centerY, currentScale, size());
        }

        @Override
        protected void closeEvent(QCloseEvent event) {
            synchronized (thread) {
                abort = true;
                thread.notify();
            }
            super.closeEvent(event);
        }

        @Override
        public void keyPressEvent(QKeyEvent event) {
            Qt.Key key = Qt.Key.resolve(event.key());
            switch (key) {
            case Key_Plus:
                zoom(ZoomInFactor);
                break;
            case Key_Minus:
                zoom(ZoomOutFactor);
                break;
            case Key_Left:
                scroll(-ScrollStep, 0);
                break;
            case Key_Right:
                scroll(+ScrollStep, 0);
                break;
            case Key_Down:
                scroll(0, -ScrollStep);
                break;
            case Key_Up:
                scroll(0, +ScrollStep);
                break;
            default:
                super.keyPressEvent(event);
            }
        }

        @Override
        public void wheelEvent(QWheelEvent event) {
            int numDegrees = event.delta() / 8;
            double numSteps = numDegrees / 15.0f;
            zoom(Math.pow(ZoomInFactor, numSteps));
        }

        @Override
        public void mousePressEvent(QMouseEvent event) {
            if (event.button() == Qt.MouseButton.LeftButton)
                lastDragPosition = event.pos();
        }

        @Override
        public void mouseMoveEvent(QMouseEvent event) {
            if (event.buttons().isSet(Qt.MouseButton.LeftButton)) {
                pixmapOffset.add(event.pos());
                pixmapOffset.subtract(lastDragPosition);

                lastDragPosition = event.pos();
                update();
            }
        }

        @Override
        public void mouseReleaseEvent(QMouseEvent event) {
            if (event.button() == Qt.MouseButton.LeftButton) {
                pixmapOffset.add(event.pos());
                pixmapOffset.subtract(lastDragPosition);
                lastDragPosition = new QPoint();

                int deltaX = (width() - pixmap.width()) / 2 - pixmapOffset.x();
                int deltaY = (height() - pixmap.height()) / 2 - pixmapOffset.y();
                scrollImage(deltaX, deltaY);
            }
        }

        private void updatePixmap(QImage image, Double scaleFactor) {
            if (!lastDragPosition.isNull())
                return;
            pixmap = image;
            pixmapOffset = new QPoint();
            lastDragPosition = new QPoint();
            pixmapScale = scaleFactor;

            QApplication.invokeLater(new Runnable() {
                public void run() {
                    synchronized (thread) {
                        if(!abort)
                            update();
                    }
                }
            });
        }

        protected void zoom(double zoomFactor) {
            currentScale *= zoomFactor;
            update();
            thread.render(centerX, centerY, currentScale, size());
        }

        public void scrollImage(int deltaX, int deltaY) {
            centerX += deltaX * currentScale;
            centerY += deltaY * currentScale;
            update();
            thread.render(centerX, centerY, currentScale, size());
        }

        private class RenderThread extends Thread {
            private double centerX;
            private double centerY;
            private double scaleFactor;
            private QSize resultSize;
            private boolean restart;

            final int ColormapSize = 512;
            int[] colormap = new int[ColormapSize];

            RenderThread() {
                restart = false;

                for (int i = 0; i < ColormapSize; ++i) {
                    double wave = 380.0 + (i * 400.0 / ColormapSize);
                    colormap[i] = rgbFromWaveLength(wave);
                }
            }

            synchronized void render(double centerX, double centerY,
                                     double scaleFactor, QSize resultSize) {

                this.centerX = centerX;
                this.centerY = centerY;
                this.scaleFactor = scaleFactor;
                this.resultSize = resultSize;

                if (!isAlive()) {
                    start();
                } else {
                    restart = true;
                    notify();
                }
            }

            @Override
            public void run() {
                QSize resultSize;
                double scaleFactor;
                double centerX;
                double centerY;

                while (true) {
                    synchronized (this) {
                        resultSize = this.resultSize;
                        scaleFactor = this.scaleFactor;
                        centerX = this.centerX;
                        centerY = this.centerY;
                    }

                    int halfWidth = resultSize.width() / 2;
                    int halfHeight = resultSize.height() / 2;
                    QImage.Format format = QImage.Format.Format_RGB32;
                    QImage image = new QImage(resultSize, format);

                    final int NumPasses = 8;
                    int pass = 0;
                    while (pass < NumPasses) {
                        final int MaxIterations = (1 << (2 * pass + 6)) + 32;
                        final int Limit = 4;
                        boolean allBlack = true;

                        for (int y = -halfHeight; y < halfHeight; ++y) {
                            if (restart)
                                break;
                            if (abort)
                                return;

                            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) {
                                    int index = numIterations % ColormapSize;
                                    image.setPixel(x + halfWidth, y + halfHeight,
                                                   colormap[index]);

                                    allBlack = false;
                                } else {
                                    image.setPixel(x + halfWidth, y + halfHeight,
                                                   0xff000000);
                                }
                            }
                        }
                        if (allBlack && pass == 0) {
                            pass = 4;
                        } else {
                            synchronized (this) {
                            if (!restart) {
                                //renderedImage.emit(image, scaleFactor);
                                updatePixmap(image, scaleFactor);
                            }
                            }
                            ++pass;
                        }
                    }
                    synchronized (this) {
                        if (!restart)
                            try {
                                wait();
                            } catch (InterruptedException event) {
                                event.printStackTrace();
                            }
                        restart = false;
                    }
                }
            }

            int rgbFromWaveLength(double wave) {
                double red= 0.0;
                double green = 0.0;
                double blue = 0.0;

                if (wave >= 380.0 && wave <= 440.0) {
                    red = -1.0 * (wave - 440.0) / (440.0 - 380.0);
                    blue = 1.0;
                } else if (wave >= 440.0 && wave <= 490.0) {
                    green = (wave - 440.0) / (490.0 - 440.0);
                    blue = 1.0;
                } else if (wave >= 490.0 && wave <= 510.0) {
                    green = 1.0;
                    blue = -1.0 * (wave - 510.0) / (510.0 - 490.0);
                } else if (wave >= 510.0 && wave <= 580.0) {
                    red = (wave - 510.0) / (580.0 - 510.0);
                    green = 1.0;
                } else if (wave >= 580.0 && wave <= 645.0) {
                    red = 1.0;
                    green = -1.0 * (wave - 645.0) / (645.0 - 580.0);
                } else if (wave >= 645.0 && wave <= 780.0) {
                    red= 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);

                red = Math.pow(red * s, 0.8) * 255;
                green = Math.pow(green * s, 0.8) * 255;
                blue = Math.pow(blue * s, 0.8) * 255;

                QColor color = new QColor((int) red, (int) green, (int) blue);

                return color.rgb();
            }
        }
    }