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

KDE's Doxygen guidelines are available online.