1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the examples of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:BSD$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** BSD License Usage
18 ** Alternatively, you may use this file under the terms of the BSD license
19 ** as follows:
20 **
21 ** "Redistribution and use in source and binary forms, with or without
22 ** modification, are permitted provided that the following conditions are
23 ** met:
24 **   * Redistributions of source code must retain the above copyright
25 **     notice, this list of conditions and the following disclaimer.
26 **   * Redistributions in binary form must reproduce the above copyright
27 **     notice, this list of conditions and the following disclaimer in
28 **     the documentation and/or other materials provided with the
29 **     distribution.
30 **   * Neither the name of The Qt Company Ltd nor the names of its
31 **     contributors may be used to endorse or promote products derived
32 **     from this software without specific prior written permission.
33 **
34 **
35 ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
36 ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37 ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
38 ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
39 ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40 ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41 ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
42 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
43 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
44 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
45 ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
46 **
47 ** $QT_END_LICENSE$
48 **
49 ****************************************************************************/
50 
51 #include "mandelbrotwidget.h"
52 #include "mandelbrotwidget.moc"
53 
54 #include <QtGui/QPainter>
55 #include <QtGui/QKeyEvent>
56 
57 #include <math.h>
58 
59 const double DefaultCenterX = -0.637011;
60 const double DefaultCenterY = -0.0395159;
61 const double DefaultScale = 0.00403897;
62 
63 const double ZoomInFactor = 0.8;
64 const double ZoomOutFactor = 1 / ZoomInFactor;
65 const int ScrollStep = 20;
66 
MandelbrotWidget(QWidget * parent)67 MandelbrotWidget::MandelbrotWidget(QWidget* parent)
68     : QWidget(parent)
69     , centerX(DefaultCenterX)
70     , centerY(DefaultCenterY)
71     , pixmapScale(DefaultScale)
72     , curScale(DefaultScale)
73 {
74     connect(&thread, &RenderThread::renderedImage, this, &MandelbrotWidget::updatePixmap);
75 
76     setWindowTitle(tr("Mandelbrot"));
77 #if QT_CONFIG(cursor)
78     setCursor(Qt::CrossCursor);
79 #endif
80     resize(550, 400);
81 }
82 
paintEvent(QPaintEvent *)83 void MandelbrotWidget::paintEvent(QPaintEvent* /* event */)
84 {
85     QPainter painter(this);
86     painter.fillRect(rect(), Qt::black);
87 
88     if (pixmap.isNull())
89     {
90         painter.setPen(Qt::white);
91         painter.drawText(rect(), Qt::AlignCenter, tr("Rendering initial image, please wait..."));
92         return;
93     }
94 
95     if (qFuzzyCompare(curScale, pixmapScale))
96     {
97         painter.drawPixmap(pixmapOffset, pixmap);
98     }
99     else
100     {
101         auto previewPixmap = qFuzzyCompare(pixmap.devicePixelRatioF(), qreal(1))
102                                  ? pixmap
103                                  : pixmap.scaled(pixmap.size() / pixmap.devicePixelRatioF(),
104                                                  Qt::KeepAspectRatio, Qt::SmoothTransformation);
105         double scaleFactor = pixmapScale / curScale;
106         int newWidth = int(previewPixmap.width() * scaleFactor);
107         int newHeight = int(previewPixmap.height() * scaleFactor);
108         int newX = pixmapOffset.x() + (previewPixmap.width() - newWidth) / 2;
109         int newY = pixmapOffset.y() + (previewPixmap.height() - newHeight) / 2;
110 
111         painter.save();
112         painter.translate(newX, newY);
113         painter.scale(scaleFactor, scaleFactor);
114 
115         QRectF exposed = painter.transform().inverted().mapRect(rect()).adjusted(-1, -1, 1, 1);
116         painter.drawPixmap(exposed, previewPixmap, exposed);
117         painter.restore();
118     }
119 
120     QString text = tr("Use mouse wheel or the '+' and '-' keys to zoom. "
121                       "Press and hold left mouse button to scroll.");
122     QFontMetrics metrics = painter.fontMetrics();
123     int textWidth = metrics.horizontalAdvance(text);
124 
125     painter.setPen(Qt::NoPen);
126     painter.setBrush(QColor(0, 0, 0, 127));
127     painter.drawRect((width() - textWidth) / 2 - 5, 0, textWidth + 10, metrics.lineSpacing() + 5);
128     painter.setPen(Qt::white);
129     painter.drawText((width() - textWidth) / 2, metrics.leading() + metrics.ascent(), text);
130 }
131 
resizeEvent(QResizeEvent *)132 void MandelbrotWidget::resizeEvent(QResizeEvent* /* event */)
133 {
134     thread.render(centerX, centerY, curScale, size(), devicePixelRatioF());
135 }
136 
keyPressEvent(QKeyEvent * event)137 void MandelbrotWidget::keyPressEvent(QKeyEvent* event)
138 {
139     switch (event->key())
140     {
141         case Qt::Key_Plus:
142             zoom(ZoomInFactor);
143             break;
144         case Qt::Key_Minus:
145             zoom(ZoomOutFactor);
146             break;
147         case Qt::Key_Left:
148             scroll(-ScrollStep, 0);
149             break;
150         case Qt::Key_Right:
151             scroll(+ScrollStep, 0);
152             break;
153         case Qt::Key_Down:
154             scroll(0, -ScrollStep);
155             break;
156         case Qt::Key_Up:
157             scroll(0, +ScrollStep);
158             break;
159         default:
160             QWidget::keyPressEvent(event);
161     }
162 }
163 
164 #if QT_CONFIG(wheelevent)
wheelEvent(QWheelEvent * event)165 void MandelbrotWidget::wheelEvent(QWheelEvent* event)
166 {
167     const int numDegrees = event->angleDelta().y() / 8;
168     const double numSteps = numDegrees / double(15);
169     zoom(pow(ZoomInFactor, numSteps));
170 }
171 #endif
172 
mousePressEvent(QMouseEvent * event)173 void MandelbrotWidget::mousePressEvent(QMouseEvent* event)
174 {
175     if (event->button() == Qt::LeftButton)
176         lastDragPos = event->pos();
177 }
178 
mouseMoveEvent(QMouseEvent * event)179 void MandelbrotWidget::mouseMoveEvent(QMouseEvent* event)
180 {
181     if (event->buttons() & Qt::LeftButton)
182     {
183         pixmapOffset += event->pos() - lastDragPos;
184         lastDragPos = event->pos();
185         update();
186     }
187 }
188 
mouseReleaseEvent(QMouseEvent * event)189 void MandelbrotWidget::mouseReleaseEvent(QMouseEvent* event)
190 {
191     if (event->button() == Qt::LeftButton)
192     {
193         pixmapOffset += event->pos() - lastDragPos;
194         lastDragPos = QPoint();
195 
196         const auto pixmapSize = pixmap.size() / pixmap.devicePixelRatioF();
197         int deltaX = (width() - pixmapSize.width()) / 2 - pixmapOffset.x();
198         int deltaY = (height() - pixmapSize.height()) / 2 - pixmapOffset.y();
199         scroll(deltaX, deltaY);
200     }
201 }
202 
updatePixmap(const QImage & image,double scaleFactor)203 void MandelbrotWidget::updatePixmap(const QImage& image, double scaleFactor)
204 {
205     if (!lastDragPos.isNull())
206         return;
207 
208     pixmap = QPixmap::fromImage(image);
209     pixmapOffset = QPoint();
210     lastDragPos = QPoint();
211     pixmapScale = scaleFactor;
212     update();
213 }
214 
zoom(double zoomFactor)215 void MandelbrotWidget::zoom(double zoomFactor)
216 {
217     curScale *= zoomFactor;
218     update();
219     thread.render(centerX, centerY, curScale, size(), devicePixelRatioF());
220 }
221 
scroll(int deltaX,int deltaY)222 void MandelbrotWidget::scroll(int deltaX, int deltaY)
223 {
224     centerX += deltaX * curScale;
225     centerY += deltaY * curScale;
226     update();
227     thread.render(centerX, centerY, curScale, size(), devicePixelRatioF());
228 }
229