load images async, champion tooltip

This commit is contained in:
mrbesen 2022-04-24 12:15:58 +02:00
parent 4926373934
commit 246d5f80bc
Signed by untrusted user: MrBesen
GPG Key ID: 596B2350DCD67504
7 changed files with 219 additions and 69 deletions

View File

@ -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;
};

View File

@ -43,6 +43,7 @@ signals:
private:
void rescaleImage();
void applyChampion(const DataDragon::ChampData& cd);
Ui::StageSettings *ui;
DataDragon* dd = nullptr;

View File

@ -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 << "]";
}

View File

@ -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();
}

View File

@ -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
}

View File

@ -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>

View File

@ -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>