load images async, champion tooltip
This commit is contained in:
parent
4926373934
commit
246d5f80bc
|
@ -1,6 +1,10 @@
|
|||
#pragma once
|
||||
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <curl/curl.h>
|
||||
#include <QJsonDocument>
|
||||
|
@ -11,7 +15,10 @@
|
|||
|
||||
class DataDragon {
|
||||
public:
|
||||
using notifyImgfunc_t = std::function<void(cv::Mat)>;
|
||||
|
||||
DataDragon();
|
||||
~DataDragon();
|
||||
DataDragon(const DataDragon&) = delete;
|
||||
DataDragon& operator=(const DataDragon&) = delete;
|
||||
|
||||
|
@ -33,25 +40,51 @@ public:
|
|||
SPLASH = 2
|
||||
};
|
||||
|
||||
// might block until version is available
|
||||
const std::string& getVersion();
|
||||
// might block until champ data is available
|
||||
const std::vector<ChampData>& getChamps();
|
||||
// might block until image is downloaded
|
||||
cv::Mat getImage(const std::string& champid, ImageType imgtype = ImageType::SQUARE);
|
||||
void getImageAsnyc(const std::string& champid, notifyImgfunc_t func, ImageType imgtype = ImageType::SQUARE);
|
||||
// might block until champ data is available
|
||||
const ChampData& getBestMatchingChamp(const std::string& name, int* count = nullptr);
|
||||
|
||||
static const ChampData EMPTYCHAMP;
|
||||
protected:
|
||||
std::string getImageUrl(const std::string& champid, ImageType type);
|
||||
std::string getCDNString() const;
|
||||
QByteArray requestRaw(const std::string& url);
|
||||
QJsonDocument request(const std::string& url);
|
||||
|
||||
void getVersionInternal();
|
||||
void getChampsInternal();
|
||||
void startThread();
|
||||
void stopThread();
|
||||
void stopAndJoinThread();
|
||||
void threadLoop();
|
||||
|
||||
std::string version;
|
||||
std::vector<ChampData> champs;
|
||||
std::mutex cachedatamutex;
|
||||
std::condition_variable cachedatacv;
|
||||
|
||||
private:
|
||||
struct Task {
|
||||
std::string champid;
|
||||
notifyImgfunc_t func;
|
||||
ImageType type;
|
||||
};
|
||||
|
||||
CURL* curl = nullptr; // the curl (does curling)
|
||||
|
||||
DataDragonImageCache cache[3];
|
||||
MemoryImageCache memcache;
|
||||
std::list<Task> tasks;
|
||||
std::mutex tasksmutex;
|
||||
std::condition_variable tasksnotemptycv;
|
||||
std::thread bgthread;
|
||||
bool shouldrun = true;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -43,6 +43,7 @@ signals:
|
|||
|
||||
private:
|
||||
void rescaleImage();
|
||||
void applyChampion(const DataDragon::ChampData& cd);
|
||||
|
||||
Ui::StageSettings *ui;
|
||||
DataDragon* dd = nullptr;
|
||||
|
|
|
@ -29,6 +29,14 @@ DataDragon::DataDragon() : cache({{"square", ".png"}, {"loading", "_0.jpg"}, {"s
|
|||
curl = curl_easy_init();
|
||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, arrayWriteCallback);
|
||||
|
||||
startThread();
|
||||
}
|
||||
|
||||
DataDragon::~DataDragon() {
|
||||
stopAndJoinThread();
|
||||
|
||||
curl_easy_cleanup(curl);
|
||||
}
|
||||
|
||||
DataDragon::ChampData::ChampData() : key(-1) {}
|
||||
|
@ -42,52 +50,24 @@ DataDragon::ChampData::ChampData(const QJsonObject& source) {
|
|||
}
|
||||
|
||||
const std::string& DataDragon::getVersion() {
|
||||
if(!version.empty()) {
|
||||
return version;
|
||||
std::unique_lock lock(cachedatamutex);
|
||||
while(version.empty() && shouldrun) {
|
||||
cachedatacv.wait(lock);
|
||||
}
|
||||
|
||||
QJsonDocument jversions = request("api/versions.json");
|
||||
if(jversions.isArray()) {
|
||||
QJsonArray jverarr = jversions.array();
|
||||
if(!jverarr.empty()) {
|
||||
version = jverarr.at(0).toString().toStdString();
|
||||
Log::info << "got League version: " << version;
|
||||
return version;
|
||||
}
|
||||
}
|
||||
|
||||
Log::error << "error parsing version object";
|
||||
// empty version str
|
||||
return version;
|
||||
}
|
||||
|
||||
const std::vector<DataDragon::ChampData>& DataDragon::getChamps() {
|
||||
if(!champs.empty()) {
|
||||
return champs;
|
||||
std::unique_lock lock(cachedatamutex);
|
||||
while(champs.empty() && shouldrun) {
|
||||
cachedatacv.wait(lock);
|
||||
}
|
||||
if(getVersion().empty()) {
|
||||
return champs;
|
||||
}
|
||||
|
||||
QJsonDocument jchamps = request(getCDNString() + "data/en_US/champion.json");
|
||||
if(jchamps.isObject()) {
|
||||
QJsonObject obj = jchamps.object();
|
||||
auto it = obj.constFind("data");
|
||||
if(it != obj.constEnd() && it.value().isObject()) {
|
||||
QJsonObject jchampsdata = it.value().toObject();
|
||||
for(auto champit = jchampsdata.constBegin(); champit != jchampsdata.constEnd(); champit++) {
|
||||
if(champit.value().isObject()) {
|
||||
champs.emplace_back(champit.value().toObject());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Log::info << "loaded " << champs.size() << " champs";
|
||||
|
||||
return champs;
|
||||
}
|
||||
|
||||
cv::Mat DataDragon::getImage(const std::string& champid, ImageType imgtype) {
|
||||
if(champid.empty()) return {};
|
||||
|
||||
// query mem cache
|
||||
cv::Mat img = memcache.getImage(champid, (int) imgtype);
|
||||
if(!img.empty()) return img;
|
||||
|
@ -100,21 +80,8 @@ cv::Mat DataDragon::getImage(const std::string& champid, ImageType imgtype) {
|
|||
return img;
|
||||
}
|
||||
|
||||
std::string url;
|
||||
if(imgtype == ImageType::SQUARE) {
|
||||
if(getVersion().empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// with version
|
||||
url = getCDNString() + "img/champion/" + champid + ".png";
|
||||
} else if(imgtype == ImageType::LOADING) {
|
||||
// no version
|
||||
url = "cdn/img/champion/loading/" + champid + "_0.jpg";
|
||||
} else if(imgtype == ImageType::SPLASH) {
|
||||
// no version!
|
||||
url = "cdn/img/champion/splash/" + champid + "_0.jpg";
|
||||
}
|
||||
const std::string url = getImageUrl(champid, imgtype);
|
||||
if(url.empty()) return {};
|
||||
|
||||
QByteArray arr = requestRaw(url);
|
||||
if(arr.isEmpty()) {
|
||||
|
@ -124,7 +91,7 @@ cv::Mat DataDragon::getImage(const std::string& champid, ImageType imgtype) {
|
|||
|
||||
// propably an error
|
||||
if(arr.size() < 1000) {
|
||||
Log::info << "small icon url: " << getCDNString() << "img/champion/" << champid << ".png";
|
||||
Log::info << "small icon url: " << url;
|
||||
Log::info << "content: " << std::string(arr.data(), arr.size());
|
||||
return {};
|
||||
}
|
||||
|
@ -132,6 +99,7 @@ cv::Mat DataDragon::getImage(const std::string& champid, ImageType imgtype) {
|
|||
// store HDD cache
|
||||
cache[(int) imgtype].addImageRaw(arr, champid);
|
||||
|
||||
// convert byte array to image data
|
||||
cv::Mat rawData(1, arr.size(), CV_8UC1, (void*) arr.data());
|
||||
|
||||
cv::Mat decodedImage = cv::imdecode(rawData, cv::IMREAD_COLOR);
|
||||
|
@ -143,6 +111,16 @@ cv::Mat DataDragon::getImage(const std::string& champid, ImageType imgtype) {
|
|||
return decodedImage;
|
||||
}
|
||||
|
||||
void DataDragon::getImageAsnyc(const std::string& champid, notifyImgfunc_t func, ImageType imgtype) {
|
||||
if(!func) return;
|
||||
|
||||
{
|
||||
std::lock_guard lock(tasksmutex);
|
||||
tasks.push_back({champid, func, imgtype});
|
||||
}
|
||||
tasksnotemptycv.notify_one();
|
||||
}
|
||||
|
||||
static std::string toLower(const std::string& in) {
|
||||
return QString::fromStdString(in).toLower().toStdString();
|
||||
}
|
||||
|
@ -202,6 +180,26 @@ const DataDragon::ChampData& DataDragon::getBestMatchingChamp(const std::string&
|
|||
return EMPTYCHAMP;
|
||||
}
|
||||
|
||||
std::string DataDragon::getImageUrl(const std::string& champid, ImageType type) {
|
||||
switch(type) {
|
||||
case ImageType::SQUARE: {
|
||||
if(getVersion().empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// with version
|
||||
return getCDNString() + "img/champion/" + champid + ".png";
|
||||
}
|
||||
case ImageType::LOADING:
|
||||
// no version
|
||||
return "cdn/img/champion/loading/" + champid + "_0.jpg";
|
||||
case ImageType::SPLASH:
|
||||
// no version
|
||||
return "cdn/img/champion/splash/" + champid + "_0.jpg";
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string DataDragon::getCDNString() const {
|
||||
return "cdn/" + version + "/";
|
||||
}
|
||||
|
@ -247,6 +245,92 @@ QJsonDocument DataDragon::request(const std::string& url) {
|
|||
return parsed;
|
||||
}
|
||||
|
||||
void DataDragon::getVersionInternal() {
|
||||
std::unique_lock lock(cachedatamutex);
|
||||
|
||||
if(!version.empty()) return;
|
||||
|
||||
QJsonDocument jversions = request("api/versions.json");
|
||||
if(jversions.isArray()) {
|
||||
QJsonArray jverarr = jversions.array();
|
||||
if(!jverarr.empty()) {
|
||||
version = jverarr.at(0).toString().toStdString();
|
||||
Log::info << "got League version: " << version;
|
||||
|
||||
lock.unlock();
|
||||
cachedatacv.notify_all();
|
||||
return;
|
||||
}
|
||||
}
|
||||
Log::error << "error parsing version object";
|
||||
}
|
||||
|
||||
void DataDragon::getChampsInternal() {
|
||||
std::unique_lock lock(cachedatamutex);
|
||||
if(!champs.empty() && version.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonDocument jchamps = request(getCDNString() + "data/en_US/champion.json");
|
||||
if(jchamps.isObject()) {
|
||||
QJsonObject obj = jchamps.object();
|
||||
auto it = obj.constFind("data");
|
||||
if(it != obj.constEnd() && it.value().isObject()) {
|
||||
QJsonObject jchampsdata = it.value().toObject();
|
||||
for(auto champit = jchampsdata.constBegin(); champit != jchampsdata.constEnd(); champit++) {
|
||||
if(champit.value().isObject()) {
|
||||
champs.emplace_back(champit.value().toObject());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Log::info << "loaded " << champs.size() << " champs";
|
||||
lock.unlock();
|
||||
cachedatacv.notify_all();
|
||||
}
|
||||
|
||||
void DataDragon::startThread() {
|
||||
shouldrun = true;
|
||||
bgthread = std::thread(&DataDragon::threadLoop, this);
|
||||
}
|
||||
|
||||
void DataDragon::stopThread() {
|
||||
shouldrun = false;
|
||||
tasksnotemptycv.notify_all();
|
||||
}
|
||||
|
||||
void DataDragon::stopAndJoinThread() {
|
||||
stopThread();
|
||||
|
||||
if(bgthread.joinable())
|
||||
bgthread.join();
|
||||
}
|
||||
|
||||
void DataDragon::threadLoop() {
|
||||
// init version and champ list
|
||||
getVersionInternal();
|
||||
getChampsInternal();
|
||||
|
||||
while(shouldrun) {
|
||||
// wait for a task in the list
|
||||
Task t;
|
||||
{
|
||||
std::unique_lock lock(tasksmutex);
|
||||
if(tasks.empty()) {
|
||||
tasksnotemptycv.wait(lock);
|
||||
}
|
||||
if(tasks.empty()) continue;
|
||||
|
||||
t = tasks.front();
|
||||
tasks.pop_front();
|
||||
}
|
||||
|
||||
cv::Mat img = getImage(t.champid, t.type);
|
||||
t.func(img);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::ostream& operator<<(std::ostream& str, const DataDragon::ChampData& cd) {
|
||||
return str << "[n: " << cd.name << " " << " k: " << cd.key << " id: " << cd.id << "]";
|
||||
}
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
|
||||
#include <Log.h>
|
||||
|
||||
#include <future>
|
||||
|
||||
static void applySetting(const Config::StageConfig& sc, StageSettings* ss) {
|
||||
ss->setState(sc.enabled);
|
||||
ss->setChampion(QString::fromStdString(sc.champ));
|
||||
|
@ -33,7 +31,7 @@ MainWindow::~MainWindow() {
|
|||
delete ui;
|
||||
}
|
||||
|
||||
void MainWindow::closeEvent(QCloseEvent* event) {
|
||||
void MainWindow::closeEvent([[maybe_unused]] QCloseEvent* event) {
|
||||
conf.save();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
#include "stagesettings.h"
|
||||
#include "ui_stagesettings.h"
|
||||
|
||||
#include <QLocale>
|
||||
|
||||
#include <Log.h>
|
||||
|
||||
StageSettings::StageSettings(QWidget *parent) : QWidget(parent), ui(new Ui::StageSettings) {
|
||||
|
@ -54,10 +56,7 @@ void StageSettings::championChangedinternal(const QString& str) {
|
|||
int count = 0;
|
||||
const DataDragon::ChampData& cd = dd->getBestMatchingChamp(str.toStdString(), &count);
|
||||
if(cd.key != currentdisplayedChampKey) {
|
||||
img = (cd.key < 0) ? cv::Mat() : dd->getImage(cd.id);
|
||||
currentdisplayedChampKey = cd.key;
|
||||
rescaleImage();
|
||||
emit championChanged(QString::fromStdString(cd.name));
|
||||
applyChampion(cd);
|
||||
}
|
||||
ui->champcount->setText(tr("Champions matched: %1").arg(QString::fromStdString(std::to_string(count))));
|
||||
} else {
|
||||
|
@ -85,3 +84,16 @@ void StageSettings::rescaleImage() {
|
|||
// set a scaled pixmap to a w x h window keeping its aspect ratio
|
||||
ui->champImg->setPixmap(p.scaled(w, h, Qt::KeepAspectRatio)); //
|
||||
}
|
||||
|
||||
void StageSettings::applyChampion(const DataDragon::ChampData& cd) {
|
||||
dd->getImageAsnyc(cd.id, [this, cd](cv::Mat img) {
|
||||
this->img = img;
|
||||
currentdisplayedChampKey = cd.key;
|
||||
rescaleImage();
|
||||
emit championChanged(QString::fromStdString(cd.name));
|
||||
});
|
||||
|
||||
#define QS(A) arg(QString::fromStdString(A))
|
||||
ui->champImg->setToolTip(tr("Champion: %1\nType: %2\nTitle: %3\nID: %4").QS(cd.name).QS(cd.partype).QS(cd.title).arg(cd.key));
|
||||
#undef QS
|
||||
}
|
||||
|
|
21
ts/de_DE.ts
21
ts/de_DE.ts
|
@ -46,17 +46,17 @@
|
|||
<translation>Bannen</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../src/mainwindow.cpp" line="45"/>
|
||||
<location filename="../src/mainwindow.cpp" line="43"/>
|
||||
<source>League of Legends Client not found!</source>
|
||||
<translation>League of Legends Client nicht gefunden!</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../src/mainwindow.cpp" line="51"/>
|
||||
<location filename="../src/mainwindow.cpp" line="49"/>
|
||||
<source>Auto-Acceptor started!</source>
|
||||
<translation></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../src/mainwindow.cpp" line="54"/>
|
||||
<location filename="../src/mainwindow.cpp" line="52"/>
|
||||
<source>Auto-Acceptor stoped!</source>
|
||||
<translation>Auto Acceptor gestoppt!</translation>
|
||||
</message>
|
||||
|
@ -80,14 +80,25 @@
|
|||
<translation>Champion:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../src/stagesettings.cpp" line="23"/>
|
||||
<location filename="../src/stagesettings.cpp" line="25"/>
|
||||
<source>Enable %1</source>
|
||||
<translation>Aktiviere %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../src/stagesettings.cpp" line="62"/>
|
||||
<location filename="../src/stagesettings.cpp" line="61"/>
|
||||
<source>Champions matched: %1</source>
|
||||
<translation>Übereinstimmende Champions: %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../src/stagesettings.cpp" line="97"/>
|
||||
<source>Champion: %1
|
||||
Type: %2
|
||||
Title: %3
|
||||
ID: %4</source>
|
||||
<translation>Champion: %1
|
||||
Typ: %2
|
||||
Titel: %3
|
||||
ID: %4</translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
|
|
21
ts/en.ts
21
ts/en.ts
|
@ -46,17 +46,17 @@
|
|||
<translation>Ban</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../src/mainwindow.cpp" line="45"/>
|
||||
<location filename="../src/mainwindow.cpp" line="43"/>
|
||||
<source>League of Legends Client not found!</source>
|
||||
<translation>League of Legends Client not found!</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../src/mainwindow.cpp" line="51"/>
|
||||
<location filename="../src/mainwindow.cpp" line="49"/>
|
||||
<source>Auto-Acceptor started!</source>
|
||||
<translation>Auto-Acceptor started!</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../src/mainwindow.cpp" line="54"/>
|
||||
<location filename="../src/mainwindow.cpp" line="52"/>
|
||||
<source>Auto-Acceptor stoped!</source>
|
||||
<translation>Auto-Acceptor stopped!</translation>
|
||||
</message>
|
||||
|
@ -80,14 +80,25 @@
|
|||
<translation>Champion:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../src/stagesettings.cpp" line="23"/>
|
||||
<location filename="../src/stagesettings.cpp" line="25"/>
|
||||
<source>Enable %1</source>
|
||||
<translation>Enable %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../src/stagesettings.cpp" line="62"/>
|
||||
<location filename="../src/stagesettings.cpp" line="61"/>
|
||||
<source>Champions matched: %1</source>
|
||||
<translation>Champions matched: %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../src/stagesettings.cpp" line="97"/>
|
||||
<source>Champion: %1
|
||||
Type: %2
|
||||
Title: %3
|
||||
ID: %4</source>
|
||||
<translation>Champion: %1
|
||||
Type: %2
|
||||
Title: %3
|
||||
ID: %4</translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
|
|
Loading…
Reference in New Issue