Unity 8
ImageCache.cpp
1 /*
2  * Copyright (C) 2016 Canonical, Ltd.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; version 3.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program. If not, see <http://www.gnu.org/licenses/>.
15  */
16 
17 #include <QDateTime>
18 #include <QDebug>
19 #include <QDir>
20 #include <QUrl>
21 #include <QUrlQuery>
22 
23 #include "ImageCache.h"
24 
25 ImageCache::ImageCache()
26  : QQuickImageProvider(QQmlImageProviderBase::Image,
27  QQmlImageProviderBase::ForceAsynchronousImageLoading)
28 {
29 }
30 
31 QString ImageCache::imageCacheRoot()
32 {
33  QString xdgCache(qgetenv("XDG_CACHE_HOME"));
34  if (xdgCache.isEmpty()) {
35  xdgCache = QDir::homePath() + QStringLiteral("/.cache");
36  }
37 
38  return QDir::cleanPath(xdgCache) + QStringLiteral("/unity8/imagecache");
39 }
40 
41 QFileInfo ImageCache::imagePath(const QUrl &image)
42 {
43  QUrlQuery query(image);
44 
45  auto name = query.queryItemValue(QStringLiteral("name"));
46  if (name.isEmpty()) {
47  name = QStringLiteral("/paths") + image.toLocalFile();
48  } else {
49  name = QStringLiteral("/names/") + name;
50  }
51 
52  return QFileInfo(imageCacheRoot() + name);
53 }
54 
55 bool ImageCache::needsUpdate(const QUrl &image, const QFileInfo &cachePath, const QSize &imageSize, const QSize &requestedSize, QSize &finalSize)
56 {
57  if (!cachePath.exists())
58  return true;
59 
60  QFileInfo imageInfo(image.toLocalFile());
61  if (imageInfo.lastModified() > cachePath.lastModified())
62  return true;
63 
64  QSize cacheSize(QImageReader(cachePath.filePath()).size());
65  finalSize = calculateSize(imageSize, requestedSize);
66  if (finalSize.isValid() && cacheSize != finalSize)
67  return true;
68 
69  return false;
70 }
71 
72 QSize ImageCache::calculateSize(const QSize &imageSize, const QSize &requestedSize)
73 {
74  QSize finalSize(requestedSize);
75 
76  if (finalSize.width() == 0) {
77  finalSize.setWidth(imageSize.width() * (((double)finalSize.height()) / imageSize.height()));
78  } else if (finalSize.height() == 0) {
79  finalSize.setHeight(imageSize.height() * (((double)finalSize.width()) / imageSize.width()));
80  }
81 
82  return finalSize;
83 }
84 
85 QImage ImageCache::loadAndCacheImage(QImageReader &reader, const QFileInfo &cachePath, const QSize &finalSize)
86 {
87  reader.setQuality(100);
88  reader.setScaledSize(finalSize);
89  auto format = reader.format(); // can't get this after reading
90 
91  QImage loadedImage(reader.read());
92  if (loadedImage.isNull()) {
93  qWarning() << "ImageCache could not read image" << reader.fileName() << ":" << reader.errorString();
94  return QImage();
95  }
96 
97  cachePath.dir().mkpath(QStringLiteral("."));
98  loadedImage.save(cachePath.filePath(), format, 100);
99 
100  return loadedImage;
101 }
102 
103 QImage ImageCache::requestImage(const QString &id, QSize *size, const QSize &requestedSize)
104 {
105  QUrl image(id);
106  QImageReader imageReader(image.toLocalFile());
107  QSize imageSize(imageReader.size());
108  QImage result;
109 
110  // Early exit here, with no sourceSize, scaled-up sourceSize, or bad source image
111  if ((requestedSize.width() <= 0 && requestedSize.height() <= 0) ||
112  imageSize.isEmpty() ||
113  requestedSize.height() >= imageSize.height() ||
114  requestedSize.width() >= imageSize.width()) {
115  // We're only interested in scaling down, not up.
116  result = imageReader.read();
117  *size = result.size();
118  return result;
119  }
120 
121  auto cachePath = imagePath(image);
122  QSize finalSize;
123 
124  if (needsUpdate(image, cachePath, imageSize, requestedSize, finalSize)) {
125  if (finalSize.isEmpty()) {
126  finalSize = calculateSize(imageSize, requestedSize);
127  }
128  result = loadAndCacheImage(imageReader, cachePath, finalSize);
129  } else {
130  result = QImage(cachePath.filePath());
131  }
132 
133  *size = result.size();
134  return result;
135 }