/**************************************************************************
* This file is part of the Fraqtive program
* Copyright (C) 2004-2012 Michał Męciński
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
**************************************************************************/
#include "bookmarkmodel.h"
#include
#ifndef M_PI
# define M_PI 3.14159265358979323846
#endif
#include
#include
#include
#include "fraqtiveapplication.h"
#include "fractaldata.h"
#include "jobscheduler.h"
#include "datafunctions.h"
Q_DECLARE_METATYPE( QModelIndex )
BookmarkModel::BookmarkModel( QObject* parent ) : QAbstractListModel( parent ),
m_gradientCache( NULL ),
m_enabled( false ),
m_activeJobs( 0 )
{
qRegisterMetaType();
}
BookmarkModel::~BookmarkModel()
{
QMutexLocker locker( &m_mutex );
cancelJobs();
while ( m_activeJobs > 0 )
m_allJobsDone.wait( &m_mutex );
delete[] m_gradientCache;
}
void BookmarkModel::setMap( BookmarkMap* map )
{
m_map = map;
update();
m_enabled = true;
m_queue = m_keys;
}
static const int GradientSize = 16384;
void BookmarkModel::setColorSettings( const Gradient& gradient, const QColor& backgroundColor, const ColorMapping& mapping )
{
QMutexLocker locker( &m_mutex );
cancelJobs();
if ( !m_gradientCache )
m_gradientCache = new QRgb[ GradientSize ];
DataFunctions::fillGradientCache( gradient, m_gradientCache, GradientSize );
m_backgroundColor = backgroundColor;
m_colorMapping = mapping;
}
void BookmarkModel::abortGeneration()
{
QMutexLocker locker( &m_mutex );
m_enabled = false;
cancelJobs();
}
void BookmarkModel::continueGeneration()
{
QMutexLocker locker( &m_mutex );
m_enabled = true;
addJobs();
}
void BookmarkModel::invalidateBookmark( const QString& name )
{
QMutexLocker locker( &m_mutex );
m_images.remove( name );
if ( !m_queue.contains( name ) ) {
m_queue.append( name );
addJobs( 1 );
}
}
static bool localeAwareLessThan( const QString& s1, const QString& s2 )
{
return QString::localeAwareCompare( s1, s2 ) < 0;
}
void BookmarkModel::update()
{
QMutexLocker locker( &m_mutex );
cancelJobs();
beginResetModel();
m_keys = m_map->keys();
qSort( m_keys.begin(), m_keys.end(), localeAwareLessThan );
endResetModel();
}
int BookmarkModel::rowCount( const QModelIndex& parent ) const
{
if ( !parent.isValid() )
return m_keys.count();
return 0;
}
QVariant BookmarkModel::data( const QModelIndex& index, int role ) const
{
if ( role == Qt::DisplayRole )
return m_keys.at( index.row() );
if ( role == Qt::DecorationRole ) {
QMutexLocker locker( &m_mutex );
QString name = m_keys.at( index.row() );
QPixmap pixmap;
if ( m_images.contains( name ) ) {
pixmap = QPixmap::fromImage( m_images.value( name ) );
} else {
pixmap = QPixmap( 48, 48 );
pixmap.fill( QApplication::palette().color( QPalette::Disabled, QPalette::Window ) );
}
locker.unlock();
QPainter painter( &pixmap );
QPixmap pixmap2 = pixmap;
QPainter painter2( &pixmap2 );
painter.setPen( QApplication::palette().color( QPalette::Dark ) );
painter.drawRect( pixmap.rect().adjusted( 0, 0, -1, -1 ) );
painter2.setPen( QApplication::palette().color( QPalette::Highlight ) );
painter2.drawRect( pixmap.rect().adjusted( 0, 0, -1, -1 ) );
painter2.drawRect( pixmap.rect().adjusted( 1, 1, -2, -2 ) );
QIcon icon;
icon.addPixmap( pixmap, QIcon::Normal );
icon.addPixmap( pixmap2, QIcon::Selected );
return icon;
}
return QVariant();
}
int BookmarkModel::priority() const
{
return 1;
}
static int roundToCellSize( int size )
{
// round up to nearest N * CellSize + 1
return ( ( size - 1 + GeneratorCore::CellSize - 1 ) / GeneratorCore::CellSize ) * GeneratorCore::CellSize + 1;
}
void BookmarkModel::executeJob()
{
QMutexLocker locker( &m_mutex );
if ( !m_enabled || m_queue.isEmpty() ) {
finishJob();
return;
}
QString name = m_queue.takeFirst();
if ( !m_map->contains( name ) ) {
finishJob();
return;
}
Bookmark bookmark = m_map->value( name );
locker.unlock();
const int imageSize = 48;
const int bufferSize = roundToCellSize( imageSize + 2 ); // 2 pixel margin for anti-aliasing
double* buffer = new double[ bufferSize * bufferSize ];
calculate( bookmark, buffer, QSize( bufferSize, bufferSize ), QSize( imageSize, imageSize ) );
FractalData data;
data.transferBuffer( buffer, bufferSize, QSize( imageSize, imageSize ) );
QImage image( imageSize, imageSize, QImage::Format_RGB32 );
ViewSettings settings = DataFunctions::defaultViewSettings();
DataFunctions::ColorMapper mapper( m_gradientCache, GradientSize, m_backgroundColor.rgb(), m_colorMapping );
DataFunctions::drawImage( image, &data, image.rect(), mapper, settings.antiAliasing() );
locker.relock();
if ( m_queue.contains( name ) ) {
finishJob();
return;
}
m_images.insert( name, image );
int row = m_keys.indexOf( name );
if ( row >= 0 )
emit dataChanged( index( row ), index( row ) );
finishJob();
}
void BookmarkModel::calculate( const Bookmark& bookmark, double* buffer, const QSize& size, const QSize& resolution )
{
GeneratorCore::Input input;
Position position = bookmark.position();
double scale = pow( 10.0, -position.zoomFactor() ) / (double)resolution.height();
double sa = scale * sin( position.angle() * M_PI / 180.0 );
double ca = scale * cos( position.angle() * M_PI / 180.0 );
double offsetX = -(double)resolution.width() / 2.0 - 0.5;
double offsetY = -(double)resolution.height() / 2.0 - 0.5;
input.m_sa = sa;
input.m_ca = ca;
input.m_x = position.center().x() + ca * offsetX + sa * offsetY;
input.m_y = position.center().y() - sa * offsetX + ca * offsetY;
GeneratorCore::Output output;
output.m_buffer = buffer;
output.m_width = size.width();
output.m_height = size.height();
output.m_stride = size.width();
GeneratorSettings settings = DataFunctions::defaultGeneratorSettings();
int maxIterations = (int)( pow( 10.0, settings.calculationDepth() ) * qMax( 1.0, 1.45 + position.zoomFactor() ) );
double threshold = settings.detailThreshold();
#if defined( HAVE_SSE2 )
GeneratorCore::FunctorSSE2* functorSSE2 = DataFunctions::createFunctorSSE2( bookmark.fractalType() );
if ( functorSSE2 ) {
GeneratorCore::generatePreviewSSE2( input, output, functorSSE2, maxIterations );
GeneratorCore::interpolate( output );
GeneratorCore::generateDetailsSSE2( input, output, functorSSE2, maxIterations, threshold );
delete functorSSE2;
return;
}
#endif
GeneratorCore::Functor* functor = DataFunctions::createFunctor( bookmark.fractalType() );
if ( functor ) {
GeneratorCore::generatePreview( input, output, functor, maxIterations );
GeneratorCore::interpolate( output );
GeneratorCore::generateDetails( input, output, functor, maxIterations, threshold );
delete functor;
}
}
void BookmarkModel::addJobs( int count /*= -1*/ )
{
if ( count < 0 )
count = m_queue.count();
if ( count > 0 ) {
fraqtive()->jobScheduler()->addJobs( this, count );
m_activeJobs += count;
}
}
void BookmarkModel::cancelJobs()
{
int count = fraqtive()->jobScheduler()->cancelAllJobs( this );
m_activeJobs -= count;
if ( m_activeJobs == 0 )
m_allJobsDone.wakeAll();
}
void BookmarkModel::finishJob()
{
m_activeJobs--;
if ( m_activeJobs == 0 )
m_allJobsDone.wakeAll();
}