KDEGames

kgamerenderer.cpp
1 /*
2  SPDX-FileCopyrightText: 2010-2012 Stefan Majewsky <[email protected]>
3 
4  SPDX-License-Identifier: LGPL-2.0-only
5 */
6 
7 #include "kgamerenderer.h"
8 #include "kgamerenderer_p.h"
9 
10 // own
11 #include "kgamerendererclient.h"
12 #include "colorproxy_p.h"
13 #include "kgtheme.h"
14 #include "kgthemeprovider.h"
15 // Qt
16 #include <QCoreApplication>
17 #include <QDateTime>
18 #include <QFileInfo>
19 #include <QScopedPointer>
20 #include <QPainter>
21 #include <QVariant>
22 
23 //TODO: automatically schedule pre-rendering of animation frames
24 //TODO: multithreaded SVG loading?
25 
26 static const QString cacheName(QByteArray theme)
27 {
29  //e.g. "themes/foobar.desktop" -> "themes/foobar"
30  if (theme.endsWith(QByteArray(".desktop")))
31  theme.chop(8); //8 = strlen(".desktop")
32  return QStringLiteral("kgamerenderer-%1-%2")
33  .arg(appName, QString::fromUtf8(theme));
34 }
35 
36 KGameRendererPrivate::KGameRendererPrivate(KgThemeProvider* provider, unsigned cacheSize, KGameRenderer* parent)
37  : m_parent(parent)
38  , m_provider(provider)
39  , m_currentTheme(nullptr) //will be loaded on first use
40  , m_frameSuffix(QStringLiteral("_%1"))
41  , m_sizePrefix(QStringLiteral("%1-%2-"))
42  , m_frameCountPrefix(QStringLiteral("fc-"))
43  , m_boundsPrefix(QStringLiteral("br-"))
44  //default cache size: 3 MiB = 3 << 20 bytes
45  , m_cacheSize((cacheSize == 0 ? 3 : cacheSize) << 20)
46  , m_strategies(KGameRenderer::UseDiskCache | KGameRenderer::UseRenderingThreads)
47  , m_frameBaseIndex(0)
48  , m_defaultPrimaryView(nullptr)
49  , m_rendererPool(&m_workerPool)
50  , m_imageCache(nullptr)
51 {
52  qRegisterMetaType<KGRInternal::Job*>();
53 
54 }
55 
56 KGameRenderer::KGameRenderer(KgThemeProvider* provider, unsigned cacheSize)
57  : d(new KGameRendererPrivate(provider, cacheSize, this))
58 {
59  if (!provider->parent())
60  {
61  provider->setParent(this);
62  }
64  this, [this](const KgTheme *theme) { d->_k_setTheme(theme); });
65 }
66 
67 static KgThemeProvider* providerForSingleTheme(KgTheme* theme, QObject* parent)
68 {
69  KgThemeProvider* prov = new KgThemeProvider(QByteArray(), parent);
70  prov->addTheme(theme);
71  return prov;
72 }
73 
74 KGameRenderer::KGameRenderer(KgTheme* theme, unsigned cacheSize)
75  : d(new KGameRendererPrivate(providerForSingleTheme(theme, this), cacheSize, this))
76 {
77 }
78 
80 {
81  //cleanup clients
82  while (!d->m_clients.isEmpty())
83  {
84  delete d->m_clients.constBegin().key();
85  }
86  //cleanup own stuff
87  d->m_workerPool.waitForDone();
88  delete d->m_imageCache;
89 }
90 
92 {
93  return d->m_defaultPrimaryView;
94 }
95 
97 {
98  d->m_defaultPrimaryView = view;
99 }
100 
102 {
103  return d->m_frameBaseIndex;
104 }
105 
107 {
108  d->m_frameBaseIndex = frameBaseIndex;
109 }
110 
112 {
113  return d->m_frameSuffix;
114 }
115 
117 {
118  d->m_frameSuffix = suffix.contains(QLatin1String("%1")) ? suffix : QStringLiteral("_%1");
119 }
120 
122 {
123  return d->m_strategies;
124 }
125 
127 {
128  const bool oldEnabled = d->m_strategies & strategy;
129  if (enabled)
130  {
131  d->m_strategies |= strategy;
132  }
133  else
134  {
135  d->m_strategies &= ~strategy;
136  }
137  if (strategy == KGameRenderer::UseDiskCache && oldEnabled != enabled)
138  {
139  //reload theme
140  const KgTheme* theme = d->m_currentTheme;
141  if (theme)
142  {
143  d->m_currentTheme = nullptr; //or setTheme() will return immediately
144  d->_k_setTheme(theme);
145  }
146  }
147 }
148 
149 void KGameRendererPrivate::_k_setTheme(const KgTheme* theme)
150 {
151  const KgTheme* oldTheme = m_currentTheme;
152  if (oldTheme == theme)
153  {
154  return;
155  }
156  qCDebug(GAMES_LIB) << "Setting theme:" << theme->name();
157  if (!setTheme(theme))
158  {
159  const KgTheme* defaultTheme = m_provider->defaultTheme();
160  if (theme != defaultTheme)
161  {
162  qCDebug(GAMES_LIB) << "Falling back to default theme:" << defaultTheme->name();
163  setTheme(defaultTheme);
164  m_provider->setCurrentTheme(defaultTheme);
165  }
166  }
167  //announce change to KGameRendererClients
168  QHash<KGameRendererClient*, QString>::iterator it1 = m_clients.begin(), it2 = m_clients.end();
169  for (; it1 != it2; ++it1)
170  {
171  it1.value().clear(); //because the pixmap is outdated
172  it1.key()->d->fetchPixmap();
173  }
174  Q_EMIT m_parent->themeChanged(m_currentTheme);
175 }
176 
177 bool KGameRendererPrivate::setTheme(const KgTheme* theme)
178 {
179  if (!theme)
180  {
181  return false;
182  }
183  //open cache (and SVG file, if necessary)
184  if (m_strategies & KGameRenderer::UseDiskCache)
185  {
186  QScopedPointer<KImageCache> oldCache(m_imageCache);
187  const QString imageCacheName = cacheName(theme->identifier());
188  m_imageCache = new KImageCache(imageCacheName, m_cacheSize);
189  m_imageCache->setPixmapCaching(false); //see big comment in KGRPrivate class declaration
190  //check timestamp of cache vs. last write access to theme/SVG
191  const uint svgTimestamp = qMax(
192  QFileInfo(theme->graphicsPath()).lastModified().toSecsSinceEpoch(),
193  theme->property("_k_themeDescTimestamp").value<qint64>()
194  );
195  QByteArray buffer;
196  if (!m_imageCache->find(QStringLiteral("kgr_timestamp"), &buffer))
197  buffer = "0";
198  const uint cacheTimestamp = buffer.toInt();
199  //try to instantiate renderer immediately if the cache does not exist or is outdated
200  //FIXME: This logic breaks if the cache evicts the "kgr_timestamp" key. We need additional API in KSharedDataCache to make sure that this key does not get evicted.
201  if (cacheTimestamp < svgTimestamp)
202  {
203  qCDebug(GAMES_LIB) << "Theme newer than cache, checking SVG";
204  QScopedPointer<QSvgRenderer> renderer(new QSvgRenderer(theme->graphicsPath()));
205  if (renderer->isValid())
206  {
207  m_rendererPool.setPath(theme->graphicsPath(), renderer.take());
208  m_imageCache->clear();
209  m_imageCache->insert(QStringLiteral("kgr_timestamp"), QByteArray::number(svgTimestamp));
210  }
211  else
212  {
213  //The SVG file is broken, so we deny to change the theme without
214  //breaking the previous theme.
215  delete m_imageCache;
216  KSharedDataCache::deleteCache(imageCacheName);
217  m_imageCache = oldCache.take();
218  qCDebug(GAMES_LIB) << "Theme change failed: SVG file broken";
219  return false;
220  }
221  }
222  //theme is cached - just delete the old renderer after making sure that no worker threads are using it anymore
223  else if (m_currentTheme != theme)
224  m_rendererPool.setPath(theme->graphicsPath());
225  }
226  else // !(m_strategies & KGameRenderer::UseDiskCache) -> no cache is used
227  {
228  //load SVG file
229  QScopedPointer<QSvgRenderer> renderer(new QSvgRenderer(theme->graphicsPath()));
230  if (renderer->isValid())
231  {
232  m_rendererPool.setPath(theme->graphicsPath(), renderer.take());
233  }
234  else
235  {
236  qCDebug(GAMES_LIB) << "Theme change failed: SVG file broken";
237  return false;
238  }
239  //disconnect from disk cache (only needed if changing strategy)
240  delete m_imageCache;
241  m_imageCache = nullptr;
242  }
243  //clear in-process caches
244  m_pixmapCache.clear();
245  m_frameCountCache.clear();
246  m_boundsCache.clear();
247  //done
248  m_currentTheme = theme;
249  return true;
250 }
251 
252 const KgTheme* KGameRenderer::theme() const
253 {
254  //ensure that some theme is loaded
255  if (!d->m_currentTheme)
256  {
257  d->_k_setTheme(d->m_provider->currentTheme());
258  }
259  return d->m_currentTheme;
260 }
261 
263 {
264  return d->m_provider;
265 }
266 
267 QString KGameRendererPrivate::spriteFrameKey(const QString& key, int frame, bool normalizeFrameNo) const
268 {
269  //fast path for non-animated sprites
270  if (frame < 0)
271  {
272  return key;
273  }
274  //normalize frame number
275  if (normalizeFrameNo)
276  {
277  const int frameCount = m_parent->frameCount(key);
278  if (frameCount <= 0)
279  {
280  //non-animated sprite
281  return key;
282  }
283  else
284  {
285  frame = (frame - m_frameBaseIndex) % frameCount + m_frameBaseIndex;
286  }
287  }
288  return key + m_frameSuffix.arg(frame);
289 }
290 
291 int KGameRenderer::frameCount(const QString& key) const
292 {
293  //ensure that some theme is loaded
294  if (!d->m_currentTheme)
295  {
296  d->_k_setTheme(d->m_provider->currentTheme());
297  }
298  //look up in in-process cache
299  QHash<QString, int>::const_iterator it = d->m_frameCountCache.constFind(key);
300  if (it != d->m_frameCountCache.constEnd())
301  {
302  return it.value();
303  }
304  //look up in shared cache (if SVG is not yet loaded)
305  int count = -1;
306  bool countFound = false;
307  const QString cacheKey = d->m_frameCountPrefix + key;
308  if (d->m_rendererPool.hasAvailableRenderers() && (d->m_strategies & KGameRenderer::UseDiskCache))
309  {
310  QByteArray buffer;
311  if (d->m_imageCache->find(cacheKey, &buffer))
312  {
313  count = buffer.toInt();
314  countFound = true;
315  }
316  }
317  //determine from SVG
318  if (!countFound)
319  {
320  QSvgRenderer* renderer = d->m_rendererPool.allocRenderer();
321  //look for animated sprite first
322  count = d->m_frameBaseIndex;
323  while (renderer->elementExists(d->spriteFrameKey(key, count, false)))
324  {
325  ++count;
326  }
327  count -= d->m_frameBaseIndex;
328  //look for non-animated sprite instead
329  if (count == 0)
330  {
331  if (!renderer->elementExists(key))
332  {
333  count = -1;
334  }
335  }
336  d->m_rendererPool.freeRenderer(renderer);
337  //save in shared cache for following requests
338  if (d->m_strategies & KGameRenderer::UseDiskCache)
339  {
340  d->m_imageCache->insert(cacheKey, QByteArray::number(count));
341  }
342  }
343  d->m_frameCountCache.insert(key, count);
344  return count;
345 }
346 
347 QRectF KGameRenderer::boundsOnSprite(const QString& key, int frame) const
348 {
349  const QString elementKey = d->spriteFrameKey(key, frame);
350  //ensure that some theme is loaded
351  if (!d->m_currentTheme)
352  {
353  d->_k_setTheme(d->m_provider->currentTheme());
354  }
355  //look up in in-process cache
356  QHash<QString, QRectF>::const_iterator it = d->m_boundsCache.constFind(elementKey);
357  if (it != d->m_boundsCache.constEnd())
358  {
359  return it.value();
360  }
361  //look up in shared cache (if SVG is not yet loaded)
362  QRectF bounds;
363  bool boundsFound = false;
364  const QString cacheKey = d->m_boundsPrefix + elementKey;
365  if (!d->m_rendererPool.hasAvailableRenderers() && (d->m_strategies & KGameRenderer::UseDiskCache))
366  {
367  QByteArray buffer;
368  if (d->m_imageCache->find(cacheKey, &buffer))
369  {
370  QDataStream stream(buffer);
371  stream >> bounds;
372  boundsFound = true;
373  }
374  }
375  //determine from SVG
376  if (!boundsFound)
377  {
378  QSvgRenderer* renderer = d->m_rendererPool.allocRenderer();
379  bounds = renderer->boundsOnElement(elementKey);
380  d->m_rendererPool.freeRenderer(renderer);
381  //save in shared cache for following requests
382  if (d->m_strategies & KGameRenderer::UseDiskCache)
383  {
384  QByteArray buffer;
385  {
386  QDataStream stream(&buffer, QIODevice::WriteOnly);
387  stream << bounds;
388  }
389  d->m_imageCache->insert(cacheKey, buffer);
390  }
391  }
392  d->m_boundsCache.insert(elementKey, bounds);
393  return bounds;
394 }
395 
396 bool KGameRenderer::spriteExists(const QString& key) const
397 {
398  return this->frameCount(key) >= 0;
399 }
400 
401 QPixmap KGameRenderer::spritePixmap(const QString& key, const QSize& size, int frame, const QHash<QColor, QColor>& customColors) const
402 {
403  QPixmap result;
404  d->requestPixmap(KGRInternal::ClientSpec(key, frame, size, customColors), nullptr, &result);
405  return result;
406 }
407 
408 //Helper function for KGameRendererPrivate::requestPixmap.
409 void KGameRendererPrivate::requestPixmap__propagateResult(const QPixmap& pixmap, KGameRendererClient* client, QPixmap* synchronousResult)
410 {
411  if (client)
412  {
413  client->receivePixmap(pixmap);
414  }
415  if (synchronousResult)
416  {
417  *synchronousResult = pixmap;
418  }
419 }
420 
421 void KGameRendererPrivate::requestPixmap(const KGRInternal::ClientSpec& spec, KGameRendererClient* client, QPixmap* synchronousResult)
422 {
423  //NOTE: If client == 0, the request is synchronous and must be finished when this method returns. This behavior is used by KGR::spritePixmap(). Instead of KGameRendererClient::receivePixmap, the QPixmap* argument is then used to return the result.
424  //parse request
425  if (spec.size.isEmpty())
426  {
427  requestPixmap__propagateResult(QPixmap(), client, synchronousResult);
428  return;
429  }
430  const QString elementKey = spriteFrameKey(spec.spriteKey, spec.frame);
431  QString cacheKey = m_sizePrefix.arg(spec.size.width()).arg(spec.size.height()) + elementKey;
432  QHash<QColor, QColor>::const_iterator it1 = spec.customColors.constBegin(), it2 = spec.customColors.constEnd();
433  static const QString colorSuffix(QStringLiteral( "-%1-%2" ));
434  for (; it1 != it2; ++it1)
435  {
436  cacheKey += colorSuffix.arg(it1.key().rgba()).arg(it1.value().rgba());
437  }
438  //check if update is needed
439  if (client)
440  {
441  if (m_clients.value(client) == cacheKey)
442  {
443  return;
444  }
445  m_clients[client] = cacheKey;
446  }
447  //ensure that some theme is loaded
448  if (!m_currentTheme)
449  {
450  _k_setTheme(m_provider->currentTheme());
451  }
452  //try to serve from high-speed cache
453  QHash<QString, QPixmap>::const_iterator it = m_pixmapCache.constFind(cacheKey);
454  if (it != m_pixmapCache.constEnd())
455  {
456  requestPixmap__propagateResult(it.value(), client, synchronousResult);
457  return;
458  }
459  //try to serve from low-speed cache
460  if (m_strategies & KGameRenderer::UseDiskCache)
461  {
462  QPixmap pix;
463  if (m_imageCache->findPixmap(cacheKey, &pix))
464  {
465  m_pixmapCache.insert(cacheKey, pix);
466  requestPixmap__propagateResult(pix, client, synchronousResult);
467  return;
468  }
469  }
470  //if asynchronous request, is such a rendering job already running?
471  if (client && m_pendingRequests.contains(cacheKey))
472  {
473  return;
474  }
475  //create job
476  KGRInternal::Job* job = new KGRInternal::Job;
477  job->rendererPool = &m_rendererPool;
478  job->cacheKey = cacheKey;
479  job->elementKey = elementKey;
480  job->spec = spec;
481  const bool synchronous = !client;
482  if (synchronous || !(m_strategies & KGameRenderer::UseRenderingThreads))
483  {
484  KGRInternal::Worker worker(job, true, this);
485  worker.run();
486  //if everything worked fine, result is in high-speed cache now
487  const QPixmap result = m_pixmapCache.value(cacheKey);
488  requestPixmap__propagateResult(result, client, synchronousResult);
489  }
490  else
491  {
492  m_workerPool.start(new KGRInternal::Worker(job, !client, this));
493  m_pendingRequests << cacheKey;
494  }
495 }
496 
497 void KGameRendererPrivate::jobFinished(KGRInternal::Job* job, bool isSynchronous)
498 {
499  //read job
500  const QString cacheKey = job->cacheKey;
501  const QImage result = job->result;
502  delete job;
503  //check who wanted this pixmap
504  m_pendingRequests.removeAll(cacheKey);
505  const QList<KGameRendererClient*> requesters = m_clients.keys(cacheKey);
506  //put result into image cache
507  if (m_strategies & KGameRenderer::UseDiskCache)
508  {
509  m_imageCache->insertImage(cacheKey, result);
510  //convert result to pixmap (and put into pixmap cache) only if it is needed now
511  //This optimization saves the image-pixmap conversion for intermediate sizes which occur during smooth resize events or window initializations.
512  if (!isSynchronous && requesters.isEmpty())
513  {
514  return;
515  }
516  }
517  const QPixmap pixmap = QPixmap::fromImage(result);
518  m_pixmapCache.insert(cacheKey, pixmap);
519  for (KGameRendererClient* requester : requesters) {
520  requester->receivePixmap(pixmap);
521  }
522 }
523 
524 //BEGIN KGRInternal::Job/Worker
525 
526 KGRInternal::Worker::Worker(KGRInternal::Job* job, bool isSynchronous, KGameRendererPrivate* parent)
527  : m_job(job)
528  , m_synchronous(isSynchronous)
529  , m_parent(parent)
530 {
531 }
532 
533 static const uint transparentRgba = QColor(Qt::transparent).rgba();
534 
535 void KGRInternal::Worker::run()
536 {
537  QImage image(m_job->spec.size, QImage::Format_ARGB32_Premultiplied);
538  image.fill(transparentRgba);
539  QPainter* painter = nullptr;
540  QPaintDeviceColorProxy* proxy = nullptr;
541  //if no custom colors requested, paint directly onto image
542  if (m_job->spec.customColors.isEmpty())
543  {
544  painter = new QPainter(&image);
545  }
546  else
547  {
548  proxy = new QPaintDeviceColorProxy(&image, m_job->spec.customColors);
549  painter = new QPainter(proxy);
550  }
551 
552  //do renderering
553  QSvgRenderer* renderer = m_job->rendererPool->allocRenderer();
554  renderer->render(painter, m_job->elementKey);
555  m_job->rendererPool->freeRenderer(renderer);
556  delete painter;
557  delete proxy;
558 
559  //talk back to the main thread
560  m_job->result = image;
562  m_parent, "jobFinished", Qt::AutoConnection,
563  Q_ARG(KGRInternal::Job*, m_job), Q_ARG(bool, m_synchronous)
564  );
565  //NOTE: KGR::spritePixmap relies on Qt::DirectConnection when this method is run in the main thread.
566 }
567 
568 //END KGRInternal::Job/Worker
569 
570 //BEGIN KGRInternal::RendererPool
571 
572 KGRInternal::RendererPool::RendererPool(QThreadPool* threadPool)
573  : m_valid(Checked_Invalid) //don't try to allocate renderers until given a valid SVG file
574  , m_threadPool(threadPool)
575 {
576 }
577 
578 KGRInternal::RendererPool::~RendererPool()
579 {
580  //This deletes all renderers.
581  setPath(QString());
582 }
583 
584 void KGRInternal::RendererPool::setPath(const QString& graphicsPath, QSvgRenderer* renderer)
585 {
586  QMutexLocker locker(&m_mutex);
587  //delete all renderers
588  m_threadPool->waitForDone();
589  QHash<QSvgRenderer*, QThread*>::const_iterator it1 = m_hash.constBegin(), it2 = m_hash.constEnd();
590  for (; it1 != it2; ++it1)
591  {
592  Q_ASSERT(it1.value() == nullptr); //nobody may be using our renderers anymore now
593  delete it1.key();
594  }
595  m_hash.clear();
596  //set path
597  m_path = graphicsPath;
598  //existence of a renderer instance is evidence for the validity of the SVG file
599  if (renderer)
600  {
601  m_valid = Checked_Valid;
602  m_hash.insert(renderer, nullptr);
603  }
604  else
605  {
606  m_valid = Unchecked;
607  }
608 }
609 
610 bool KGRInternal::RendererPool::hasAvailableRenderers() const
611 {
612  //look for a renderer which is not associated with a thread
613  QMutexLocker locker(&m_mutex);
614  return m_hash.key(nullptr) != nullptr;
615 }
616 
617 QSvgRenderer* KGRInternal::RendererPool::allocRenderer()
618 {
620  //look for an available renderer
621  QMutexLocker locker(&m_mutex);
622  QSvgRenderer* renderer = m_hash.key(nullptr);
623  if (!renderer)
624  {
625  //instantiate a new renderer (only if the SVG file has not been found to be invalid yet)
626  if (m_valid == Checked_Invalid)
627  {
628  return nullptr;
629  }
630  renderer = new QSvgRenderer(m_path);
631  m_valid = renderer->isValid() ? Checked_Valid : Checked_Invalid;
632  }
633  //mark renderer as used
634  m_hash.insert(renderer, thread);
635  return renderer;
636 }
637 
638 void KGRInternal::RendererPool::freeRenderer(QSvgRenderer* renderer)
639 {
640  //mark renderer as available
641  QMutexLocker locker(&m_mutex);
642  m_hash.insert(renderer, nullptr);
643 }
644 
645 //END KGRInternal::RendererPool
646 
647 #include "moc_kgamerenderer.cpp"
648 #include "moc_kgamerenderer_p.cpp"
int toInt(bool *ok, int base) const const
Format_ARGB32_Premultiplied
void render(QPainter *painter)
A theme provider manages KgTheme instances, and maintains a selection of the currentTheme().
bool isValid() const const
void chop(int n)
QRectF boundsOnSprite(const QString &key, int frame=-1) const
void addTheme(KgTheme *theme)
Adds a theme to this instance.
T value() const const
QPixmap fromImage(const QImage &image, Qt::ImageConversionFlags flags)
QThread * thread() const const
int frameCount(const QString &key) const
void setStrategyEnabled(Strategy strategy, bool enabled=true)
Enables/disables an optimization strategy for this renderer.
static void deleteCache(const QString &cacheName)
KgThemeProvider * themeProvider() const
QString fromUtf8(const char *str, int size)
QString & insert(int position, QChar ch)
QVariant property(const char *name) const const
virtual void receivePixmap(const QPixmap &pixmap)=0
This method is called when the KGameRenderer has provided a new pixmap for this client (esp...
void fill(uint pixelValue)
void setDefaultPrimaryView(QGraphicsView *view)
Set the primary view which will be used by newly created KGameRenderedItem instances associated with ...
QGraphicsView * defaultPrimaryView() const
Cache-enabled rendering of SVG themes.
Definition: kgamerenderer.h:86
bool isEmpty() const const
QByteArray number(int n, int base)
Strategies strategies() const
const KgTheme * theme() const
QCoreApplication * instance()
~KGameRenderer() override
Deletes this KGameRenderer instance, as well as all clients using it.
void setParent(QObject *parent)
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType type, QGenericReturnArgument ret, QGenericArgument val0, QGenericArgument val1, QGenericArgument val2, QGenericArgument val3, QGenericArgument val4, QGenericArgument val5, QGenericArgument val6, QGenericArgument val7, QGenericArgument val8, QGenericArgument val9)
An object that receives pixmaps from a KGameRenderer.
QRectF boundsOnElement(const QString &id) const const
Strategy
Describes the various strategies which KGameRenderer can use to speed up rendering.
Definition: kgamerenderer.h:95
void setFrameSuffix(const QString &suffix)
Sets the frame suffix.
void setFrameBaseIndex(int frameBaseIndex)
Sets the frame base index, i.e.
QThread * currentThread()
bool elementExists(const QString &id) const const
If set, pixmaps will be cached in a shared disk cache (using KSharedDataCache).
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
bool spriteExists(const QString &key) const
If set, pixmap requests from KGameRendererClients will be handled asynchronously if possible...
KGameRenderer(KgThemeProvider *prov, unsigned cacheSize=0)
Constructs a new KGameRenderer that renders prov->currentTheme().
A theme describes the visual appearance of a game.
Definition: kgtheme.h:58
void currentThemeChanged(const KgTheme *theme)
Emitted when the current theme changes.
QPixmap spritePixmap(const QString &key, const QSize &size, int frame=-1, const QHash< QColor, QColor > &customColors=(QHash< QColor, QColor >())) const
transparent
AutoConnection
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QObject * parent() const const
bool endsWith(const QByteArray &ba) const const
Q_EMITQ_EMIT
QString frameSuffix() const
QRgb rgba() const const
int frameBaseIndex() const
QCA_EXPORT QString appName()
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Tue Dec 7 2021 22:34:14 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.