File indexing completed on 2022-09-27 13:20:31

0001 /*
0002     SPDX-FileCopyrightText: 1997 Mathias Mueller <in5y158@public.uni-hamburg.de>
0003     SPDX-FileCopyrightText: 2006 Mauricio Piacentini <mauricio@tabuleiro.com>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 // own
0009 #include "kmahjonggtileset.h"
0010 
0011 // STL
0012 #include <cstdlib>
0013 
0014 // Qt
0015 #include <QFile>
0016 #include <QGuiApplication>
0017 #include <QImage>
0018 #include <QMap>
0019 #include <QPainter>
0020 #include <QPixmapCache>
0021 #include <QStandardPaths>
0022 #include <QSvgRenderer>
0023 
0024 // KF
0025 #include <KConfig>
0026 #include <KConfigGroup>
0027 #include <KLocalizedString>
0028 
0029 // LibKMahjongg
0030 #include "libkmahjongg_debug.h"
0031 
0032 class KMahjonggTilesetMetricsData
0033 {
0034 public:
0035     short lvloffx; // used for 3D indentation, x value
0036     short lvloffy; // used for 3D indentation, y value
0037     short w; // tile width ( +border +shadow)
0038     short h; // tile height ( +border +shadow)
0039     short fw; // face width
0040     short fh; // face height
0041 
0042     KMahjonggTilesetMetricsData()
0043         : lvloffx(0)
0044         , lvloffy(0)
0045         , w(0)
0046         , h(0)
0047         , fw(0)
0048         , fh(0)
0049     {
0050     }
0051 };
0052 
0053 class KMahjonggTilesetPrivate
0054 {
0055 public:
0056     KMahjonggTilesetPrivate()
0057         : isSVG(false)
0058         , graphicsLoaded(false)
0059     {
0060     }
0061     QList<QString> elementIdTable;
0062     QMap<QString, QString> authorproperties;
0063 
0064     KMahjonggTilesetMetricsData originaldata;
0065     KMahjonggTilesetMetricsData scaleddata;
0066     QString filename; // cache the last file loaded to save reloading it
0067     QString graphicspath;
0068 
0069     QSvgRenderer svg;
0070     bool isSVG;
0071     bool graphicsLoaded;
0072 };
0073 
0074 // ---------------------------------------------------------
0075 
0076 KMahjonggTileset::KMahjonggTileset()
0077     : d(new KMahjonggTilesetPrivate)
0078 {
0079     buildElementIdTable();
0080 
0081     static bool _inited = false;
0082     if (_inited) {
0083         return;
0084     }
0085     _inited = true;
0086 }
0087 
0088 // ---------------------------------------------------------
0089 
0090 KMahjonggTileset::~KMahjonggTileset() = default;
0091 
0092 void KMahjonggTileset::updateScaleInfo(short tilew, short tileh)
0093 {
0094     d->scaleddata.w = tilew;
0095     d->scaleddata.h = tileh;
0096     double ratio = (static_cast<qreal>(d->scaleddata.w)) / (static_cast<qreal>(d->originaldata.w));
0097     d->scaleddata.lvloffx = static_cast<short>(d->originaldata.lvloffx * ratio);
0098     d->scaleddata.lvloffy = static_cast<short>(d->originaldata.lvloffy * ratio);
0099     d->scaleddata.fw = static_cast<short>(d->originaldata.fw * ratio);
0100     d->scaleddata.fh = static_cast<short>(d->originaldata.fh * ratio);
0101 }
0102 
0103 QSize KMahjonggTileset::preferredTileSize(const QSize & boardsize, int horizontalCells, int verticalCells)
0104 {
0105     //calculate our best tile size to fit the boardsize passed to us
0106     qreal newtilew, newtileh, aspectratio;
0107     qreal bw = boardsize.width();
0108     qreal bh = boardsize.height();
0109 
0110     //use tileface for calculation, with one complete tile in the sum for extra margin
0111     qreal fullh = (d->originaldata.fh * verticalCells) + d->originaldata.h;
0112     qreal fullw = (d->originaldata.fw * horizontalCells) + d->originaldata.w;
0113     qreal floatw = d->originaldata.w;
0114     qreal floath = d->originaldata.h;
0115 
0116     if ((fullw / fullh) > (bw / bh)) {
0117         //space will be left on height, use width as limit
0118         aspectratio = bw / fullw;
0119     } else {
0120         aspectratio = bh / fullh;
0121     }
0122     newtilew = aspectratio * floatw;
0123     newtileh = aspectratio * floath;
0124     return QSize(static_cast<short>(newtilew), static_cast<short>(newtileh));
0125 }
0126 
0127 bool KMahjonggTileset::loadDefault()
0128 {
0129     QString idx = QStringLiteral("default.desktop");
0130 
0131     QString tilesetPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kmahjongglib/tilesets/") + idx);
0132     qCDebug(LIBKMAHJONGG_LOG) << "Inside LoadDefault(), located path at" << tilesetPath;
0133     if (tilesetPath.isEmpty()) {
0134         return false;
0135     }
0136     return loadTileset(tilesetPath);
0137 }
0138 
0139 QString KMahjonggTileset::authorProperty(const QString & key) const
0140 {
0141     return d->authorproperties[key];
0142 }
0143 
0144 short KMahjonggTileset::width() const
0145 {
0146     return d->scaleddata.w;
0147 }
0148 
0149 short KMahjonggTileset::height() const
0150 {
0151     return d->scaleddata.h;
0152 }
0153 
0154 short KMahjonggTileset::levelOffsetX() const
0155 {
0156     return d->scaleddata.lvloffx;
0157 }
0158 
0159 short KMahjonggTileset::levelOffsetY() const
0160 {
0161     return d->scaleddata.lvloffy;
0162 }
0163 
0164 short KMahjonggTileset::qWidth() const
0165 {
0166     return static_cast<short>(d->scaleddata.fw / 2.0);
0167 }
0168 
0169 short KMahjonggTileset::qHeight() const
0170 {
0171     return static_cast<short>(d->scaleddata.fh / 2.0);
0172 }
0173 
0174 QString KMahjonggTileset::path() const
0175 {
0176     return d->filename;
0177 }
0178 
0179 #define kTilesetVersionFormat 1
0180 
0181 // ---------------------------------------------------------
0182 bool KMahjonggTileset::loadTileset(const QString & tilesetPath)
0183 {
0184     //qCDebug(LIBKMAHJONGG_LOG) << "Attempting to load .desktop at" << tilesetPath;
0185 
0186     //clear our properties map
0187     d->authorproperties.clear();
0188 
0189     // verify if it is a valid file first and if we can open it
0190     QFile tilesetfile(tilesetPath);
0191     if (!tilesetfile.open(QIODevice::ReadOnly)) {
0192         return false;
0193     }
0194     tilesetfile.close();
0195 
0196     KConfig tileconfig(tilesetPath, KConfig::SimpleConfig);
0197     KConfigGroup group = tileconfig.group("KMahjonggTileset");
0198 
0199     d->authorproperties.insert(QStringLiteral("Name"), group.readEntry("Name")); // Returns translated data
0200     d->authorproperties.insert(QStringLiteral("Author"), group.readEntry("Author"));
0201     d->authorproperties.insert(QStringLiteral("Description"), group.readEntry("Description"));
0202     d->authorproperties.insert(QStringLiteral("AuthorEmail"), group.readEntry("AuthorEmail"));
0203 
0204     //Version control
0205     int tileversion = group.readEntry("VersionFormat", 0);
0206     //Format is increased when we have incompatible changes, meaning that older clients are not able to use the remaining information safely
0207     if (tileversion > kTilesetVersionFormat) {
0208         return false;
0209     }
0210 
0211     QString graphName = group.readEntry("FileName");
0212 
0213     d->graphicspath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kmahjongglib/tilesets/") + graphName);
0214     //qCDebug(LIBKMAHJONGG_LOG) << "Using tileset at" << d->graphicspath;
0215 
0216     //only SVG for now
0217     d->isSVG = true;
0218     if (d->graphicspath.isEmpty()) {
0219         return false;
0220     }
0221 
0222     d->originaldata.w = group.readEntry("TileWidth", 30);
0223     d->originaldata.h = group.readEntry("TileHeight", 50);
0224     d->originaldata.fw = group.readEntry("TileFaceWidth", 30);
0225     d->originaldata.fh = group.readEntry("TileFaceHeight", 50);
0226     d->originaldata.lvloffx = group.readEntry("LevelOffsetX", 10);
0227     d->originaldata.lvloffy = group.readEntry("LevelOffsetY", 10);
0228 
0229     //client application needs to call loadGraphics()
0230     d->graphicsLoaded = false;
0231     d->filename = tilesetPath;
0232 
0233     return true;
0234 }
0235 
0236 // ---------------------------------------------------------
0237 bool KMahjonggTileset::loadGraphics()
0238 {
0239     if (d->graphicsLoaded) {
0240         return true;
0241     }
0242     if (d->isSVG) {
0243         //really?
0244         d->svg.load(d->graphicspath);
0245         if (d->svg.isValid()) {
0246             //invalidate our global cache
0247             QPixmapCache::clear();
0248             d->graphicsLoaded = true;
0249             reloadTileset(QSize(d->originaldata.w, d->originaldata.h));
0250         } else {
0251             return false;
0252         }
0253     } else {
0254         //TODO add support for png??
0255         return false;
0256     }
0257 
0258     return true;
0259 }
0260 
0261 // ---------------------------------------------------------
0262 bool KMahjonggTileset::reloadTileset(const QSize & newTilesize)
0263 {
0264     if (QSize(d->scaleddata.w, d->scaleddata.h) == newTilesize) {
0265         return false;
0266     }
0267 
0268     if (d->isSVG) {
0269         if (d->svg.isValid()) {
0270             updateScaleInfo(newTilesize.width(), newTilesize.height());
0271             //rendering will be done when needed, automatically using the global cache
0272         } else {
0273             return false;
0274         }
0275     } else {
0276         //TODO add support for png???
0277         return false;
0278     }
0279 
0280     return true;
0281 }
0282 
0283 void KMahjonggTileset::buildElementIdTable()
0284 {
0285     //Build a list for faster lookup of element ids, mapped to the enumeration used by GameData and BoardWidget
0286     //Unselected tiles
0287     for (short idx = 1; idx <= 4; idx++) {
0288         d->elementIdTable.append(QStringLiteral("TILE_%1").arg(idx));
0289     }
0290     //Selected tiles
0291     for (short idx = 1; idx <= 4; idx++) {
0292         d->elementIdTable.append(QStringLiteral("TILE_%1_SEL").arg(idx));
0293     }
0294     //now faces
0295     for (short idx = 1; idx <= 9; idx++) {
0296         d->elementIdTable.append(QStringLiteral("CHARACTER_%1").arg(idx));
0297     }
0298     for (short idx = 1; idx <= 9; idx++) {
0299         d->elementIdTable.append(QStringLiteral("BAMBOO_%1").arg(idx));
0300     }
0301     for (short idx = 1; idx <= 9; idx++) {
0302         d->elementIdTable.append(QStringLiteral("ROD_%1").arg(idx));
0303     }
0304     for (short idx = 1; idx <= 4; idx++) {
0305         d->elementIdTable.append(QStringLiteral("SEASON_%1").arg(idx));
0306     }
0307     for (short idx = 1; idx <= 4; idx++) {
0308         d->elementIdTable.append(QStringLiteral("WIND_%1").arg(idx));
0309     }
0310     for (short idx = 1; idx <= 3; idx++) {
0311         d->elementIdTable.append(QStringLiteral("DRAGON_%1").arg(idx));
0312     }
0313     for (short idx = 1; idx <= 4; idx++) {
0314         d->elementIdTable.append(QStringLiteral("FLOWER_%1").arg(idx));
0315     }
0316 }
0317 
0318 QString KMahjonggTileset::pixmapCacheNameFromElementId(const QString & elementid)
0319 {
0320     return authorProperty(QStringLiteral("Name")) + elementid + QStringLiteral("W%1H%2").arg(d->scaleddata.w).arg(d->scaleddata.h);
0321 }
0322 
0323 QPixmap KMahjonggTileset::renderElement(short width, short height, const QString & elementid)
0324 {
0325     //qCDebug(LIBKMAHJONGG_LOG) << "render element" << elementid << width << height;
0326     const qreal dpr = qApp->devicePixelRatio();
0327     width = width * dpr;
0328     height = height * dpr;
0329     QImage qiRend(QSize(width, height), QImage::Format_ARGB32_Premultiplied);
0330     qiRend.fill(0);
0331 
0332     if (d->svg.isValid()) {
0333         QPainter p(&qiRend);
0334         d->svg.render(&p, elementid);
0335     }
0336     qiRend.setDevicePixelRatio(dpr);
0337     return QPixmap::fromImage(qiRend);
0338 }
0339 
0340 QPixmap KMahjonggTileset::selectedTile(int num)
0341 {
0342     QPixmap pm;
0343     QString elemId = d->elementIdTable.at(num + 4); //selected offset in our idtable;
0344     if (!QPixmapCache::find(pixmapCacheNameFromElementId(elemId), &pm)) {
0345         //use tile size
0346         pm = renderElement(d->scaleddata.w, d->scaleddata.h, elemId);
0347         QPixmapCache::insert(pixmapCacheNameFromElementId(elemId), pm);
0348     }
0349     return pm;
0350 }
0351 
0352 QPixmap KMahjonggTileset::unselectedTile(int num)
0353 {
0354     QPixmap pm;
0355     QString elemId = d->elementIdTable.at(num);
0356     if (!QPixmapCache::find(pixmapCacheNameFromElementId(elemId), &pm)) {
0357         //use tile size
0358         pm = renderElement(d->scaleddata.w, d->scaleddata.h, elemId);
0359         QPixmapCache::insert(pixmapCacheNameFromElementId(elemId), pm);
0360     }
0361     return pm;
0362 }
0363 
0364 QPixmap KMahjonggTileset::tileface(int num)
0365 {
0366     QPixmap pm;
0367     if ((num + 8) >= d->elementIdTable.count()) {
0368         //qCDebug(LIBKMAHJONGG_LOG) << "Client asked for invalid tileface id";
0369         return pm;
0370     }
0371 
0372     QString elemId = d->elementIdTable.at(num + 8); //tileface offset in our idtable;
0373     if (!QPixmapCache::find(pixmapCacheNameFromElementId(elemId), &pm)) {
0374         //use face size
0375         pm = renderElement(d->scaleddata.fw, d->scaleddata.fh, elemId);
0376         QPixmapCache::insert(pixmapCacheNameFromElementId(elemId), pm);
0377     }
0378     return pm;
0379 }