1 /**************************************************************************
2 * This file is part of the Fraqtive program
3 * Copyright (C) 2004-2012 Micha� M�ci�ski
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 **************************************************************************/
18 
19 #include "bookmarkmodel.h"
20 
21 #include <math.h>
22 
23 #ifndef M_PI
24 # define M_PI 3.14159265358979323846
25 #endif
26 
27 #include <QPalette>
28 #include <QPainter>
29 #include <QIcon>
30 
31 #include "fraqtiveapplication.h"
32 #include "fractaldata.h"
33 #include "jobscheduler.h"
34 #include "datafunctions.h"
35 
Q_DECLARE_METATYPE(QModelIndex)36 Q_DECLARE_METATYPE( QModelIndex )
37 
38 BookmarkModel::BookmarkModel( QObject* parent ) : QAbstractListModel( parent ),
39     m_gradientCache( NULL ),
40     m_enabled( false ),
41     m_activeJobs( 0 )
42 {
43     qRegisterMetaType<QModelIndex>();
44 }
45 
~BookmarkModel()46 BookmarkModel::~BookmarkModel()
47 {
48     QMutexLocker locker( &m_mutex );
49 
50     cancelJobs();
51 
52     while ( m_activeJobs > 0 )
53         m_allJobsDone.wait( &m_mutex );
54 
55     delete[] m_gradientCache;
56 }
57 
setMap(BookmarkMap * map)58 void BookmarkModel::setMap( BookmarkMap* map )
59 {
60     m_map = map;
61 
62     update();
63 
64     m_enabled = true;
65     m_queue = m_keys;
66 }
67 
68 static const int GradientSize = 16384;
69 
setColorSettings(const Gradient & gradient,const QColor & backgroundColor,const ColorMapping & mapping)70 void BookmarkModel::setColorSettings( const Gradient& gradient, const QColor& backgroundColor, const ColorMapping& mapping )
71 {
72     QMutexLocker locker( &m_mutex );
73 
74     cancelJobs();
75 
76     if ( !m_gradientCache )
77         m_gradientCache = new QRgb[ GradientSize ];
78 
79     DataFunctions::fillGradientCache( gradient, m_gradientCache, GradientSize );
80 
81     m_backgroundColor = backgroundColor;
82     m_colorMapping = mapping;
83 }
84 
abortGeneration()85 void BookmarkModel::abortGeneration()
86 {
87     QMutexLocker locker( &m_mutex );
88 
89     m_enabled = false;
90 
91     cancelJobs();
92 }
93 
continueGeneration()94 void BookmarkModel::continueGeneration()
95 {
96     QMutexLocker locker( &m_mutex );
97 
98     m_enabled = true;
99 
100     addJobs();
101 }
102 
invalidateBookmark(const QString & name)103 void BookmarkModel::invalidateBookmark( const QString& name )
104 {
105     QMutexLocker locker( &m_mutex );
106 
107     m_images.remove( name );
108 
109     if ( !m_queue.contains( name ) ) {
110         m_queue.append( name );
111         addJobs( 1 );
112     }
113 }
114 
localeAwareLessThan(const QString & s1,const QString & s2)115 static bool localeAwareLessThan( const QString& s1, const QString& s2 )
116 {
117     return QString::localeAwareCompare( s1, s2 ) < 0;
118 }
119 
update()120 void BookmarkModel::update()
121 {
122     QMutexLocker locker( &m_mutex );
123 
124     cancelJobs();
125 
126     beginResetModel();
127 
128     m_keys = m_map->keys();
129     qSort( m_keys.begin(), m_keys.end(), localeAwareLessThan );
130 
131     endResetModel();
132 }
133 
rowCount(const QModelIndex & parent) const134 int BookmarkModel::rowCount( const QModelIndex& parent ) const
135 {
136     if ( !parent.isValid() )
137         return m_keys.count();
138     return 0;
139 }
140 
data(const QModelIndex & index,int role) const141 QVariant BookmarkModel::data( const QModelIndex& index, int role ) const
142 {
143     if ( role == Qt::DisplayRole )
144         return m_keys.at( index.row() );
145 
146     if ( role == Qt::DecorationRole ) {
147         QMutexLocker locker( &m_mutex );
148 
149         QString name = m_keys.at( index.row() );
150 
151         QPixmap pixmap;
152         if ( m_images.contains( name ) ) {
153             pixmap = QPixmap::fromImage( m_images.value( name ) );
154         } else {
155             pixmap = QPixmap( 48, 48 );
156             pixmap.fill( QApplication::palette().color( QPalette::Disabled, QPalette::Window ) );
157         }
158 
159         locker.unlock();
160 
161         QPainter painter( &pixmap );
162 
163         QPixmap pixmap2 = pixmap;
164         QPainter painter2( &pixmap2 );
165 
166         painter.setPen( QApplication::palette().color( QPalette::Dark ) );
167         painter.drawRect( pixmap.rect().adjusted( 0, 0, -1, -1 ) );
168 
169         painter2.setPen( QApplication::palette().color( QPalette::Highlight ) );
170         painter2.drawRect( pixmap.rect().adjusted( 0, 0, -1, -1 ) );
171         painter2.drawRect( pixmap.rect().adjusted( 1, 1, -2, -2 ) );
172 
173         QIcon icon;
174         icon.addPixmap( pixmap, QIcon::Normal );
175         icon.addPixmap( pixmap2, QIcon::Selected );
176 
177         return icon;
178     }
179 
180     return QVariant();
181 }
182 
priority() const183 int BookmarkModel::priority() const
184 {
185     return 1;
186 }
187 
roundToCellSize(int size)188 static int roundToCellSize( int size )
189 {
190     // round up to nearest N * CellSize + 1
191     return ( ( size - 1 + GeneratorCore::CellSize - 1 ) / GeneratorCore::CellSize ) * GeneratorCore::CellSize + 1;
192 }
193 
executeJob()194 void BookmarkModel::executeJob()
195 {
196     QMutexLocker locker( &m_mutex );
197 
198     if ( !m_enabled || m_queue.isEmpty() ) {
199         finishJob();
200         return;
201     }
202 
203     QString name = m_queue.takeFirst();
204 
205     if ( !m_map->contains( name ) ) {
206         finishJob();
207         return;
208     }
209 
210     Bookmark bookmark = m_map->value( name );
211 
212     locker.unlock();
213 
214     const int imageSize = 48;
215     const int bufferSize = roundToCellSize( imageSize + 2 ); // 2 pixel margin for anti-aliasing
216 
217     double* buffer = new double[ bufferSize * bufferSize ];
218 
219     calculate( bookmark, buffer, QSize( bufferSize, bufferSize ), QSize( imageSize, imageSize ) );
220 
221     FractalData data;
222     data.transferBuffer( buffer, bufferSize, QSize( imageSize, imageSize ) );
223 
224     QImage image( imageSize, imageSize, QImage::Format_RGB32 );
225 
226     ViewSettings settings = DataFunctions::defaultViewSettings();
227 
228     DataFunctions::ColorMapper mapper( m_gradientCache, GradientSize, m_backgroundColor.rgb(), m_colorMapping );
229     DataFunctions::drawImage( image, &data, image.rect(), mapper, settings.antiAliasing() );
230 
231     locker.relock();
232 
233     if ( m_queue.contains( name ) ) {
234         finishJob();
235         return;
236     }
237 
238     m_images.insert( name, image );
239 
240     int row = m_keys.indexOf( name );
241     if ( row >= 0 )
242         emit dataChanged( index( row ), index( row ) );
243 
244     finishJob();
245 }
246 
calculate(const Bookmark & bookmark,double * buffer,const QSize & size,const QSize & resolution)247 void BookmarkModel::calculate( const Bookmark& bookmark, double* buffer, const QSize& size, const QSize& resolution )
248 {
249     GeneratorCore::Input input;
250 
251     Position position = bookmark.position();
252 
253     double scale = pow( 10.0, -position.zoomFactor() ) / (double)resolution.height();
254 
255     double sa = scale * sin( position.angle() * M_PI / 180.0 );
256     double ca = scale * cos( position.angle() * M_PI / 180.0 );
257 
258     double offsetX = -(double)resolution.width() / 2.0 - 0.5;
259     double offsetY = -(double)resolution.height() / 2.0 - 0.5;
260 
261     input.m_sa = sa;
262     input.m_ca = ca;
263     input.m_x = position.center().x() + ca * offsetX + sa * offsetY;
264     input.m_y = position.center().y() - sa * offsetX + ca * offsetY;
265 
266     GeneratorCore::Output output;
267 
268     output.m_buffer = buffer;
269     output.m_width = size.width();
270     output.m_height = size.height();
271     output.m_stride = size.width();
272 
273     GeneratorSettings settings = DataFunctions::defaultGeneratorSettings();
274 
275     int maxIterations = (int)( pow( 10.0, settings.calculationDepth() ) * qMax( 1.0, 1.45 + position.zoomFactor() ) );
276     double threshold = settings.detailThreshold();
277 
278 #if defined( HAVE_SSE2 )
279     GeneratorCore::FunctorSSE2* functorSSE2 = DataFunctions::createFunctorSSE2( bookmark.fractalType() );
280     if ( functorSSE2 ) {
281         GeneratorCore::generatePreviewSSE2( input, output, functorSSE2, maxIterations );
282         GeneratorCore::interpolate( output );
283         GeneratorCore::generateDetailsSSE2( input, output, functorSSE2, maxIterations, threshold );
284         delete functorSSE2;
285         return;
286     }
287 #endif
288 
289     GeneratorCore::Functor* functor = DataFunctions::createFunctor( bookmark.fractalType() );
290     if ( functor ) {
291         GeneratorCore::generatePreview( input, output, functor, maxIterations );
292         GeneratorCore::interpolate( output );
293         GeneratorCore::generateDetails( input, output, functor, maxIterations, threshold );
294         delete functor;
295     }
296 }
297 
addJobs(int count)298 void BookmarkModel::addJobs( int count /*= -1*/ )
299 {
300     if ( count < 0 )
301         count = m_queue.count();
302     if ( count > 0 ) {
303         fraqtive()->jobScheduler()->addJobs( this, count );
304         m_activeJobs += count;
305     }
306 }
307 
cancelJobs()308 void BookmarkModel::cancelJobs()
309 {
310     int count = fraqtive()->jobScheduler()->cancelAllJobs( this );
311     m_activeJobs -= count;
312 
313     if ( m_activeJobs == 0 )
314         m_allJobsDone.wakeAll();
315 }
316 
finishJob()317 void BookmarkModel::finishJob()
318 {
319     m_activeJobs--;
320 
321     if ( m_activeJobs == 0 )
322         m_allJobsDone.wakeAll();
323 }
324