#include "image_manager.h" #include "image_utility.h" #include "game.h" #include 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(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(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(width, height)) { texture = driver->addTexture(name, srcimg); } else { irr::video::IImage* destimg = driver->createImage(srcimg->getColorFormat(), irr::core::dimension2d(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(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(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(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; } }