28 #include <QApplication>
29 #include <QCoreApplication>
31 #include <QDomDocument>
32 #include <QDomElement>
38 #include <QNetworkReply>
39 #include <QNetworkRequest>
41 #include <QImageReader>
42 #include <QSvgRenderer>
43 #include <QTemporaryDir>
48 QgsImageCacheEntry::QgsImageCacheEntry(
const QString &path, QSize size,
const bool keepAspectRatio,
const double opacity,
double dpi,
int frameNumber )
51 , keepAspectRatio( keepAspectRatio )
54 , frameNumber( frameNumber )
60 const QgsImageCacheEntry *otherImage =
dynamic_cast< const QgsImageCacheEntry *
>( other );
63 || otherImage->keepAspectRatio != keepAspectRatio
64 || otherImage->frameNumber != frameNumber
65 || otherImage->size != size
66 || ( !size.isValid() && otherImage->targetDpi != targetDpi )
67 || otherImage->opacity != opacity
68 || otherImage->path != path )
74 int QgsImageCacheEntry::dataSize()
const
77 if ( !image.isNull() )
79 size += image.sizeInBytes();
84 void QgsImageCacheEntry::dump()
const
86 QgsDebugMsgLevel( QStringLiteral(
"path: %1, size %2x%3" ).arg( path ).arg( size.width() ).arg( size.height() ), 3 );
94 mTemporaryDir.reset(
new QTemporaryDir() );
96 const int bytes =
QgsSettings().
value( QStringLiteral(
"/qgis/maxImageCacheSize" ), 0 ).toInt();
106 if ( sysMemory >= 32000 )
108 else if ( sysMemory >= 16000 )
115 mMissingSvg = QStringLiteral(
"<svg width='10' height='10'><text x='5' y='10' font-size='10' text-anchor='middle'>?</text></svg>" ).toLatin1();
118 if ( QFile::exists( downloadingSvgPath ) )
120 QFile file( downloadingSvgPath );
121 if ( file.open( QIODevice::ReadOnly ) )
123 mFetchingSvg = file.readAll();
127 if ( mFetchingSvg.isEmpty() )
129 mFetchingSvg = QStringLiteral(
"<svg width='10' height='10'><text x='5' y='10' font-size='10' text-anchor='middle'>?</text></svg>" ).toLatin1();
137 QImage
QgsImageCache::pathAsImage(
const QString &f,
const QSize size,
const bool keepAspectRatio,
const double opacity,
bool &fitsInCache,
bool blocking,
double targetDpi,
int frameNumber,
bool *isMissing )
140 int nextFrameDelayMs = 0;
141 return pathAsImagePrivate( f, size, keepAspectRatio, opacity, fitsInCache, blocking, targetDpi, frameNumber, isMissing,
totalFrameCount, nextFrameDelayMs );
144 QImage QgsImageCache::pathAsImagePrivate(
const QString &f,
const QSize size,
const bool keepAspectRatio,
const double opacity,
bool &fitsInCache,
bool blocking,
double targetDpi,
int frameNumber,
bool *isMissing,
int &totalFrameCount,
int &nextFrameDelayMs )
146 QString file = f.trimmed();
150 if ( file.isEmpty() )
153 const QMutexLocker locker( &
mMutex );
155 const auto extractedAnimationIt = mExtractedAnimationPaths.constFind( file );
156 if ( extractedAnimationIt != mExtractedAnimationPaths.constEnd() )
158 file = QDir( extractedAnimationIt.value() ).filePath( QStringLiteral(
"frame_%1.png" ).arg( frameNumber ) );
164 QgsImageCacheEntry *currentEntry =
findExistingEntry(
new QgsImageCacheEntry( file, size, keepAspectRatio, opacity, targetDpi, frameNumber ) );
171 if ( currentEntry->image.isNull() )
173 long cachedDataSize = 0;
174 bool isBroken =
false;
175 result = renderImage( file, size, keepAspectRatio, opacity, targetDpi, frameNumber, isBroken,
totalFrameCount, nextFrameDelayMs, blocking );
176 cachedDataSize += result.sizeInBytes();
180 currentEntry->image = QImage();
185 currentEntry->image = result;
187 currentEntry->nextFrameDelay = nextFrameDelayMs;
191 *isMissing = isBroken;
192 currentEntry->isMissingImage = isBroken;
198 result = currentEntry->image;
200 nextFrameDelayMs = currentEntry->nextFrameDelay;
202 *isMissing = currentEntry->isMissingImage;
210 if ( path.isEmpty() )
214 if ( !path.startsWith( QLatin1String(
"base64:" ) ) && QFile::exists( path ) )
216 const QImageReader reader( path );
217 if ( reader.size().isValid() )
218 return reader.size();
220 return QImage( path ).size();
224 QByteArray ba =
getContent( path, QByteArray(
"broken" ), QByteArray(
"fetching" ), blocking );
226 if ( ba !=
"broken" && ba !=
"fetching" )
228 QBuffer buffer( &ba );
229 buffer.open( QIODevice::ReadOnly );
231 QImageReader reader( &buffer );
234 const QSize s = reader.size();
237 const QImage im = reader.read();
238 return im.isNull() ? QSize() : im.size();
246 const QString file = path.trimmed();
248 if ( file.isEmpty() )
251 const QMutexLocker locker( &
mMutex );
253 auto it = mTotalFrameCounts.find( path );
254 if ( it != mTotalFrameCounts.end() )
258 int nextFrameDelayMs = 0;
259 bool fitsInCache =
false;
260 bool isMissing =
false;
261 ( void )pathAsImagePrivate( file, QSize(),
true, 1.0, fitsInCache, blocking, 96, 0, &isMissing, res, nextFrameDelayMs );
268 const QString file = path.trimmed();
270 if ( file.isEmpty() )
273 const QMutexLocker locker( &
mMutex );
275 auto it = mImageDelays.find( path );
276 if ( it != mImageDelays.end() )
277 return it.value().value( currentFrame );
280 int nextFrameDelayMs = 0;
281 bool fitsInCache =
false;
282 bool isMissing =
false;
283 const QImage res = pathAsImagePrivate( file, QSize(),
true, 1.0, fitsInCache, blocking, 96, currentFrame, &isMissing, frameCount, nextFrameDelayMs );
285 return nextFrameDelayMs <= 0 || res.isNull() ? -1 : nextFrameDelayMs;
290 const QMutexLocker locker( &
mMutex );
292 auto it = mExtractedAnimationPaths.find( path );
293 if ( it != mExtractedAnimationPaths.end() )
297 std::unique_ptr< QImageReader > reader;
298 std::unique_ptr< QBuffer > buffer;
300 if ( !path.startsWith( QLatin1String(
"base64:" ) ) && QFile::exists( path ) )
302 const QString basePart = QFileInfo( path ).baseName();
304 filePath = mTemporaryDir->filePath( QStringLiteral(
"%1_%2" ).arg( basePart ).arg(
id ) );
305 while ( QFile::exists( filePath ) )
306 filePath = mTemporaryDir->filePath( QStringLiteral(
"%1_%2" ).arg( basePart ).arg( ++
id ) );
308 reader = std::make_unique< QImageReader >( path );
312 QByteArray ba =
getContent( path, QByteArray(
"broken" ), QByteArray(
"fetching" ),
false );
313 if ( ba ==
"broken" || ba ==
"fetching" )
319 const QString path = QUuid::createUuid().toString( QUuid::WithoutBraces );
320 filePath = mTemporaryDir->filePath( path );
322 buffer = std::make_unique< QBuffer >( &ba );
323 buffer->open( QIODevice::ReadOnly );
324 reader = std::make_unique< QImageReader> ( buffer.get() );
328 QDir().mkpath( filePath );
329 mExtractedAnimationPaths.insert( path, filePath );
331 const QDir frameDirectory( filePath );
334 reader->setAutoTransform(
true );
338 const QImage frame = reader->read();
339 if ( frame.isNull() )
342 mImageDelays[ path ].append( reader->nextImageDelay() );
344 const QString framePath = frameDirectory.filePath( QStringLiteral(
"frame_%1.png" ).arg( frameNumber++ ) );
345 frame.save( framePath,
"PNG" );
348 mTotalFrameCounts.insert( path, frameNumber );
351 QImage QgsImageCache::renderImage(
const QString &path, QSize size,
const bool keepAspectRatio,
const double opacity,
double targetDpi,
int frameNumber,
bool &isBroken,
int &totalFrameCount,
int &nextFrameDelayMs,
bool blocking )
const
357 if ( !path.startsWith( QLatin1String(
"base64:" ) ) && QFile::exists( path ) )
359 QImageReader reader( path );
360 reader.setAutoTransform(
true );
362 if ( reader.format() ==
"pdf" )
364 if ( !size.isEmpty() )
371 reader.setScaledSize( size );
376 const QSize sizeAt72Dpi = reader.size();
377 const QSize sizeAtTargetDpi = sizeAt72Dpi * targetDpi / 72;
378 reader.setScaledSize( sizeAtTargetDpi );
384 if ( frameNumber == -1 )
390 im = getFrameFromReader( reader, frameNumber );
392 nextFrameDelayMs = reader.nextImageDelay();
396 QByteArray ba =
getContent( path, QByteArray(
"broken" ), QByteArray(
"fetching" ), blocking );
398 if ( ba ==
"broken" )
403 if ( !size.isValid() )
407 if ( size.width() == 0 )
408 size.setWidth( size.height() );
409 if ( size.height() == 0 )
410 size.setHeight( size.width() );
412 im = QImage( size, QImage::Format_ARGB32_Premultiplied );
416 QSvgRenderer r( mMissingSvg );
418 QSizeF s( r.viewBox().size() );
419 s.scale( size.width(), size.height(), Qt::KeepAspectRatio );
420 const QRectF rect( ( size.width() - s.width() ) / 2, ( size.height() - s.height() ) / 2, s.width(), s.height() );
421 r.render( &p, rect );
423 else if ( ba ==
"fetching" )
426 if ( size.width() == 0 )
427 size.setWidth( size.height() );
428 if ( size.height() == 0 )
429 size.setHeight( size.width() );
432 im = QImage( size, QImage::Format_ARGB32_Premultiplied );
436 QSvgRenderer r( mFetchingSvg );
438 QSizeF s( r.viewBox().size() );
439 s.scale( size.width(), size.height(), Qt::KeepAspectRatio );
440 const QRectF rect( ( size.width() - s.width() ) / 2, ( size.height() - s.height() ) / 2, s.width(), s.height() );
441 r.render( &p, rect );
445 QBuffer buffer( &ba );
446 buffer.open( QIODevice::ReadOnly );
448 QImageReader reader( &buffer );
449 reader.setAutoTransform(
true );
451 if ( reader.format() ==
"pdf" )
453 if ( !size.isEmpty() )
460 reader.setScaledSize( size );
465 const QSize sizeAt72Dpi = reader.size();
466 const QSize sizeAtTargetDpi = sizeAt72Dpi * targetDpi / 72;
467 reader.setScaledSize( sizeAtTargetDpi );
472 if ( frameNumber == -1 )
478 im = getFrameFromReader( reader, frameNumber );
480 nextFrameDelayMs = reader.nextImageDelay();
484 if ( !im.hasAlphaChannel() )
485 im = im.convertToFormat( QImage::Format_ARGB32 );
491 if ( !size.isValid() || size.isNull() || im.size() == size )
494 else if ( keepAspectRatio && size.height() == 0 )
495 return im.scaledToWidth( size.width(), Qt::SmoothTransformation );
497 else if ( keepAspectRatio && size.width() == 0 )
498 return im.scaledToHeight( size.height(), Qt::SmoothTransformation );
500 return im.scaled( size, keepAspectRatio ? Qt::KeepAspectRatio : Qt::IgnoreAspectRatio, Qt::SmoothTransformation );
503 QImage QgsImageCache::getFrameFromReader( QImageReader &reader,
int frameNumber )
505 if ( reader.jumpToImage( frameNumber ) )
506 return reader.read();
509 for (
int frame = 0; frame < frameNumber; ++frame )
511 if ( reader.read().isNull() )
514 return reader.read();