mirror of
https://github.com/Fluorohydride/ygopro.git
synced 2026-10-19 07:01:28 +08:00
467 lines
17 KiB
C++
467 lines
17 KiB
C++
#include "image_manager.h"
|
|
#include "image_utility.h"
|
|
#include "game.h"
|
|
#include <thread>
|
|
|
|
namespace ygo {
|
|
|
|
ImageManager imageManager;
|
|
|
|
bool ImageManager::Initial() {
|
|
for(int i = 0; i < 2; ++i) {
|
|
tCover[i] = nullptr;
|
|
tButtonFacedown[i] = nullptr;
|
|
tButtonFacedownDefense[i] = nullptr;
|
|
}
|
|
tUnknown = nullptr;
|
|
tUnknownFit = nullptr;
|
|
tUnknownThumb = nullptr;
|
|
tBigPicture = nullptr;
|
|
tLoading = nullptr;
|
|
tThumbLoadingThreadRunning = false;
|
|
tAct = driver->getTexture("textures/act.png");
|
|
tAttack = driver->getTexture("textures/attack.png");
|
|
tChain = driver->getTexture("textures/chain.png");
|
|
tNegated = driver->getTexture("textures/negated.png");
|
|
tNumber = driver->getTexture("textures/number.png");
|
|
tLPBar = driver->getTexture("textures/lp.png");
|
|
tLPFrame = driver->getTexture("textures/lpf.png");
|
|
tMask = driver->getTexture("textures/mask.png");
|
|
tEquip = driver->getTexture("textures/equip.png");
|
|
tTarget = driver->getTexture("textures/target.png");
|
|
tChainTarget = driver->getTexture("textures/chaintarget.png");
|
|
tLim = driver->getTexture("textures/lim.png");
|
|
tOT = driver->getTexture("textures/ot.png");
|
|
tHand[0] = driver->getTexture("textures/f1.jpg");
|
|
tHand[1] = driver->getTexture("textures/f2.jpg");
|
|
tHand[2] = driver->getTexture("textures/f3.jpg");
|
|
tBackGround = nullptr;
|
|
tBackGround_menu = nullptr;
|
|
tBackGround_deck = nullptr;
|
|
tField[0] = driver->getTexture("textures/field2.png");
|
|
tFieldTransparent[0] = driver->getTexture("textures/field-transparent2.png");
|
|
tField[1] = driver->getTexture("textures/field3.png");
|
|
tFieldTransparent[1] = driver->getTexture("textures/field-transparent3.png");
|
|
// ResizeTexture will be called by Game::OnResize() during initialization,
|
|
// it will initialize the other textures with the correct size.
|
|
return true;
|
|
}
|
|
void ImageManager::SetDevice(irr::IrrlichtDevice* dev) {
|
|
device = dev;
|
|
driver = dev->getVideoDriver();
|
|
irrFileSystem = dev->getFileSystem();
|
|
}
|
|
void ImageManager::ClearTexture() {
|
|
for(auto tit = tMap[0].begin(); tit != tMap[0].end(); ++tit) {
|
|
if(tit->second)
|
|
driver->removeTexture(tit->second);
|
|
}
|
|
for(auto tit = tMap[1].begin(); tit != tMap[1].end(); ++tit) {
|
|
if(tit->second)
|
|
driver->removeTexture(tit->second);
|
|
}
|
|
for(auto tit = tThumb.begin(); tit != tThumb.end(); ++tit) {
|
|
if(tit->second && tit->second != tLoading)
|
|
driver->removeTexture(tit->second);
|
|
}
|
|
for(auto tit = tFields.begin(); tit != tFields.end(); ++tit) {
|
|
if(tit->second)
|
|
driver->removeTexture(tit->second);
|
|
}
|
|
for(auto tit = tButton.begin(); tit != tButton.end(); ++tit) {
|
|
if(tit->second)
|
|
driver->removeTexture(tit->second);
|
|
}
|
|
for(auto tit = tButtonDefense.begin(); tit != tButtonDefense.end(); ++tit) {
|
|
if(tit->second)
|
|
driver->removeTexture(tit->second);
|
|
}
|
|
if(tBigPicture != nullptr) {
|
|
driver->removeTexture(tBigPicture);
|
|
tBigPicture = nullptr;
|
|
}
|
|
tMap[0].clear();
|
|
tMap[1].clear();
|
|
tThumb.clear();
|
|
tFields.clear();
|
|
tButton.clear();
|
|
tButtonDefense.clear();
|
|
tThumbLoadingMutex.lock();
|
|
tThumbLoading.clear();
|
|
while(!tThumbLoadingCodes.empty())
|
|
tThumbLoadingCodes.pop();
|
|
tThumbLoadingThreadRunning = false;
|
|
tThumbLoadingMutex.unlock();
|
|
}
|
|
void ImageManager::ResizeTexture() {
|
|
irr::s32 imgWidth = CARD_IMG_WIDTH * mainGame->xScale;
|
|
irr::s32 imgHeight = CARD_IMG_HEIGHT * mainGame->yScale;
|
|
irr::s32 imgWidthThumb = CARD_THUMB_WIDTH * mainGame->xScale;
|
|
irr::s32 imgHeightThumb = CARD_THUMB_HEIGHT * mainGame->yScale;
|
|
float mul = (mainGame->xScale > mainGame->yScale) ? mainGame->yScale : mainGame->xScale;
|
|
irr::s32 imgWidthFit = CARD_IMG_WIDTH * mul;
|
|
irr::s32 imgHeightFit = CARD_IMG_HEIGHT * mul;
|
|
irr::s32 bgWidth = GAME_WINDOW_WIDTH * mainGame->xScale;
|
|
irr::s32 bgHeight = GAME_WINDOW_HEIGHT * mainGame->yScale;
|
|
float btnScale = 0.5f * mainGame->yScale;
|
|
irr::s32 btnImgWidth = CARD_IMG_WIDTH * btnScale;
|
|
irr::s32 btnImgHeight = CARD_IMG_HEIGHT * btnScale;
|
|
const char* coverFiles[2] = { "textures/cover.jpg", "textures/cover2.jpg" };
|
|
driver->removeTexture(tCover[0]);
|
|
if(tCover[1] != tCover[0])
|
|
driver->removeTexture(tCover[1]);
|
|
tCover[0] = GetTextureFromFile(coverFiles[0], imgWidth, imgHeight);
|
|
tCover[1] = GetTextureFromFile(coverFiles[1], imgWidth, imgHeight);
|
|
if(!tCover[1])
|
|
tCover[1] = tCover[0];
|
|
driver->removeTexture(tUnknown);
|
|
driver->removeTexture(tUnknownFit);
|
|
driver->removeTexture(tUnknownThumb);
|
|
driver->removeTexture(tLoading);
|
|
tLoading = GetTextureFromFile(coverFiles[0], imgWidthThumb, imgHeightThumb);
|
|
if(!tLoading && mainGame->gameConf.use_image_load_background_thread) {
|
|
// If the cover image is missing, create a blank texture instead.
|
|
// tLoading is used as a sentinel value when loading thumbnails, so it must not be null.
|
|
irr::video::IImage* blankImg = driver->createImage(irr::video::ECF_A8R8G8B8, irr::core::dimension2d<irr::u32>(imgWidthThumb, imgHeightThumb));
|
|
blankImg->fill(0);
|
|
tLoading = driver->addTexture("textures/loading", blankImg);
|
|
blankImg->drop();
|
|
}
|
|
tUnknown = GetTextureFromFile("textures/unknown.jpg", CARD_IMG_WIDTH, CARD_IMG_HEIGHT);
|
|
tUnknownFit = GetTextureFromFile("textures/unknown.jpg", imgWidthFit, imgHeightFit);
|
|
tUnknownThumb = GetTextureFromFile("textures/unknown.jpg", imgWidthThumb, imgHeightThumb);
|
|
driver->removeTexture(tBackGround);
|
|
if(tBackGround_menu != tBackGround)
|
|
driver->removeTexture(tBackGround_menu);
|
|
if(tBackGround_deck != tBackGround)
|
|
driver->removeTexture(tBackGround_deck);
|
|
tBackGround = GetTextureFromFile("textures/bg.jpg", bgWidth, bgHeight);
|
|
tBackGround_menu = GetTextureFromFile("textures/bg_menu.jpg", bgWidth, bgHeight);
|
|
if(!tBackGround_menu)
|
|
tBackGround_menu = tBackGround;
|
|
tBackGround_deck = GetTextureFromFile("textures/bg_deck.jpg", bgWidth, bgHeight);
|
|
if(!tBackGround_deck)
|
|
tBackGround_deck = tBackGround;
|
|
driver->removeTexture(tButtonFacedown[0]);
|
|
if(tButtonFacedown[1] != tButtonFacedown[0])
|
|
driver->removeTexture(tButtonFacedown[1]);
|
|
driver->removeTexture(tButtonFacedownDefense[0]);
|
|
if(tButtonFacedownDefense[1] != tButtonFacedownDefense[0])
|
|
driver->removeTexture(tButtonFacedownDefense[1]);
|
|
for(int i = 0; i < 2; ++i) {
|
|
irr::video::IImage* coverImg = driver->createImageFromFile(coverFiles[i]);
|
|
if(coverImg) {
|
|
irr::video::IImage* coverResized = driver->createImage(coverImg->getColorFormat(), irr::core::dimension2d<irr::u32>(btnImgWidth, btnImgHeight));
|
|
ImageUtility::Resize(coverImg, coverResized, mainGame->gameConf.use_image_scale_multi_thread);
|
|
coverImg->drop();
|
|
char name[256];
|
|
mysnprintf(name, "btn_facedown/%d", i);
|
|
tButtonFacedown[i] = driver->addTexture(name, coverResized);
|
|
irr::video::IImage* coverRotated = ImageUtility::RotateImageCCW90(driver, coverResized);
|
|
coverResized->drop();
|
|
mysnprintf(name, "btn_facedown_defense/%d", i);
|
|
tButtonFacedownDefense[i] = driver->addTexture(name, coverRotated);
|
|
coverRotated->drop();
|
|
} else if(i == 0) {
|
|
tButtonFacedown[i] = nullptr;
|
|
tButtonFacedownDefense[i] = nullptr;
|
|
} else if(i == 1) {
|
|
tButtonFacedown[i] = tButtonFacedown[0];
|
|
tButtonFacedownDefense[i] = tButtonFacedownDefense[0];
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Convert image to texture, resizing if needed.
|
|
* @param name Texture name (Irrlicht texture key).
|
|
* @param srcimg Source image; will be dropped by this function.
|
|
* @return Texture pointer. Remove via `driver->removeTexture` (do not `drop`).
|
|
*/
|
|
irr::video::ITexture* ImageManager::addTexture(const char* name, irr::video::IImage* srcimg, irr::s32 width, irr::s32 height) {
|
|
if(srcimg == nullptr)
|
|
return nullptr;
|
|
irr::video::ITexture* texture;
|
|
if(srcimg->getDimension() == irr::core::dimension2d<irr::u32>(width, height)) {
|
|
texture = driver->addTexture(name, srcimg);
|
|
} else {
|
|
irr::video::IImage* destimg = driver->createImage(srcimg->getColorFormat(), irr::core::dimension2d<irr::u32>(width, height));
|
|
ImageUtility::Resize(srcimg, destimg, mainGame->gameConf.use_image_scale_multi_thread);
|
|
texture = driver->addTexture(name, destimg);
|
|
destimg->drop();
|
|
}
|
|
srcimg->drop();
|
|
return texture;
|
|
}
|
|
/**
|
|
* Load image from file and convert to texture.
|
|
* @return Texture pointer. Remove via `driver->removeTexture` (do not `drop`).
|
|
*/
|
|
irr::video::ITexture* ImageManager::GetTextureFromFile(const char* file, irr::s32 width, irr::s32 height) {
|
|
irr::video::IImage* img = driver->createImageFromFile(file);
|
|
if(img == nullptr) {
|
|
return nullptr;
|
|
}
|
|
char name[256];
|
|
mysnprintf(name, "%s/%d_%d", file, width, height);
|
|
return addTexture(name, img, width, height);
|
|
}
|
|
/**
|
|
* Load card picture from `expansions` or `pics` folder.
|
|
* Files in the expansions directory have priority, allowing custom pictures to be loaded without modifying the original files.
|
|
* If targetWidth / targetHeight are provided, libjpeg DCT-domain scaling is used for faster decoding.
|
|
* Note that the actual dimensions of the returned image are near to but not same as the target dimensions.
|
|
* @return Image pointer. Must be dropped after use.
|
|
*/
|
|
irr::video::IImage* ImageManager::GetImage(int code, irr::s32 targetWidth, irr::s32 targetHeight) {
|
|
char file[256];
|
|
mysnprintf(file, "expansions/pics/%d.jpg", code);
|
|
auto reader = irrFileSystem->createAndOpenFile(file);
|
|
if(reader == nullptr) {
|
|
mysnprintf(file, "pics/%d.jpg", code);
|
|
reader = irrFileSystem->createAndOpenFile(file);
|
|
}
|
|
if(reader == nullptr)
|
|
return nullptr;
|
|
irr::video::IImage* img = ImageUtility::LoadJpegImage(driver, reader, targetWidth, targetHeight);
|
|
reader->drop();
|
|
return img;
|
|
}
|
|
/**
|
|
* Load card picture.
|
|
* @return Texture pointer. Remove via `driver->removeTexture` (do not `drop`).
|
|
*/
|
|
irr::video::ITexture* ImageManager::GetTexture(int code, irr::s32 width, irr::s32 height) {
|
|
irr::video::IImage* img = GetImage(code, width, height);
|
|
if(img == nullptr) {
|
|
return nullptr;
|
|
}
|
|
char name[256];
|
|
mysnprintf(name, "pics/%d/%d_%d", code, width, height);
|
|
return addTexture(name, img, width, height);
|
|
}
|
|
/**
|
|
* Load managed card picture texture.
|
|
* @param fit Resize to fit scale if true.
|
|
* @return Texture pointer. Should NOT be removed nor dropped.
|
|
*/
|
|
irr::video::ITexture* ImageManager::GetTexture(int code, bool fit) {
|
|
if(code == 0)
|
|
return fit ? tUnknownFit : tUnknown;
|
|
int width = CARD_IMG_WIDTH;
|
|
int height = CARD_IMG_HEIGHT;
|
|
if(fit) {
|
|
float mul = mainGame->xScale;
|
|
if(mainGame->xScale > mainGame->yScale)
|
|
mul = mainGame->yScale;
|
|
width = width * mul;
|
|
height = height * mul;
|
|
}
|
|
auto tit = tMap[fit ? 1 : 0].find(code);
|
|
if(tit == tMap[fit ? 1 : 0].end()) {
|
|
irr::video::ITexture* texture = GetTexture(code, width, height);
|
|
tMap[fit ? 1 : 0][code] = texture;
|
|
return (texture == nullptr) ? (fit ? tUnknownFit : tUnknown) : texture;
|
|
}
|
|
if(tit->second)
|
|
return tit->second;
|
|
else
|
|
return fit ? tUnknownFit : tUnknown;
|
|
}
|
|
/**
|
|
* Load managed card picture texture with zoom.
|
|
* @return Texture pointer. Should NOT be removed nor dropped.
|
|
*/
|
|
irr::video::ITexture* ImageManager::GetBigPicture(int code, float zoom) {
|
|
if(code == 0)
|
|
return tUnknownFit;
|
|
if(tBigPicture != nullptr) {
|
|
driver->removeTexture(tBigPicture);
|
|
tBigPicture = nullptr;
|
|
}
|
|
irr::video::IImage* img = GetImage(code);
|
|
if(img == nullptr) {
|
|
return tUnknownFit;
|
|
}
|
|
char name[256];
|
|
mysnprintf(name, "pics/%d/big", code);
|
|
auto origsize = img->getDimension();
|
|
tBigPicture = addTexture(name, img, origsize.Width * zoom, origsize.Height * zoom);
|
|
return tBigPicture;
|
|
}
|
|
int ImageManager::LoadThumbThread() {
|
|
while(true) {
|
|
imageManager.tThumbLoadingMutex.lock();
|
|
imageManager.tThumbLoadingThreadRunning = !imageManager.tThumbLoadingCodes.empty();
|
|
if(!imageManager.tThumbLoadingThreadRunning) {
|
|
imageManager.tThumbLoadingMutex.unlock();
|
|
break;
|
|
}
|
|
int code = imageManager.tThumbLoadingCodes.front();
|
|
imageManager.tThumbLoadingCodes.pop();
|
|
imageManager.tThumbLoadingMutex.unlock();
|
|
const int width = CARD_THUMB_WIDTH * mainGame->xScale;
|
|
const int height = CARD_THUMB_HEIGHT * mainGame->yScale;
|
|
irr::video::IImage* img = imageManager.GetImage(code, width, height);
|
|
if(img != nullptr) {
|
|
if(img->getDimension() == irr::core::dimension2d<irr::u32>(width, height)) {
|
|
imageManager.tThumbLoadingMutex.lock();
|
|
if(imageManager.tThumbLoadingThreadRunning)
|
|
imageManager.tThumbLoading[code] = img;
|
|
else
|
|
img->drop();
|
|
imageManager.tThumbLoadingMutex.unlock();
|
|
} else {
|
|
irr::video::IImage *destimg = imageManager.driver->createImage(img->getColorFormat(), irr::core::dimension2d<irr::u32>(width, height));
|
|
ImageUtility::Resize(img, destimg, mainGame->gameConf.use_image_scale_multi_thread);
|
|
img->drop();
|
|
imageManager.tThumbLoadingMutex.lock();
|
|
if(imageManager.tThumbLoadingThreadRunning)
|
|
imageManager.tThumbLoading[code] = destimg;
|
|
else
|
|
destimg->drop();
|
|
imageManager.tThumbLoadingMutex.unlock();
|
|
}
|
|
} else {
|
|
imageManager.tThumbLoadingMutex.lock();
|
|
if(imageManager.tThumbLoadingThreadRunning)
|
|
imageManager.tThumbLoading[code] = nullptr;
|
|
imageManager.tThumbLoadingMutex.unlock();
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
/**
|
|
* Load managed card thumbnail texture.
|
|
* @return Texture pointer. Should NOT be removed nor dropped.
|
|
*/
|
|
irr::video::ITexture* ImageManager::GetTextureThumb(int code) {
|
|
if(code == 0)
|
|
return tUnknownThumb;
|
|
auto tit = tThumb.find(code);
|
|
if(tit == tThumb.end() && !mainGame->gameConf.use_image_load_background_thread) {
|
|
int width = CARD_THUMB_WIDTH * mainGame->xScale;
|
|
int height = CARD_THUMB_HEIGHT * mainGame->yScale;
|
|
irr::video::ITexture* texture = GetTexture(code, width, height);
|
|
tThumb[code] = texture;
|
|
return (texture == nullptr) ? tUnknownThumb : texture;
|
|
}
|
|
if(tit == tThumb.end() || tit->second == tLoading) {
|
|
// textures must be added in the main thread which handle OpenGL context
|
|
imageManager.tThumbLoadingMutex.lock();
|
|
auto lit = tThumbLoading.find(code);
|
|
if(lit != tThumbLoading.end()) {
|
|
if(lit->second != nullptr) {
|
|
char textureName[256];
|
|
mysnprintf(textureName, "pics/%d/thumbnail", code);
|
|
irr::video::ITexture* texture = driver->addTexture(textureName, lit->second);
|
|
lit->second->drop();
|
|
tThumb[code] = texture;
|
|
} else {
|
|
tThumb[code] = nullptr;
|
|
}
|
|
tThumbLoading.erase(lit);
|
|
}
|
|
imageManager.tThumbLoadingMutex.unlock();
|
|
tit = tThumb.find(code);
|
|
}
|
|
if(tit == tThumb.end()) {
|
|
tThumb[code] = tLoading;
|
|
imageManager.tThumbLoadingMutex.lock();
|
|
tThumbLoadingCodes.push(code);
|
|
if(!tThumbLoadingThreadRunning) {
|
|
tThumbLoadingThreadRunning = true;
|
|
std::thread(LoadThumbThread).detach();
|
|
}
|
|
imageManager.tThumbLoadingMutex.unlock();
|
|
return tLoading;
|
|
}
|
|
if(tit->second)
|
|
return tit->second;
|
|
else
|
|
return tUnknownThumb;
|
|
}
|
|
/**
|
|
* Load managed duel field texture.
|
|
* @return Texture pointer. Should NOT be removed nor dropped.
|
|
*/
|
|
irr::video::ITexture* ImageManager::GetTextureField(int code) {
|
|
if(code == 0)
|
|
return nullptr;
|
|
auto tit = tFields.find(code);
|
|
if(tit == tFields.end()) {
|
|
irr::s32 width = 512 * mainGame->xScale;
|
|
irr::s32 height = 512 * mainGame->yScale;
|
|
char file[256];
|
|
mysnprintf(file, "expansions/pics/field/%d.png", code);
|
|
irr::video::ITexture* img = GetTextureFromFile(file, width, height);
|
|
if(img == nullptr) {
|
|
mysnprintf(file, "expansions/pics/field/%d.jpg", code);
|
|
img = GetTextureFromFile(file, width, height);
|
|
}
|
|
if(img == nullptr) {
|
|
mysnprintf(file, "pics/field/%d.png", code);
|
|
img = GetTextureFromFile(file, width, height);
|
|
}
|
|
if(img == nullptr) {
|
|
mysnprintf(file, "pics/field/%d.jpg", code);
|
|
img = GetTextureFromFile(file, width, height);
|
|
if(img == nullptr) {
|
|
tFields[code] = nullptr;
|
|
return nullptr;
|
|
} else {
|
|
tFields[code] = img;
|
|
return img;
|
|
}
|
|
} else {
|
|
tFields[code] = img;
|
|
return img;
|
|
}
|
|
}
|
|
if(tit->second)
|
|
return tit->second;
|
|
else
|
|
return nullptr;
|
|
}
|
|
/**
|
|
* Load managed duel button (select card, atk/def position) texture.
|
|
* @return Texture pointer. Should NOT be removed nor dropped.
|
|
*/
|
|
irr::video::ITexture* ImageManager::GetTextureButton(int code, bool defense) {
|
|
if(code == 0)
|
|
return nullptr;
|
|
auto& cache = defense ? tButtonDefense : tButton;
|
|
auto tit = cache.find(code);
|
|
if(tit != cache.end())
|
|
return tit->second;
|
|
float btnScale = 0.5f * mainGame->yScale;
|
|
irr::s32 width = (irr::s32)(CARD_IMG_WIDTH * btnScale);
|
|
irr::s32 height = (irr::s32)(CARD_IMG_HEIGHT * btnScale);
|
|
irr::video::IImage* img = GetImage(code);
|
|
if(!img) {
|
|
cache[code] = nullptr;
|
|
return nullptr;
|
|
}
|
|
irr::video::IImage* resized = driver->createImage(img->getColorFormat(), irr::core::dimension2d<irr::u32>(width, height));
|
|
ImageUtility::Resize(img, resized, mainGame->gameConf.use_image_scale_multi_thread);
|
|
img->drop();
|
|
irr::video::ITexture* texture = nullptr;
|
|
if(defense) {
|
|
irr::video::IImage* rotated = ImageUtility::RotateImageCCW90(driver, resized);
|
|
resized->drop();
|
|
if(rotated) {
|
|
char name[256];
|
|
mysnprintf(name, "pics/%d/btn_defense", code);
|
|
texture = driver->addTexture(name, rotated);
|
|
rotated->drop();
|
|
}
|
|
} else {
|
|
char name[256];
|
|
mysnprintf(name, "pics/%d/btn", code);
|
|
texture = driver->addTexture(name, resized);
|
|
resized->drop();
|
|
}
|
|
cache[code] = texture;
|
|
return texture;
|
|
}
|
|
}
|