LibKmahjongg

kmahjonggtileset.cpp
1 /*
2  SPDX-FileCopyrightText: 1997 Mathias Mueller <[email protected]>
3  SPDX-FileCopyrightText: 2006 Mauricio Piacentini <[email protected]>
4 
5  SPDX-License-Identifier: GPL-2.0-or-later
6 */
7 
8 // own
9 #include "kmahjonggtileset.h"
10 
11 // STL
12 #include <cstdlib>
13 
14 // Qt
15 #include <QFile>
16 #include <QGuiApplication>
17 #include <QImage>
18 #include <QMap>
19 #include <QPainter>
20 #include <QPixmapCache>
21 #include <QStandardPaths>
22 #include <QSvgRenderer>
23 
24 // KF
25 #include <KConfig>
26 #include <KConfigGroup>
27 #include <KLocalizedString>
28 
29 // LibKMahjongg
30 #include "libkmahjongg_debug.h"
31 
32 class KMahjonggTilesetMetricsData
33 {
34 public:
35  short lvloffx; // used for 3D indentation, x value
36  short lvloffy; // used for 3D indentation, y value
37  short w; // tile width ( +border +shadow)
38  short h; // tile height ( +border +shadow)
39  short fw; // face width
40  short fh; // face height
41 
42  KMahjonggTilesetMetricsData()
43  : lvloffx(0)
44  , lvloffy(0)
45  , w(0)
46  , h(0)
47  , fw(0)
48  , fh(0)
49  {
50  }
51 };
52 
53 class KMahjonggTilesetPrivate
54 {
55 public:
56  KMahjonggTilesetPrivate()
57  : isSVG(false)
58  , graphicsLoaded(false)
59  {
60  }
61  QList<QString> elementIdTable;
62  QMap<QString, QString> authorproperties;
63 
64  KMahjonggTilesetMetricsData originaldata;
65  KMahjonggTilesetMetricsData scaleddata;
66  QString filename; // cache the last file loaded to save reloading it
67  QString graphicspath;
68 
69  QSvgRenderer svg;
70  bool isSVG;
71  bool graphicsLoaded;
72 };
73 
74 // ---------------------------------------------------------
75 
76 KMahjonggTileset::KMahjonggTileset()
77  : d(new KMahjonggTilesetPrivate)
78 {
79  buildElementIdTable();
80 
81  static bool _inited = false;
82  if (_inited) {
83  return;
84  }
85  _inited = true;
86 }
87 
88 // ---------------------------------------------------------
89 
90 KMahjonggTileset::~KMahjonggTileset() = default;
91 
92 void KMahjonggTileset::updateScaleInfo(short tilew, short tileh)
93 {
94  d->scaleddata.w = tilew;
95  d->scaleddata.h = tileh;
96  double ratio = (static_cast<qreal>(d->scaleddata.w)) / (static_cast<qreal>(d->originaldata.w));
97  d->scaleddata.lvloffx = static_cast<short>(d->originaldata.lvloffx * ratio);
98  d->scaleddata.lvloffy = static_cast<short>(d->originaldata.lvloffy * ratio);
99  d->scaleddata.fw = static_cast<short>(d->originaldata.fw * ratio);
100  d->scaleddata.fh = static_cast<short>(d->originaldata.fh * ratio);
101 }
102 
103 QSize KMahjonggTileset::preferredTileSize(const QSize & boardsize, int horizontalCells, int verticalCells)
104 {
105  //calculate our best tile size to fit the boardsize passed to us
106  qreal newtilew, newtileh, aspectratio;
107  qreal bw = boardsize.width();
108  qreal bh = boardsize.height();
109 
110  //use tileface for calculation, with one complete tile in the sum for extra margin
111  qreal fullh = (d->originaldata.fh * verticalCells) + d->originaldata.h;
112  qreal fullw = (d->originaldata.fw * horizontalCells) + d->originaldata.w;
113  qreal floatw = d->originaldata.w;
114  qreal floath = d->originaldata.h;
115 
116  if ((fullw / fullh) > (bw / bh)) {
117  //space will be left on height, use width as limit
118  aspectratio = bw / fullw;
119  } else {
120  aspectratio = bh / fullh;
121  }
122  newtilew = aspectratio * floatw;
123  newtileh = aspectratio * floath;
124  return QSize(static_cast<short>(newtilew), static_cast<short>(newtileh));
125 }
126 
127 bool KMahjonggTileset::loadDefault()
128 {
129  QString idx = QStringLiteral("default.desktop");
130 
131  QString tilesetPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kmahjongglib/tilesets/") + idx);
132  qCDebug(LIBKMAHJONGG_LOG) << "Inside LoadDefault(), located path at" << tilesetPath;
133  if (tilesetPath.isEmpty()) {
134  return false;
135  }
136  return loadTileset(tilesetPath);
137 }
138 
139 QString KMahjonggTileset::authorProperty(const QString & key) const
140 {
141  return d->authorproperties[key];
142 }
143 
144 short KMahjonggTileset::width() const
145 {
146  return d->scaleddata.w;
147 }
148 
149 short KMahjonggTileset::height() const
150 {
151  return d->scaleddata.h;
152 }
153 
154 short KMahjonggTileset::levelOffsetX() const
155 {
156  return d->scaleddata.lvloffx;
157 }
158 
159 short KMahjonggTileset::levelOffsetY() const
160 {
161  return d->scaleddata.lvloffy;
162 }
163 
164 short KMahjonggTileset::qWidth() const
165 {
166  return static_cast<short>(d->scaleddata.fw / 2.0);
167 }
168 
169 short KMahjonggTileset::qHeight() const
170 {
171  return static_cast<short>(d->scaleddata.fh / 2.0);
172 }
173 
174 QString KMahjonggTileset::path() const
175 {
176  return d->filename;
177 }
178 
179 #define kTilesetVersionFormat 1
180 
181 // ---------------------------------------------------------
182 bool KMahjonggTileset::loadTileset(const QString & tilesetPath)
183 {
184  //qCDebug(LIBKMAHJONGG_LOG) << "Attempting to load .desktop at" << tilesetPath;
185 
186  //clear our properties map
187  d->authorproperties.clear();
188 
189  // verify if it is a valid file first and if we can open it
190  QFile tilesetfile(tilesetPath);
191  if (!tilesetfile.open(QIODevice::ReadOnly)) {
192  return false;
193  }
194  tilesetfile.close();
195 
196  KConfig tileconfig(tilesetPath, KConfig::SimpleConfig);
197  KConfigGroup group = tileconfig.group("KMahjonggTileset");
198 
199  d->authorproperties.insert(QStringLiteral("Name"), group.readEntry("Name")); // Returns translated data
200  d->authorproperties.insert(QStringLiteral("Author"), group.readEntry("Author"));
201  d->authorproperties.insert(QStringLiteral("Description"), group.readEntry("Description"));
202  d->authorproperties.insert(QStringLiteral("AuthorEmail"), group.readEntry("AuthorEmail"));
203 
204  //Version control
205  int tileversion = group.readEntry("VersionFormat", 0);
206  //Format is increased when we have incompatible changes, meaning that older clients are not able to use the remaining information safely
207  if (tileversion > kTilesetVersionFormat) {
208  return false;
209  }
210 
211  QString graphName = group.readEntry("FileName");
212 
213  d->graphicspath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kmahjongglib/tilesets/") + graphName);
214  //qCDebug(LIBKMAHJONGG_LOG) << "Using tileset at" << d->graphicspath;
215 
216  //only SVG for now
217  d->isSVG = true;
218  if (d->graphicspath.isEmpty()) {
219  return false;
220  }
221 
222  d->originaldata.w = group.readEntry("TileWidth", 30);
223  d->originaldata.h = group.readEntry("TileHeight", 50);
224  d->originaldata.fw = group.readEntry("TileFaceWidth", 30);
225  d->originaldata.fh = group.readEntry("TileFaceHeight", 50);
226  d->originaldata.lvloffx = group.readEntry("LevelOffsetX", 10);
227  d->originaldata.lvloffy = group.readEntry("LevelOffsetY", 10);
228 
229  //client application needs to call loadGraphics()
230  d->graphicsLoaded = false;
231  d->filename = tilesetPath;
232 
233  return true;
234 }
235 
236 // ---------------------------------------------------------
237 bool KMahjonggTileset::loadGraphics()
238 {
239  if (d->graphicsLoaded) {
240  return true;
241  }
242  if (d->isSVG) {
243  //really?
244  d->svg.load(d->graphicspath);
245  if (d->svg.isValid()) {
246  //invalidate our global cache
248  d->graphicsLoaded = true;
249  reloadTileset(QSize(d->originaldata.w, d->originaldata.h));
250  } else {
251  return false;
252  }
253  } else {
254  //TODO add support for png??
255  return false;
256  }
257 
258  return true;
259 }
260 
261 // ---------------------------------------------------------
262 bool KMahjonggTileset::reloadTileset(const QSize & newTilesize)
263 {
264  if (QSize(d->scaleddata.w, d->scaleddata.h) == newTilesize) {
265  return false;
266  }
267 
268  if (d->isSVG) {
269  if (d->svg.isValid()) {
270  updateScaleInfo(newTilesize.width(), newTilesize.height());
271  //rendering will be done when needed, automatically using the global cache
272  } else {
273  return false;
274  }
275  } else {
276  //TODO add support for png???
277  return false;
278  }
279 
280  return true;
281 }
282 
283 void KMahjonggTileset::buildElementIdTable()
284 {
285  //Build a list for faster lookup of element ids, mapped to the enumeration used by GameData and BoardWidget
286  //Unselected tiles
287  for (short idx = 1; idx <= 4; idx++) {
288  d->elementIdTable.append(QStringLiteral("TILE_%1").arg(idx));
289  }
290  //Selected tiles
291  for (short idx = 1; idx <= 4; idx++) {
292  d->elementIdTable.append(QStringLiteral("TILE_%1_SEL").arg(idx));
293  }
294  //now faces
295  for (short idx = 1; idx <= 9; idx++) {
296  d->elementIdTable.append(QStringLiteral("CHARACTER_%1").arg(idx));
297  }
298  for (short idx = 1; idx <= 9; idx++) {
299  d->elementIdTable.append(QStringLiteral("BAMBOO_%1").arg(idx));
300  }
301  for (short idx = 1; idx <= 9; idx++) {
302  d->elementIdTable.append(QStringLiteral("ROD_%1").arg(idx));
303  }
304  for (short idx = 1; idx <= 4; idx++) {
305  d->elementIdTable.append(QStringLiteral("SEASON_%1").arg(idx));
306  }
307  for (short idx = 1; idx <= 4; idx++) {
308  d->elementIdTable.append(QStringLiteral("WIND_%1").arg(idx));
309  }
310  for (short idx = 1; idx <= 3; idx++) {
311  d->elementIdTable.append(QStringLiteral("DRAGON_%1").arg(idx));
312  }
313  for (short idx = 1; idx <= 4; idx++) {
314  d->elementIdTable.append(QStringLiteral("FLOWER_%1").arg(idx));
315  }
316 }
317 
318 QString KMahjonggTileset::pixmapCacheNameFromElementId(const QString & elementid)
319 {
320  return authorProperty(QStringLiteral("Name")) + elementid + QStringLiteral("W%1H%2").arg(d->scaleddata.w).arg(d->scaleddata.h);
321 }
322 
323 QPixmap KMahjonggTileset::renderElement(short width, short height, const QString & elementid)
324 {
325  //qCDebug(LIBKMAHJONGG_LOG) << "render element" << elementid << width << height;
326  const qreal dpr = qApp->devicePixelRatio();
327  width = width * dpr;
328  height = height * dpr;
329  QImage qiRend(QSize(width, height), QImage::Format_ARGB32_Premultiplied);
330  qiRend.fill(0);
331 
332  if (d->svg.isValid()) {
333  QPainter p(&qiRend);
334  d->svg.render(&p, elementid);
335  }
336  qiRend.setDevicePixelRatio(dpr);
337  return QPixmap::fromImage(qiRend);
338 }
339 
340 QPixmap KMahjonggTileset::selectedTile(int num)
341 {
342  QPixmap pm;
343  QString elemId = d->elementIdTable.at(num + 4); //selected offset in our idtable;
344  if (!QPixmapCache::find(pixmapCacheNameFromElementId(elemId), &pm)) {
345  //use tile size
346  pm = renderElement(d->scaleddata.w, d->scaleddata.h, elemId);
347  QPixmapCache::insert(pixmapCacheNameFromElementId(elemId), pm);
348  }
349  return pm;
350 }
351 
352 QPixmap KMahjonggTileset::unselectedTile(int num)
353 {
354  QPixmap pm;
355  QString elemId = d->elementIdTable.at(num);
356  if (!QPixmapCache::find(pixmapCacheNameFromElementId(elemId), &pm)) {
357  //use tile size
358  pm = renderElement(d->scaleddata.w, d->scaleddata.h, elemId);
359  QPixmapCache::insert(pixmapCacheNameFromElementId(elemId), pm);
360  }
361  return pm;
362 }
363 
364 QPixmap KMahjonggTileset::tileface(int num)
365 {
366  QPixmap pm;
367  if ((num + 8) >= d->elementIdTable.count()) {
368  //qCDebug(LIBKMAHJONGG_LOG) << "Client asked for invalid tileface id";
369  return pm;
370  }
371 
372  QString elemId = d->elementIdTable.at(num + 8); //tileface offset in our idtable;
373  if (!QPixmapCache::find(pixmapCacheNameFromElementId(elemId), &pm)) {
374  //use face size
375  pm = renderElement(d->scaleddata.fw, d->scaleddata.fh, elemId);
376  QPixmapCache::insert(pixmapCacheNameFromElementId(elemId), pm);
377  }
378  return pm;
379 }
QString readEntry(const char *key, const char *aDefault=nullptr) const
QPixmap fromImage(const QImage &image, Qt::ImageConversionFlags flags)
Format_ARGB32_Premultiplied
qreal devicePixelRatio() const const
int width() const const
QString locate(QStandardPaths::StandardLocation type, const QString &fileName, QStandardPaths::LocateOptions options)
int height() const const
bool isEmpty() const const
KConfigGroup group(const char *group)
QPixmap * find(const QString &key)
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
bool insert(const QString &key, const QPixmap &pixmap)
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Mon May 8 2023 04:09:42 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.