diff --git a/client/ClientCommandManager.cpp b/client/ClientCommandManager.cpp index fc41ffab1..c6f31e881 100644 --- a/client/ClientCommandManager.cpp +++ b/client/ClientCommandManager.cpp @@ -18,7 +18,6 @@ #include "gui/CGuiHandler.h" #include "gui/WindowHandler.h" #include "render/IRenderHandler.h" -#include "render/AssetGenerator.h" #include "ClientNetPackVisitors.h" #include "../lib/CConfigHandler.h" #include "../lib/gameState/CGameState.h" @@ -510,7 +509,7 @@ void ClientCommandManager::handleVsLog(std::istringstream & singleWordBuffer) void ClientCommandManager::handleGenerateAssets() { - AssetGenerator::generateAll(); + GH.renderHandler().exportGeneratedAssets(); printCommandMessage("All assets generated"); } diff --git a/client/adventureMap/AdventureMapInterface.cpp b/client/adventureMap/AdventureMapInterface.cpp index 2742ae213..b77809af7 100644 --- a/client/adventureMap/AdventureMapInterface.cpp +++ b/client/adventureMap/AdventureMapInterface.cpp @@ -34,7 +34,6 @@ #include "../render/IImage.h" #include "../render/IRenderHandler.h" #include "../render/IScreenHandler.h" -#include "../render/AssetGenerator.h" #include "../CMT.h" #include "../PlayerLocalState.h" #include "../CPlayerInterface.h" @@ -65,8 +64,6 @@ AdventureMapInterface::AdventureMapInterface(): pos.w = GH.screenDimensions().x; pos.h = GH.screenDimensions().y; - AssetGenerator::createPaletteShiftedSprites(); - shortcuts = std::make_shared(*this); widget = std::make_shared(shortcuts); diff --git a/client/battle/BattleStacksController.cpp b/client/battle/BattleStacksController.cpp index 6c981d621..d0be307cc 100644 --- a/client/battle/BattleStacksController.cpp +++ b/client/battle/BattleStacksController.cpp @@ -27,7 +27,6 @@ #include "../gui/CGuiHandler.h" #include "../gui/WindowHandler.h" #include "../media/ISoundPlayer.h" -#include "../render/AssetGenerator.h" #include "../render/Colors.h" #include "../render/Canvas.h" #include "../render/IRenderHandler.h" @@ -80,7 +79,6 @@ BattleStacksController::BattleStacksController(BattleInterface & owner): stackToActivate(nullptr), animIDhelper(0) { - AssetGenerator::createCombatUnitNumberWindow(); //preparing graphics for displaying amounts of creatures amountNormal = GH.renderHandler().loadImage(ImagePath::builtin("combatUnitNumberWindowDefault"), EImageBlitMode::COLORKEY); amountPositive = GH.renderHandler().loadImage(ImagePath::builtin("combatUnitNumberWindowPositive"), EImageBlitMode::COLORKEY); diff --git a/client/lobby/OptionsTabBase.cpp b/client/lobby/OptionsTabBase.cpp index 8335d9f9d..ad148b7a2 100644 --- a/client/lobby/OptionsTabBase.cpp +++ b/client/lobby/OptionsTabBase.cpp @@ -18,7 +18,6 @@ #include "../widgets/TextControls.h" #include "../CServerHandler.h" #include "../CGameInfo.h" -#include "../render/AssetGenerator.h" #include "../../lib/StartInfo.h" #include "../../lib/texts/CGeneralTextHandler.h" @@ -69,8 +68,6 @@ std::vector OptionsTabBase::getSimturnsPresets() const OptionsTabBase::OptionsTabBase(const JsonPath & configPath) { - AssetGenerator::createAdventureOptionsCleanBackground(); - recActions = 0; auto setTimerPresetCallback = [this](int index){ diff --git a/client/mainmenu/CMainMenu.cpp b/client/mainmenu/CMainMenu.cpp index 05b2f71cb..e5e8af87c 100644 --- a/client/mainmenu/CMainMenu.cpp +++ b/client/mainmenu/CMainMenu.cpp @@ -38,7 +38,6 @@ #include "../widgets/VideoWidget.h" #include "../windows/InfoWindows.h" #include "../CServerHandler.h" -#include "../render/AssetGenerator.h" #include "../CGameInfo.h" #include "../CPlayerInterface.h" @@ -428,9 +427,6 @@ void CMainMenu::openCampaignScreen(std::string name) { auto const & config = CMainMenuConfig::get().getCampaigns(); - AssetGenerator::createCampaignBackground(); - AssetGenerator::createChroniclesCampaignImages(); - if(!vstd::contains(config.Struct(), name)) { logGlobal->error("Unknown campaign set: %s", name); diff --git a/client/render/AssetGenerator.cpp b/client/render/AssetGenerator.cpp index 20882069d..6f264b0ac 100644 --- a/client/render/AssetGenerator.cpp +++ b/client/render/AssetGenerator.cpp @@ -29,36 +29,60 @@ #include "../lib/RoadHandler.h" #include "../lib/TerrainHandler.h" -void AssetGenerator::clear() +AssetGenerator::AssetGenerator() +{ +} + +void AssetGenerator::initialize() { // clear to avoid non updated sprites after mod change (if base imnages are used) if(boost::filesystem::is_directory(VCMIDirs::get().userDataPath() / "Generated")) boost::filesystem::remove_all(VCMIDirs::get().userDataPath() / "Generated"); -} -void AssetGenerator::generateAll() -{ - createBigSpellBook(); - createAdventureOptionsCleanBackground(); - for (int i = 0; i < PlayerColor::PLAYER_LIMIT_I; ++i) - createPlayerColoredBackground(PlayerColor(i)); - createCombatUnitNumberWindow(); - createCampaignBackground(); - createChroniclesCampaignImages(); + imageFiles[ImagePath::builtin("AdventureOptionsBackgroundClear.png")] = [this](){ return createAdventureOptionsCleanBackground();}; + imageFiles[ImagePath::builtin("SpellBookLarge.png")] = [this](){ return createBigSpellBook();}; + + imageFiles[ImagePath::builtin("combatUnitNumberWindowDefault.png")] = [this](){ return createCombatUnitNumberWindow(0.6f, 0.2f, 1.0f);}; + imageFiles[ImagePath::builtin("combatUnitNumberWindowNeutral.png")] = [this](){ return createCombatUnitNumberWindow(1.0f, 1.0f, 2.0f);}; + imageFiles[ImagePath::builtin("combatUnitNumberWindowPositive.png")] = [this](){ return createCombatUnitNumberWindow(0.2f, 1.0f, 0.2f);}; + imageFiles[ImagePath::builtin("combatUnitNumberWindowNegative.png")] = [this](){ return createCombatUnitNumberWindow(1.0f, 0.2f, 0.2f);}; + + imageFiles[ImagePath::builtin("CampaignBackground8.png")] = [this](){ return createCampaignBackground();}; + + for (PlayerColor color(0); color < PlayerColor::PLAYER_LIMIT; ++color) + imageFiles[ImagePath::builtin("DialogBoxBackground_" + color.toString())] = [this, color](){ return createPlayerColoredBackground(color);}; + + for(int i = 1; i < 9; i++) + imageFiles[ImagePath::builtin("CampaignHc" + std::to_string(i) + "Image.png")] = [this, i](){ return createChroniclesCampaignImages(i);}; + createPaletteShiftedSprites(); } -void AssetGenerator::createAdventureOptionsCleanBackground() +std::shared_ptr AssetGenerator::generateImage(const ImagePath & image) { - std::string filename = "data/AdventureOptionsBackgroundClear.png"; + if (imageFiles.count(image)) + return imageFiles.at(image)()->toSharedImage(); // TODO: cache? + else + return nullptr; +} - if(CResourceHandler::get()->existsResource(ResourcePath(filename))) // overridden by mod, no generation - return; +std::map> AssetGenerator::generateAllImages() +{ + std::map> result; - if(!CResourceHandler::get("local")->createResource(filename)) - return; - ResourcePath savePath(filename, EResType::IMAGE); + for (const auto & entry : imageFiles) + result[entry.first] = entry.second()->toSharedImage(); + return result; +} + +std::map AssetGenerator::generateAllAnimations() +{ + return animationFiles; +} + +AssetGenerator::CanvasPtr AssetGenerator::createAdventureOptionsCleanBackground() +{ auto locator = ImageLocator(ImagePath::builtin("ADVOPTBK"), EImageBlitMode::OPAQUE); std::shared_ptr img = GH.renderHandler().loadImage(locator); @@ -74,20 +98,11 @@ void AssetGenerator::createAdventureOptionsCleanBackground() canvas.draw(img, Point(53, 567), Rect(53, 520, 339, 3)); canvas.draw(img, Point(53, 520), Rect(53, 264, 339, 47)); - image->exportBitmap(*CResourceHandler::get("local")->getResourceName(savePath)); + return image; } -void AssetGenerator::createBigSpellBook() +AssetGenerator::CanvasPtr AssetGenerator::createBigSpellBook() { - std::string filename = "data/SpellBookLarge.png"; - - if(CResourceHandler::get()->existsResource(ResourcePath(filename))) // overridden by mod, no generation - return; - - if(!CResourceHandler::get("local")->createResource(filename)) - return; - ResourcePath savePath(filename, EResType::IMAGE); - auto locator = ImageLocator(ImagePath::builtin("SpelBack"), EImageBlitMode::OPAQUE); std::shared_ptr img = GH.renderHandler().loadImage(locator); @@ -135,21 +150,11 @@ void AssetGenerator::createBigSpellBook() canvas.draw(img, Point(575, 465), Rect(417, 406, 37, 45)); canvas.draw(img, Point(667, 465), Rect(478, 406, 37, 47)); - image->exportBitmap(*CResourceHandler::get("local")->getResourceName(savePath)); + return image; } -void AssetGenerator::createPlayerColoredBackground(const PlayerColor & player) +AssetGenerator::CanvasPtr AssetGenerator::createPlayerColoredBackground(const PlayerColor & player) { - std::string filename = "data/DialogBoxBackground_" + player.toString() + ".png"; - - if(CResourceHandler::get()->existsResource(ResourcePath(filename))) // overridden by mod, no generation - return; - - if(!CResourceHandler::get("local")->createResource(filename)) - return; - - ResourcePath savePath(filename, EResType::IMAGE); - auto locator = ImageLocator(ImagePath::builtin("DiBoxBck"), EImageBlitMode::OPAQUE); std::shared_ptr texture = GH.renderHandler().loadImage(locator); @@ -169,71 +174,44 @@ void AssetGenerator::createPlayerColoredBackground(const PlayerColor & player) assert(player.isValidPlayer()); if (!player.isValidPlayer()) - { - logGlobal->error("Unable to colorize to invalid player color %d!", player.getNum()); - return; - } + throw std::runtime_error("Unable to colorize to invalid player color" + std::to_string(player.getNum())); texture->adjustPalette(filters[player.getNum()], 0); - texture->exportBitmap(*CResourceHandler::get("local")->getResourceName(savePath)); + + auto image = GH.renderHandler().createImage(texture->dimensions(), CanvasScalingPolicy::IGNORE); + Canvas canvas = image->getCanvas(); + canvas.draw(texture, Point(0,0)); + + return image; } -void AssetGenerator::createCombatUnitNumberWindow() +AssetGenerator::CanvasPtr AssetGenerator::createCombatUnitNumberWindow(float multR, float multG, float multB) { - std::string filenameToSave = "data/combatUnitNumberWindow"; - - ResourcePath savePathDefault(filenameToSave + "Default.png", EResType::IMAGE); - ResourcePath savePathNeutral(filenameToSave + "Neutral.png", EResType::IMAGE); - ResourcePath savePathPositive(filenameToSave + "Positive.png", EResType::IMAGE); - ResourcePath savePathNegative(filenameToSave + "Negative.png", EResType::IMAGE); - - if(CResourceHandler::get()->existsResource(savePathDefault)) // overridden by mod, no generation - return; - - if(!CResourceHandler::get("local")->createResource(savePathDefault.getOriginalName() + ".png") || - !CResourceHandler::get("local")->createResource(savePathNeutral.getOriginalName() + ".png") || - !CResourceHandler::get("local")->createResource(savePathPositive.getOriginalName() + ".png") || - !CResourceHandler::get("local")->createResource(savePathNegative.getOriginalName() + ".png")) - return; - auto locator = ImageLocator(ImagePath::builtin("CMNUMWIN"), EImageBlitMode::OPAQUE); locator.layer = EImageBlitMode::OPAQUE; std::shared_ptr texture = GH.renderHandler().loadImage(locator); - static const auto shifterNormal = ColorFilter::genRangeShifter( 0.f, 0.f, 0.f, 0.6f, 0.2f, 1.0f ); - static const auto shifterPositive = ColorFilter::genRangeShifter( 0.f, 0.f, 0.f, 0.2f, 1.0f, 0.2f ); - static const auto shifterNegative = ColorFilter::genRangeShifter( 0.f, 0.f, 0.f, 1.0f, 0.2f, 0.2f ); - static const auto shifterNeutral = ColorFilter::genRangeShifter( 0.f, 0.f, 0.f, 1.0f, 1.0f, 0.2f ); + const auto shifter= ColorFilter::genRangeShifter(0.f, 0.f, 0.f, multR, multG, multB); // do not change border color static const int32_t ignoredMask = 1 << 26; - texture->adjustPalette(shifterNormal, ignoredMask); - texture->exportBitmap(*CResourceHandler::get("local")->getResourceName(savePathDefault)); - texture->adjustPalette(shifterPositive, ignoredMask); - texture->exportBitmap(*CResourceHandler::get("local")->getResourceName(savePathPositive)); - texture->adjustPalette(shifterNegative, ignoredMask); - texture->exportBitmap(*CResourceHandler::get("local")->getResourceName(savePathNegative)); - texture->adjustPalette(shifterNeutral, ignoredMask); - texture->exportBitmap(*CResourceHandler::get("local")->getResourceName(savePathNeutral)); + texture->adjustPalette(shifter, ignoredMask); + + auto image = GH.renderHandler().createImage(texture->dimensions(), CanvasScalingPolicy::IGNORE); + Canvas canvas = image->getCanvas(); + canvas.draw(texture, Point(0,0)); + + return image; } -void AssetGenerator::createCampaignBackground() +AssetGenerator::CanvasPtr AssetGenerator::createCampaignBackground() { - std::string filename = "data/CampaignBackground8.png"; - - if(CResourceHandler::get()->existsResource(ResourcePath(filename))) // overridden by mod, no generation - return; - - if(!CResourceHandler::get("local")->createResource(filename)) - return; - ResourcePath savePath(filename, EResType::IMAGE); - auto locator = ImageLocator(ImagePath::builtin("CAMPBACK"), EImageBlitMode::OPAQUE); std::shared_ptr img = GH.renderHandler().loadImage(locator); - auto image = GH.renderHandler().createImage(Point(800, 600), CanvasScalingPolicy::IGNORE); + auto image = GH.renderHandler().createImage(Point(200, 116), CanvasScalingPolicy::IGNORE); Canvas canvas = image->getCanvas(); canvas.draw(img, Point(0, 0), Rect(0, 0, 800, 600)); @@ -264,171 +242,112 @@ void AssetGenerator::createCampaignBackground() std::shared_ptr imgSkull = GH.renderHandler().loadImage(locatorSkull); canvas.draw(imgSkull, Point(562, 509), Rect(178, 108, 43, 19)); - image->exportBitmap(*CResourceHandler::get("local")->getResourceName(savePath)); + return image; } -void AssetGenerator::createChroniclesCampaignImages() +AssetGenerator::CanvasPtr AssetGenerator::createChroniclesCampaignImages(int chronicle) { - for(int i = 1; i < 9; i++) + auto imgPathBg = ImagePath::builtin("data/chronicles_" + std::to_string(chronicle) + "/GamSelBk"); + auto locator = ImageLocator(imgPathBg, EImageBlitMode::OPAQUE); + + std::shared_ptr img = GH.renderHandler().loadImage(locator); + auto image = GH.renderHandler().createImage(Point(800, 600), CanvasScalingPolicy::IGNORE); + Canvas canvas = image->getCanvas(); + + std::array sourceRect = { + Rect(149, 144, 200, 116), + Rect(156, 150, 200, 116), + Rect(171, 153, 200, 116), + Rect(35, 358, 200, 116), + Rect(216, 248, 200, 116), + Rect(58, 234, 200, 116), + Rect(184, 219, 200, 116), + Rect(268, 210, 200, 116), + }; + + canvas.draw(img, Point(0, 0), sourceRect.at(chronicle-1)); + + if (chronicle == 8) { - std::string filename = "data/CampaignHc" + std::to_string(i) + "Image.png"; - - if(CResourceHandler::get()->existsResource(ResourcePath(filename))) // overridden by mod, no generation - continue; - - auto imgPathBg = ImagePath::builtin("data/chronicles_" + std::to_string(i) + "/GamSelBk"); - if(!CResourceHandler::get()->existsResource(imgPathBg)) // Chronicle episode not installed - continue; - - if(!CResourceHandler::get("local")->createResource(filename)) - continue; - ResourcePath savePath(filename, EResType::IMAGE); - - auto locator = ImageLocator(imgPathBg, EImageBlitMode::OPAQUE); - - std::shared_ptr img = GH.renderHandler().loadImage(locator); - auto image = GH.renderHandler().createImage(Point(800, 600), CanvasScalingPolicy::IGNORE); - Canvas canvas = image->getCanvas(); - - switch (i) - { - case 1: - canvas.draw(img, Point(0, 0), Rect(149, 144, 200, 116)); - break; - case 2: - canvas.draw(img, Point(0, 0), Rect(156, 150, 200, 116)); - break; - case 3: - canvas.draw(img, Point(0, 0), Rect(171, 153, 200, 116)); - break; - case 4: - canvas.draw(img, Point(0, 0), Rect(35, 358, 200, 116)); - break; - case 5: - canvas.draw(img, Point(0, 0), Rect(216, 248, 200, 116)); - break; - case 6: - canvas.draw(img, Point(0, 0), Rect(58, 234, 200, 116)); - break; - case 7: - canvas.draw(img, Point(0, 0), Rect(184, 219, 200, 116)); - break; - case 8: - canvas.draw(img, Point(0, 0), Rect(268, 210, 200, 116)); - - //skull - auto locatorSkull = ImageLocator(ImagePath::builtin("CampSP1"), EImageBlitMode::OPAQUE); - std::shared_ptr imgSkull = GH.renderHandler().loadImage(locatorSkull); - canvas.draw(imgSkull, Point(162, 94), Rect(162, 94, 41, 22)); - canvas.draw(img, Point(162, 94), Rect(424, 304, 14, 4)); - canvas.draw(img, Point(162, 98), Rect(424, 308, 10, 4)); - canvas.draw(img, Point(158, 102), Rect(424, 312, 10, 4)); - canvas.draw(img, Point(154, 106), Rect(424, 316, 10, 4)); - break; - } - - image->exportBitmap(*CResourceHandler::get("local")->getResourceName(savePath)); + //skull + auto locatorSkull = ImageLocator(ImagePath::builtin("CampSP1"), EImageBlitMode::OPAQUE); + std::shared_ptr imgSkull = GH.renderHandler().loadImage(locatorSkull); + canvas.draw(imgSkull, Point(162, 94), Rect(162, 94, 41, 22)); + canvas.draw(img, Point(162, 94), Rect(424, 304, 14, 4)); + canvas.draw(img, Point(162, 98), Rect(424, 308, 10, 4)); + canvas.draw(img, Point(158, 102), Rect(424, 312, 10, 4)); + canvas.draw(img, Point(154, 106), Rect(424, 316, 10, 4)); } + + return image; } void AssetGenerator::createPaletteShiftedSprites() { - std::vector tiles; - std::vector>> paletteAnimations; for(auto entity : VLC->terrainTypeHandler->objects) { - if(entity->paletteAnimation.size()) - { - tiles.push_back(entity->tilesFilename.getName()); - std::vector> tmpAnim; - for(auto & animEntity : entity->paletteAnimation) - tmpAnim.push_back(animEntity); - paletteAnimations.push_back(tmpAnim); - } + if(entity->paletteAnimation.empty()) + continue; + + std::vector paletteShifts; + for(auto & animEntity : entity->paletteAnimation) + paletteShifts.push_back({animEntity.start, animEntity.length}); + + generatePaletteShiftedAnimation(entity->tilesFilename, paletteShifts); + } for(auto entity : VLC->riverTypeHandler->objects) { - if(entity->paletteAnimation.size()) - { - tiles.push_back(entity->tilesFilename.getName()); - std::vector> tmpAnim; - for(auto & animEntity : entity->paletteAnimation) - tmpAnim.push_back(animEntity); - paletteAnimations.push_back(tmpAnim); - } - } + if(entity->paletteAnimation.empty()) + continue; - for(int i = 0; i < tiles.size(); i++) - { - auto sprite = tiles[i]; + std::vector paletteShifts; + for(auto & animEntity : entity->paletteAnimation) + paletteShifts.push_back({animEntity.start, animEntity.length}); - JsonNode config; - config["basepath"].String() = sprite + "_Shifted/"; - config["images"].Vector(); - - auto filename = AnimationPath::builtin(sprite).addPrefix("SPRITES/"); - auto filenameNew = AnimationPath::builtin(sprite + "_Shifted").addPrefix("SPRITES/"); - - if(CResourceHandler::get()->existsResource(ResourcePath(filenameNew.getName(), EResType::JSON))) // overridden by mod, no generation - return; - - auto anim = GH.renderHandler().loadAnimation(filename, EImageBlitMode::COLORKEY); - for(int j = 0; j < anim->size(); j++) - { - int maxLen = 1; - for(int k = 0; k < paletteAnimations[i].size(); k++) - { - auto element = paletteAnimations[i][k]; - if(std::holds_alternative(element)) - maxLen = std::lcm(maxLen, std::get(element).length); - else - maxLen = std::lcm(maxLen, std::get(element).length); - } - for(int l = 0; l < maxLen; l++) - { - std::string spriteName = sprite + boost::str(boost::format("%02d") % j) + "_" + std::to_string(l) + ".png"; - std::string filenameNewImg = "sprites/" + sprite + "_Shifted" + "/" + spriteName; - ResourcePath savePath(filenameNewImg, EResType::IMAGE); - - if(!CResourceHandler::get("local")->createResource(filenameNewImg)) - return; - - auto imgLoc = anim->getImageLocator(j, 0); - auto img = GH.renderHandler().loadImage(imgLoc); - for(int k = 0; k < paletteAnimations[i].size(); k++) - { - auto element = paletteAnimations[i][k]; - if(std::holds_alternative(element)) - { - auto tmp = std::get(element); - img->shiftPalette(tmp.start, tmp.length, l % tmp.length); - } - else - { - auto tmp = std::get(element); - img->shiftPalette(tmp.start, tmp.length, l % tmp.length); - } - } - - auto image = GH.renderHandler().createImage(Point(32, 32), CanvasScalingPolicy::IGNORE); - Canvas canvas = image->getCanvas(); - canvas.draw(img, Point((32 - img->dimensions().x) / 2, (32 - img->dimensions().y) / 2)); - image->exportBitmap(*CResourceHandler::get("local")->getResourceName(savePath)); - - JsonNode node(JsonMap{ - { "group", JsonNode(l) }, - { "frame", JsonNode(j) }, - { "file", JsonNode(spriteName) } - }); - config["images"].Vector().push_back(node); - } - } - - ResourcePath savePath(filenameNew.getOriginalName(), EResType::JSON); - if(!CResourceHandler::get("local")->createResource(filenameNew.getOriginalName() + ".json")) - return; - - std::fstream file(CResourceHandler::get("local")->getResourceName(savePath)->c_str(), std::ofstream::out | std::ofstream::trunc); - file << config.toString(); + generatePaletteShiftedAnimation(entity->tilesFilename, paletteShifts); } } + +void AssetGenerator::generatePaletteShiftedAnimation(const AnimationPath & sprite, const std::vector & paletteAnimations) +{ + AnimationLayoutMap layout; + + auto animation = GH.renderHandler().loadAnimation(sprite, EImageBlitMode::COLORKEY); + + int paletteTransformLength = 1; + for (const auto & transform : paletteAnimations) + paletteTransformLength = std::lcm(paletteTransformLength, transform.length); + + for(int tileIndex = 0; tileIndex < animation->size(); tileIndex++) + { + for(int paletteIndex = 0; paletteIndex < paletteTransformLength; paletteIndex++) + { + ImagePath spriteName = ImagePath::builtin(sprite.getName() + boost::str(boost::format("%02d") % tileIndex) + "_" + std::to_string(paletteIndex) + ".png"); + layout[paletteIndex].push_back(ImageLocator(spriteName, EImageBlitMode::SIMPLE)); + + imageFiles[spriteName] = [=](){ return createPaletteShiftedImage(sprite, paletteAnimations, tileIndex, paletteIndex);}; + } + } + + AnimationPath shiftedPath = AnimationPath::builtin("SPRITES/" + sprite.getName() + "_SHIFTED"); + animationFiles[shiftedPath] = layout; +} + +AssetGenerator::CanvasPtr AssetGenerator::createPaletteShiftedImage(const AnimationPath & source, const std::vector & palette, int frameIndex, int paletteShiftCounter) +{ + auto animation = GH.renderHandler().loadAnimation(source, EImageBlitMode::COLORKEY); + + auto imgLoc = animation->getImageLocator(frameIndex, 0); + auto img = GH.renderHandler().loadImage(imgLoc); + + for(const auto & element : palette) + img->shiftPalette(element.start, element.length, paletteShiftCounter % element.length); + + auto image = GH.renderHandler().createImage(Point(32, 32), CanvasScalingPolicy::IGNORE); + Canvas canvas = image->getCanvas(); + canvas.draw(img, Point((32 - img->dimensions().x) / 2, (32 - img->dimensions().y) / 2)); + + return image; + +} diff --git a/client/render/AssetGenerator.h b/client/render/AssetGenerator.h index 51584754a..c0646b727 100644 --- a/client/render/AssetGenerator.h +++ b/client/render/AssetGenerator.h @@ -9,20 +9,53 @@ */ #pragma once +#include "ImageLocator.h" + VCMI_LIB_NAMESPACE_BEGIN class PlayerColor; VCMI_LIB_NAMESPACE_END +class ISharedImage; +class CanvasImage; + class AssetGenerator { public: - static void clear(); - static void generateAll(); - static void createAdventureOptionsCleanBackground(); - static void createBigSpellBook(); - static void createPlayerColoredBackground(const PlayerColor & player); - static void createCombatUnitNumberWindow(); - static void createCampaignBackground(); - static void createChroniclesCampaignImages(); - static void createPaletteShiftedSprites(); + using AnimationLayoutMap = std::map>; + using CanvasPtr = std::shared_ptr; + + AssetGenerator(); + + void initialize(); + + std::shared_ptr generateImage(const ImagePath & image); + + std::map> generateAllImages(); + std::map generateAllAnimations(); + +private: + using ImageGenerationFunctor = std::function; + + struct PaletteAnimation + { + /// index of first color to cycle + int32_t start; + /// total numbers of colors to cycle + int32_t length; + }; + + std::map imageFiles; + std::map animationFiles; + + CanvasPtr createAdventureOptionsCleanBackground(); + CanvasPtr createBigSpellBook(); + CanvasPtr createPlayerColoredBackground(const PlayerColor & player); + CanvasPtr createCombatUnitNumberWindow(float multR, float multG, float multB); + CanvasPtr createCampaignBackground(); + CanvasPtr createChroniclesCampaignImages(int chronicle); + CanvasPtr createPaletteShiftedImage(const AnimationPath & source, const std::vector & animation, int frameIndex, int paletteShiftCounter); + + void createPaletteShiftedSprites(); + void generatePaletteShiftedAnimation(const AnimationPath & source, const std::vector & animation); + }; diff --git a/client/render/CanvasImage.cpp b/client/render/CanvasImage.cpp index 07e9b3474..dc6d619bb 100644 --- a/client/render/CanvasImage.cpp +++ b/client/render/CanvasImage.cpp @@ -14,6 +14,7 @@ #include "../render/IScreenHandler.h" #include "../renderSDL/SDL_Extensions.h" #include "../renderSDL/SDLImageScaler.h" +#include "../renderSDL/SDLImage.h" #include #include @@ -61,3 +62,8 @@ Point CanvasImage::dimensions() const { return {surface->w, surface->h}; } + +std::shared_ptr CanvasImage::toSharedImage() +{ + return std::make_shared(surface); +} diff --git a/client/render/CanvasImage.h b/client/render/CanvasImage.h index 18c342832..095a4276b 100644 --- a/client/render/CanvasImage.h +++ b/client/render/CanvasImage.h @@ -34,6 +34,8 @@ public: void shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove) override{}; void adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask) override{}; + std::shared_ptr toSharedImage(); + private: SDL_Surface * surface; CanvasScalingPolicy scalingPolicy; diff --git a/client/render/IRenderHandler.h b/client/render/IRenderHandler.h index cefd02890..2210bd797 100644 --- a/client/render/IRenderHandler.h +++ b/client/render/IRenderHandler.h @@ -50,4 +50,6 @@ public: /// Returns font with specified identifer virtual std::shared_ptr loadFont(EFonts font) = 0; + + virtual void exportGeneratedAssets() = 0; }; diff --git a/client/renderSDL/RenderHandler.cpp b/client/renderSDL/RenderHandler.cpp index 285c539cd..92469f4d3 100644 --- a/client/renderSDL/RenderHandler.cpp +++ b/client/renderSDL/RenderHandler.cpp @@ -16,6 +16,7 @@ #include "../gui/CGuiHandler.h" +#include "../render/AssetGenerator.h" #include "../render/CAnimation.h" #include "../render/CanvasImage.h" #include "../render/CDefFile.h" @@ -43,6 +44,13 @@ #include #include +RenderHandler::RenderHandler() + :assetGenerator(std::make_unique()) +{ +} + +RenderHandler::~RenderHandler() = default; + std::shared_ptr RenderHandler::getAnimationFile(const AnimationPath & path) { AnimationPath actualPath = boost::starts_with(path.getName(), "SPRITES") ? path : path.addPrefix("SPRITES/"); @@ -201,12 +209,28 @@ std::shared_ptr RenderHandler::loadImageImpl(const ImageLoc return scaledImage; } -std::shared_ptr RenderHandler::loadImageFromFileUncached(const ImageLocator & locator) +std::shared_ptr RenderHandler::loadImageFromFileUncached(const ImageLocator & locator) { if(locator.image) { - // TODO: create EmptySharedImage class that will be instantiated if image does not exists or fails to load - return std::make_shared(*locator.image); + auto imagePath = *locator.image; + auto imagePathSprites = imagePath.addPrefix("SPRITES/"); + auto imagePathData = imagePath.addPrefix("DATA/"); + + if(CResourceHandler::get()->existsResource(imagePathSprites)) + return std::make_shared(imagePathSprites); + + if(CResourceHandler::get()->existsResource(imagePathData)) + return std::make_shared(imagePathData); + + if(CResourceHandler::get()->existsResource(imagePath)) + return std::make_shared(imagePath); + + auto generated = assetGenerator->generateImage(imagePath); + if (generated) + return generated; + + return std::make_shared(ImagePath::builtin("DEFAULT")); } if(locator.defFile) @@ -423,6 +447,10 @@ static void detectOverlappingBuildings(RenderHandler * renderHandler, const Fact void RenderHandler::onLibraryLoadingFinished(const Services * services) { + assert(animationLayouts.empty()); + assetGenerator->initialize(); + animationLayouts = assetGenerator->generateAllAnimations(); + addImageListEntries(services->creatures()); addImageListEntries(services->heroTypes()); addImageListEntries(services->artifacts()); @@ -469,3 +497,9 @@ std::shared_ptr RenderHandler::loadFont(EFonts font) fonts[font] = loadedFont; return loadedFont; } + +void RenderHandler::exportGeneratedAssets() +{ + for (const auto & entry : assetGenerator->generateAllImages()) + entry.second->exportBitmap(VCMIDirs::get().userDataPath() / "Generated" / (entry.first.getOriginalName() + ".png"), nullptr); +} diff --git a/client/renderSDL/RenderHandler.h b/client/renderSDL/RenderHandler.h index 4a7e73c6a..b795e5a4e 100644 --- a/client/renderSDL/RenderHandler.h +++ b/client/renderSDL/RenderHandler.h @@ -18,8 +18,9 @@ VCMI_LIB_NAMESPACE_END class CDefFile; class SDLImageShared; class ScalableImageShared; +class AssetGenerator; -class RenderHandler : public IRenderHandler +class RenderHandler final : public IRenderHandler { using AnimationLayoutMap = std::map>; @@ -27,6 +28,7 @@ class RenderHandler : public IRenderHandler std::map animationLayouts; std::map> imageFiles; std::map> fonts; + std::unique_ptr assetGenerator; std::shared_ptr getAnimationFile(const AnimationPath & path); AnimationLayoutMap & getAnimationLayout(const AnimationPath & path, int scalingFactor, EImageBlitMode mode); @@ -38,13 +40,15 @@ class RenderHandler : public IRenderHandler std::shared_ptr loadImageImpl(const ImageLocator & config); - std::shared_ptr loadImageFromFileUncached(const ImageLocator & locator); + std::shared_ptr loadImageFromFileUncached(const ImageLocator & locator); ImageLocator getLocatorForAnimationFrame(const AnimationPath & path, int frame, int group, int scaling, EImageBlitMode mode); int getScalingFactor() const; public: + RenderHandler(); + ~RenderHandler(); // IRenderHandler implementation void onLibraryLoadingFinished(const Services * services) override; @@ -61,4 +65,6 @@ public: /// Returns font with specified identifer std::shared_ptr loadFont(EFonts font) override; + + void exportGeneratedAssets() override; }; diff --git a/client/renderSDL/SDLImage.cpp b/client/renderSDL/SDLImage.cpp index 16adecf69..d584f4683 100644 --- a/client/renderSDL/SDLImage.cpp +++ b/client/renderSDL/SDLImage.cpp @@ -306,6 +306,10 @@ std::shared_ptr SDLImageShared::scaleTo(const Point & size, void SDLImageShared::exportBitmap(const boost::filesystem::path& path, SDL_Palette * palette) const { + auto directory = path; + directory.remove_filename(); + boost::filesystem::create_directories(directory); + assert(upscalingInProgress == false); if (!surf) return; diff --git a/client/widgets/Images.cpp b/client/widgets/Images.cpp index 6ebabdc0f..6693300e3 100644 --- a/client/widgets/Images.cpp +++ b/client/widgets/Images.cpp @@ -13,7 +13,6 @@ #include "MiscWidgets.h" #include "../gui/CGuiHandler.h" -#include "../render/AssetGenerator.h" #include "../render/IImage.h" #include "../render/IRenderHandler.h" #include "../render/CAnimation.h" @@ -184,8 +183,6 @@ FilledTexturePlayerColored::FilledTexturePlayerColored(Rect position) void FilledTexturePlayerColored::setPlayerColor(PlayerColor player) { - AssetGenerator::createPlayerColoredBackground(player); - ImagePath imagePath = ImagePath::builtin("DialogBoxBackground_" + player.toString() + ".bmp"); texture = GH.renderHandler().loadImage(imagePath, EImageBlitMode::COLORKEY); diff --git a/client/windows/CSpellWindow.cpp b/client/windows/CSpellWindow.cpp index 56168d707..f3033e864 100644 --- a/client/windows/CSpellWindow.cpp +++ b/client/windows/CSpellWindow.cpp @@ -32,7 +32,6 @@ #include "../widgets/Buttons.h" #include "../widgets/VideoWidget.h" #include "../adventureMap/AdventureMapInterface.h" -#include "../render/AssetGenerator.h" #include "../../CCallback.h" @@ -118,7 +117,6 @@ CSpellWindow::CSpellWindow(const CGHeroInstance * _myHero, CPlayerInterface * _m if(isBigSpellbook) { - AssetGenerator::createBigSpellBook(); background = std::make_shared(ImagePath::builtin("SpellBookLarge"), 0, 0); updateShadow(); } diff --git a/clientapp/EntryPoint.cpp b/clientapp/EntryPoint.cpp index b4081ce66..a0cbd7b16 100644 --- a/clientapp/EntryPoint.cpp +++ b/clientapp/EntryPoint.cpp @@ -27,7 +27,6 @@ #include "../client/media/CMusicHandler.h" #include "../client/media/CSoundHandler.h" #include "../client/media/CVideoHandler.h" -#include "../client/render/AssetGenerator.h" #include "../client/render/Graphics.h" #include "../client/render/IRenderHandler.h" #include "../client/render/IScreenHandler.h" @@ -235,8 +234,6 @@ int main(int argc, char * argv[]) logGlobal->info("Creating console and configuring logger: %d ms", pomtime.getDiff()); logGlobal->info("The log file will be saved to %s", logPath); - AssetGenerator::clear(); - // Init filesystem and settings try {