Quantum GIS API Documentation  1.8
src/core/symbology-ng/qgssvgcache.cpp
Go to the documentation of this file.
00001 /***************************************************************************
00002                               qgssvgcache.h
00003                             ------------------------------
00004   begin                :  2011
00005   copyright            : (C) 2011 by Marco Hugentobler
00006   email                : marco dot hugentobler at sourcepole dot ch
00007  ***************************************************************************/
00008 
00009 /***************************************************************************
00010  *                                                                         *
00011  *   This program is free software; you can redistribute it and/or modify  *
00012  *   it under the terms of the GNU General Public License as published by  *
00013  *   the Free Software Foundation; either version 2 of the License, or     *
00014  *   (at your option) any later version.                                   *
00015  *                                                                         *
00016  ***************************************************************************/
00017 
00018 #include "qgssvgcache.h"
00019 #include "qgslogger.h"
00020 #include <QDomDocument>
00021 #include <QDomElement>
00022 #include <QFile>
00023 #include <QImage>
00024 #include <QPainter>
00025 #include <QPicture>
00026 #include <QSvgRenderer>
00027 
00028 QgsSvgCacheEntry::QgsSvgCacheEntry(): file( QString() ), size( 0 ), outlineWidth( 0 ), widthScaleFactor( 1.0 ), rasterScaleFactor( 1.0 ), fill( Qt::black ),
00029     outline( Qt::black ), image( 0 ), picture( 0 )
00030 {
00031 }
00032 
00033 QgsSvgCacheEntry::QgsSvgCacheEntry( const QString& f, double s, double ow, double wsf, double rsf, const QColor& fi, const QColor& ou ): file( f ), size( s ), outlineWidth( ow ),
00034     widthScaleFactor( wsf ), rasterScaleFactor( rsf ), fill( fi ), outline( ou ), image( 0 ), picture( 0 )
00035 {
00036 }
00037 
00038 
00039 QgsSvgCacheEntry::~QgsSvgCacheEntry()
00040 {
00041   delete image;
00042   delete picture;
00043 }
00044 
00045 bool QgsSvgCacheEntry::operator==( const QgsSvgCacheEntry& other ) const
00046 {
00047   return ( other.file == file && other.size == size && other.outlineWidth == outlineWidth && other.widthScaleFactor == widthScaleFactor
00048            && other.rasterScaleFactor == rasterScaleFactor && other.fill == fill && other.outline == outline );
00049 }
00050 
00051 int QgsSvgCacheEntry::dataSize() const
00052 {
00053   int size = svgContent.size();
00054   if ( picture )
00055   {
00056     size += picture->size();
00057   }
00058   if ( image )
00059   {
00060     size += ( image->width() * image->height() * 32 );
00061   }
00062   return size;
00063 }
00064 
00065 QString file;
00066 double size;
00067 double outlineWidth;
00068 double widthScaleFactor;
00069 double rasterScaleFactor;
00070 QColor fill;
00071 QColor outline;
00072 
00073 QgsSvgCache* QgsSvgCache::mInstance = 0;
00074 
00075 QgsSvgCache* QgsSvgCache::instance()
00076 {
00077   if ( !mInstance )
00078   {
00079     mInstance = new QgsSvgCache();
00080   }
00081   return mInstance;
00082 }
00083 
00084 QgsSvgCache::QgsSvgCache(): mTotalSize( 0 ), mLeastRecentEntry( 0 ), mMostRecentEntry( 0 )
00085 {
00086 }
00087 
00088 QgsSvgCache::~QgsSvgCache()
00089 {
00090   QMultiHash< QString, QgsSvgCacheEntry* >::iterator it = mEntryLookup.begin();
00091   for ( ; it != mEntryLookup.end(); ++it )
00092   {
00093     delete it.value();
00094   }
00095 }
00096 
00097 
00098 const QImage& QgsSvgCache::svgAsImage( const QString& file, int size, const QColor& fill, const QColor& outline, double outlineWidth,
00099                                        double widthScaleFactor, double rasterScaleFactor )
00100 {
00101   QgsSvgCacheEntry* currentEntry = cacheEntry( file, size, fill, outline, outlineWidth, widthScaleFactor, rasterScaleFactor );
00102 
00103   //if current entry image is 0: cache image for entry
00104   //update stats for memory usage
00105   if ( !currentEntry->image )
00106   {
00107     cacheImage( currentEntry );
00108     trimToMaximumSize();
00109   }
00110 
00111   return *( currentEntry->image );
00112 }
00113 
00114 const QPicture& QgsSvgCache::svgAsPicture( const QString& file, int size, const QColor& fill, const QColor& outline, double outlineWidth,
00115     double widthScaleFactor, double rasterScaleFactor )
00116 {
00117   QgsSvgCacheEntry* currentEntry = cacheEntry( file, size, fill, outline, outlineWidth, widthScaleFactor, rasterScaleFactor );
00118 
00119   //if current entry image is 0: cache image for entry
00120   //update stats for memory usage
00121   if ( !currentEntry->picture )
00122   {
00123     cachePicture( currentEntry );
00124     trimToMaximumSize();
00125   }
00126 
00127   return *( currentEntry->picture );
00128 }
00129 
00130 QgsSvgCacheEntry* QgsSvgCache::insertSVG( const QString& file, int size, const QColor& fill, const QColor& outline, double outlineWidth,
00131     double widthScaleFactor, double rasterScaleFactor )
00132 {
00133   QgsSvgCacheEntry* entry = new QgsSvgCacheEntry( file, size, outlineWidth, widthScaleFactor, rasterScaleFactor, fill, outline );
00134 
00135   replaceParamsAndCacheSvg( entry );
00136 
00137   mEntryLookup.insert( file, entry );
00138 
00139   //insert to most recent place in entry list
00140   if ( !mMostRecentEntry ) //inserting first entry
00141   {
00142     mLeastRecentEntry = entry;
00143     mMostRecentEntry = entry;
00144     entry->previousEntry = 0;
00145     entry->nextEntry = 0;
00146   }
00147   else
00148   {
00149     entry->previousEntry = mMostRecentEntry;
00150     entry->nextEntry = 0;
00151     mMostRecentEntry->nextEntry = entry;
00152     mMostRecentEntry = entry;
00153   }
00154 
00155   trimToMaximumSize();
00156   return entry;
00157 }
00158 
00159 void QgsSvgCache::containsParams( const QString& path, bool& hasFillParam, QColor& defaultFillColor, bool& hasOutlineParam, QColor& defaultOutlineColor,
00160                                   bool& hasOutlineWidthParam, double& defaultOutlineWidth ) const
00161 {
00162   defaultFillColor = QColor( Qt::black );
00163   defaultOutlineColor = QColor( Qt::black );
00164   defaultOutlineWidth = 1.0;
00165 
00166   QFile svgFile( path );
00167   if ( !svgFile.open( QIODevice::ReadOnly ) )
00168   {
00169     return;
00170   }
00171 
00172   QDomDocument svgDoc;
00173   if ( !svgDoc.setContent( &svgFile ) )
00174   {
00175     return;
00176   }
00177 
00178   QDomElement docElem = svgDoc.documentElement();
00179   containsElemParams( docElem, hasFillParam, defaultFillColor, hasOutlineParam, defaultOutlineColor, hasOutlineWidthParam, defaultOutlineWidth );
00180 }
00181 
00182 void QgsSvgCache::replaceParamsAndCacheSvg( QgsSvgCacheEntry* entry )
00183 {
00184   if ( !entry )
00185   {
00186     return;
00187   }
00188 
00189   QFile svgFile( entry->file );
00190   if ( !svgFile.open( QIODevice::ReadOnly ) )
00191   {
00192     return;
00193   }
00194 
00195   QDomDocument svgDoc;
00196   if ( !svgDoc.setContent( &svgFile ) )
00197   {
00198     return;
00199   }
00200 
00201   //replace fill color, outline color, outline with in all nodes
00202   QDomElement docElem = svgDoc.documentElement();
00203   replaceElemParams( docElem, entry->fill, entry->outline, entry->outlineWidth );
00204 
00205   entry->svgContent = svgDoc.toByteArray();
00206   mTotalSize += entry->svgContent.size();
00207 }
00208 
00209 void QgsSvgCache::cacheImage( QgsSvgCacheEntry* entry )
00210 {
00211   if ( !entry )
00212   {
00213     return;
00214   }
00215 
00216   delete entry->image;
00217   entry->image = 0;
00218 
00219   int imageSize = entry->size;
00220   QImage* image = new QImage( imageSize, imageSize, QImage::Format_ARGB32_Premultiplied );
00221   image->fill( 0 ); // transparent background
00222 
00223   QPainter p( image );
00224   QSvgRenderer r( entry->svgContent );
00225   r.render( &p );
00226 
00227   entry->image = image;
00228   mTotalSize += ( image->width() * image->height() * 32 );
00229 }
00230 
00231 void QgsSvgCache::cachePicture( QgsSvgCacheEntry *entry )
00232 {
00233   if ( !entry )
00234   {
00235     return;
00236   }
00237 
00238   delete entry->picture;
00239   entry->picture = 0;
00240 
00241   //correct QPictures dpi correction
00242   QPicture* picture = new QPicture();
00243   double pictureSize = entry->size  /  25.4 / ( entry->rasterScaleFactor * entry->widthScaleFactor )  * picture->logicalDpiX();
00244   QRectF rect( QPointF( -pictureSize / 2.0, -pictureSize / 2.0 ), QSizeF( pictureSize, pictureSize ) );
00245 
00246 
00247   QSvgRenderer renderer( entry->svgContent );
00248   QPainter painter( picture );
00249   renderer.render( &painter, rect );
00250   entry->picture = picture;
00251   mTotalSize += entry->picture->size();
00252 }
00253 
00254 QgsSvgCacheEntry* QgsSvgCache::cacheEntry( const QString& file, int size, const QColor& fill, const QColor& outline, double outlineWidth,
00255     double widthScaleFactor, double rasterScaleFactor )
00256 {
00257   //search entries in mEntryLookup
00258   QgsSvgCacheEntry* currentEntry = 0;
00259   QList<QgsSvgCacheEntry*> entries = mEntryLookup.values( file );
00260 
00261   QList<QgsSvgCacheEntry*>::iterator entryIt = entries.begin();
00262   for ( ; entryIt != entries.end(); ++entryIt )
00263   {
00264     QgsSvgCacheEntry* cacheEntry = *entryIt;
00265     if ( cacheEntry->file == file && cacheEntry->size == size && cacheEntry->fill == fill && cacheEntry->outline == outline &&
00266          cacheEntry->outlineWidth == outlineWidth && cacheEntry->widthScaleFactor == widthScaleFactor && cacheEntry->rasterScaleFactor == rasterScaleFactor )
00267     {
00268       currentEntry = cacheEntry;
00269       break;
00270     }
00271   }
00272 
00273   //if not found: create new entry
00274   //cache and replace params in svg content
00275   if ( !currentEntry )
00276   {
00277     currentEntry = insertSVG( file, size, fill, outline, outlineWidth, widthScaleFactor, rasterScaleFactor );
00278   }
00279   else
00280   {
00281     takeEntryFromList( currentEntry );
00282     if ( !mMostRecentEntry ) //list is empty
00283     {
00284       mMostRecentEntry = currentEntry;
00285       mLeastRecentEntry = currentEntry;
00286     }
00287     else
00288     {
00289       mMostRecentEntry->nextEntry = currentEntry;
00290       currentEntry->previousEntry = mMostRecentEntry;
00291       currentEntry->nextEntry = 0;
00292       mMostRecentEntry = currentEntry;
00293     }
00294   }
00295 
00296   //debugging
00297   //printEntryList();
00298 
00299   return currentEntry;
00300 }
00301 
00302 void QgsSvgCache::replaceElemParams( QDomElement& elem, const QColor& fill, const QColor& outline, double outlineWidth )
00303 {
00304   if ( elem.isNull() )
00305   {
00306     return;
00307   }
00308 
00309   //go through attributes
00310   QDomNamedNodeMap attributes = elem.attributes();
00311   int nAttributes = attributes.count();
00312   for ( int i = 0; i < nAttributes; ++i )
00313   {
00314     QDomAttr attribute = attributes.item( i ).toAttr();
00315     //e.g. style="fill:param(fill);param(stroke)"
00316     if ( attribute.name().compare( "style", Qt::CaseInsensitive ) == 0 )
00317     {
00318       //entries separated by ';'
00319       QString newAttributeString;
00320 
00321       QStringList entryList = attribute.value().split( ';' );
00322       QStringList::const_iterator entryIt = entryList.constBegin();
00323       for ( ; entryIt != entryList.constEnd(); ++entryIt )
00324       {
00325         QStringList keyValueSplit = entryIt->split( ':' );
00326         if ( keyValueSplit.size() < 2 )
00327         {
00328           continue;
00329         }
00330         QString key = keyValueSplit.at( 0 );
00331         QString value = keyValueSplit.at( 1 );
00332         if ( value.startsWith( "param(fill" ) )
00333         {
00334           value = fill.name();
00335         }
00336         else if ( value.startsWith( "param(outline)" ) )
00337         {
00338           value = outline.name();
00339         }
00340         else if ( value.startsWith( "param(outline-width)" ) )
00341         {
00342           value = QString::number( outlineWidth );
00343         }
00344 
00345         if ( entryIt != entryList.constBegin() )
00346         {
00347           newAttributeString.append( ";" );
00348         }
00349         newAttributeString.append( key + ":" + value );
00350       }
00351       elem.setAttribute( attribute.name(), newAttributeString );
00352     }
00353     else
00354     {
00355       QString value = attribute.value();
00356       if ( value.startsWith( "param(fill)" ) )
00357       {
00358         elem.setAttribute( attribute.name(), fill.name() );
00359       }
00360       else if ( value.startsWith( "param(outline)" ) )
00361       {
00362         elem.setAttribute( attribute.name(), outline.name() );
00363       }
00364       else if ( value.startsWith( "param(outline-width)" ) )
00365       {
00366         elem.setAttribute( attribute.name(), QString::number( outlineWidth ) );
00367       }
00368     }
00369   }
00370 
00371   QDomNodeList childList = elem.childNodes();
00372   int nChildren = childList.count();
00373   for ( int i = 0; i < nChildren; ++i )
00374   {
00375     QDomElement childElem = childList.at( i ).toElement();
00376     replaceElemParams( childElem, fill, outline, outlineWidth );
00377   }
00378 }
00379 
00380 void QgsSvgCache::containsElemParams( const QDomElement& elem, bool& hasFillParam, QColor& defaultFill, bool& hasOutlineParam, QColor& defaultOutline,
00381                                       bool& hasOutlineWidthParam, double& defaultOutlineWidth ) const
00382 {
00383   if ( elem.isNull() )
00384   {
00385     return;
00386   }
00387 
00388   //we already have all the information, no need to go deeper
00389   if ( hasFillParam && hasOutlineParam && hasOutlineWidthParam )
00390   {
00391     return;
00392   }
00393 
00394   //check this elements attribute
00395   QDomNamedNodeMap attributes = elem.attributes();
00396   int nAttributes = attributes.count();
00397 
00398   QStringList valueSplit;
00399   for ( int i = 0; i < nAttributes; ++i )
00400   {
00401     QDomAttr attribute = attributes.item( i ).toAttr();
00402     if ( attribute.name().compare( "style", Qt::CaseInsensitive ) == 0 )
00403     {
00404       //entries separated by ';'
00405       QStringList entryList = attribute.value().split( ';' );
00406       QStringList::const_iterator entryIt = entryList.constBegin();
00407       for ( ; entryIt != entryList.constEnd(); ++entryIt )
00408       {
00409         QStringList keyValueSplit = entryIt->split( ':' );
00410         if ( keyValueSplit.size() < 2 )
00411         {
00412           continue;
00413         }
00414         QString key = keyValueSplit.at( 0 );
00415         QString value = keyValueSplit.at( 1 );
00416         valueSplit = value.split( " " );
00417         if ( !hasFillParam && value.startsWith( "param(fill)" ) )
00418         {
00419           hasFillParam = true;
00420           if ( valueSplit.size() > 1 )
00421           {
00422             defaultFill = QColor( valueSplit.at( 1 ) );
00423           }
00424         }
00425         else if ( !hasOutlineParam && value.startsWith( "param(outline)" ) )
00426         {
00427           hasOutlineParam = true;
00428           if ( valueSplit.size() > 1 )
00429           {
00430             defaultOutline = QColor( valueSplit.at( 1 ) );
00431           }
00432         }
00433         else if ( !hasOutlineWidthParam && value.startsWith( "param(outline-width)" ) )
00434         {
00435           hasOutlineWidthParam = true;
00436           if ( valueSplit.size() > 1 )
00437           {
00438             defaultOutlineWidth = valueSplit.at( 1 ).toDouble();
00439           }
00440         }
00441       }
00442     }
00443     else
00444     {
00445       QString value = attribute.value();
00446       valueSplit = value.split( " " );
00447       if ( !hasFillParam && value.startsWith( "param(fill)" ) )
00448       {
00449         hasFillParam = true;
00450         if ( valueSplit.size() > 1 )
00451         {
00452           defaultFill = QColor( valueSplit.at( 1 ) );
00453         }
00454       }
00455       else if ( !hasOutlineParam && value.startsWith( "param(outline)" ) )
00456       {
00457         hasOutlineParam = true;
00458         if ( valueSplit.size() > 1 )
00459         {
00460           defaultOutline = QColor( valueSplit.at( 1 ) );
00461         }
00462       }
00463       else if ( !hasOutlineWidthParam && value.startsWith( "param(outline-width)" ) )
00464       {
00465         hasOutlineWidthParam = true;
00466         if ( valueSplit.size() > 1 )
00467         {
00468           defaultOutlineWidth = valueSplit.at( 1 ).toDouble();
00469         }
00470       }
00471     }
00472   }
00473 
00474   //pass it further to child items
00475   QDomNodeList childList = elem.childNodes();
00476   int nChildren = childList.count();
00477   for ( int i = 0; i < nChildren; ++i )
00478   {
00479     QDomElement childElem = childList.at( i ).toElement();
00480     containsElemParams( childElem, hasFillParam, defaultFill, hasOutlineParam, defaultOutline, hasOutlineWidthParam, defaultOutlineWidth );
00481   }
00482 }
00483 
00484 void QgsSvgCache::removeCacheEntry( QString s, QgsSvgCacheEntry* entry )
00485 {
00486   delete entry;
00487   mEntryLookup.remove( s , entry );
00488 }
00489 
00490 void QgsSvgCache::printEntryList()
00491 {
00492   QgsDebugMsg( "****************svg cache entry list*************************" );
00493   QgsDebugMsg( "Cache size: " + QString::number( mTotalSize ) );
00494   QgsSvgCacheEntry* entry = mLeastRecentEntry;
00495   while ( entry )
00496   {
00497     QgsDebugMsg( "***Entry:" );
00498     QgsDebugMsg( "File:" + entry->file );
00499     QgsDebugMsg( "Size:" + QString::number( entry->size ) );
00500     QgsDebugMsg( "Width scale factor" + QString::number( entry->widthScaleFactor ) );
00501     QgsDebugMsg( "Raster scale factor" + QString::number( entry->rasterScaleFactor ) );
00502     entry = entry->nextEntry;
00503   }
00504 }
00505 
00506 void QgsSvgCache::trimToMaximumSize()
00507 {
00508   QgsSvgCacheEntry* entry = mLeastRecentEntry;
00509   while ( entry && ( mTotalSize > mMaximumSize ) )
00510   {
00511     QgsSvgCacheEntry* bkEntry = entry;
00512     entry = entry->nextEntry;
00513 
00514     takeEntryFromList( bkEntry );
00515     mEntryLookup.remove( bkEntry->file, bkEntry );
00516     mTotalSize -= bkEntry->dataSize();
00517     delete bkEntry;
00518   }
00519 }
00520 
00521 void QgsSvgCache::takeEntryFromList( QgsSvgCacheEntry* entry )
00522 {
00523   if ( !entry )
00524   {
00525     return;
00526   }
00527 
00528   if ( entry->previousEntry )
00529   {
00530     entry->previousEntry->nextEntry = entry->nextEntry;
00531   }
00532   else
00533   {
00534     mLeastRecentEntry = entry->nextEntry;
00535   }
00536   if ( entry->nextEntry )
00537   {
00538     entry->nextEntry->previousEntry = entry->previousEntry;
00539   }
00540   else
00541   {
00542     mMostRecentEntry = entry->previousEntry;
00543   }
00544 }
00545 
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines