Quantum GIS API Documentation
1.8
|
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