Files
ygopro/gframe/image_manager.cpp

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