1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-06-10 23:48:04 +02:00

In-memory assets generation

All assets generation (large spellbook, terrain animations, etc) are now
done in memory and used as it, without saving to disk.

This should slightly improve load times since there is no encode png /
decode png, and should help with avoiding strange bug when vcmi fails to
load recently saved assets.

If needed, such assets can be force-dumped on disk using already
existing console command
This commit is contained in:
Ivan Savenko 2025-01-29 10:03:58 +00:00
parent 1bf1aa8489
commit cca4c0888c
16 changed files with 257 additions and 272 deletions

View File

@ -18,7 +18,6 @@
#include "gui/CGuiHandler.h" #include "gui/CGuiHandler.h"
#include "gui/WindowHandler.h" #include "gui/WindowHandler.h"
#include "render/IRenderHandler.h" #include "render/IRenderHandler.h"
#include "render/AssetGenerator.h"
#include "ClientNetPackVisitors.h" #include "ClientNetPackVisitors.h"
#include "../lib/CConfigHandler.h" #include "../lib/CConfigHandler.h"
#include "../lib/gameState/CGameState.h" #include "../lib/gameState/CGameState.h"
@ -510,7 +509,7 @@ void ClientCommandManager::handleVsLog(std::istringstream & singleWordBuffer)
void ClientCommandManager::handleGenerateAssets() void ClientCommandManager::handleGenerateAssets()
{ {
AssetGenerator::generateAll(); GH.renderHandler().exportGeneratedAssets();
printCommandMessage("All assets generated"); printCommandMessage("All assets generated");
} }

View File

@ -34,7 +34,6 @@
#include "../render/IImage.h" #include "../render/IImage.h"
#include "../render/IRenderHandler.h" #include "../render/IRenderHandler.h"
#include "../render/IScreenHandler.h" #include "../render/IScreenHandler.h"
#include "../render/AssetGenerator.h"
#include "../CMT.h" #include "../CMT.h"
#include "../PlayerLocalState.h" #include "../PlayerLocalState.h"
#include "../CPlayerInterface.h" #include "../CPlayerInterface.h"
@ -65,8 +64,6 @@ AdventureMapInterface::AdventureMapInterface():
pos.w = GH.screenDimensions().x; pos.w = GH.screenDimensions().x;
pos.h = GH.screenDimensions().y; pos.h = GH.screenDimensions().y;
AssetGenerator::createPaletteShiftedSprites();
shortcuts = std::make_shared<AdventureMapShortcuts>(*this); shortcuts = std::make_shared<AdventureMapShortcuts>(*this);
widget = std::make_shared<AdventureMapWidget>(shortcuts); widget = std::make_shared<AdventureMapWidget>(shortcuts);

View File

@ -27,7 +27,6 @@
#include "../gui/CGuiHandler.h" #include "../gui/CGuiHandler.h"
#include "../gui/WindowHandler.h" #include "../gui/WindowHandler.h"
#include "../media/ISoundPlayer.h" #include "../media/ISoundPlayer.h"
#include "../render/AssetGenerator.h"
#include "../render/Colors.h" #include "../render/Colors.h"
#include "../render/Canvas.h" #include "../render/Canvas.h"
#include "../render/IRenderHandler.h" #include "../render/IRenderHandler.h"
@ -80,7 +79,6 @@ BattleStacksController::BattleStacksController(BattleInterface & owner):
stackToActivate(nullptr), stackToActivate(nullptr),
animIDhelper(0) animIDhelper(0)
{ {
AssetGenerator::createCombatUnitNumberWindow();
//preparing graphics for displaying amounts of creatures //preparing graphics for displaying amounts of creatures
amountNormal = GH.renderHandler().loadImage(ImagePath::builtin("combatUnitNumberWindowDefault"), EImageBlitMode::COLORKEY); amountNormal = GH.renderHandler().loadImage(ImagePath::builtin("combatUnitNumberWindowDefault"), EImageBlitMode::COLORKEY);
amountPositive = GH.renderHandler().loadImage(ImagePath::builtin("combatUnitNumberWindowPositive"), EImageBlitMode::COLORKEY); amountPositive = GH.renderHandler().loadImage(ImagePath::builtin("combatUnitNumberWindowPositive"), EImageBlitMode::COLORKEY);

View File

@ -18,7 +18,6 @@
#include "../widgets/TextControls.h" #include "../widgets/TextControls.h"
#include "../CServerHandler.h" #include "../CServerHandler.h"
#include "../CGameInfo.h" #include "../CGameInfo.h"
#include "../render/AssetGenerator.h"
#include "../../lib/StartInfo.h" #include "../../lib/StartInfo.h"
#include "../../lib/texts/CGeneralTextHandler.h" #include "../../lib/texts/CGeneralTextHandler.h"
@ -69,8 +68,6 @@ std::vector<SimturnsInfo> OptionsTabBase::getSimturnsPresets() const
OptionsTabBase::OptionsTabBase(const JsonPath & configPath) OptionsTabBase::OptionsTabBase(const JsonPath & configPath)
{ {
AssetGenerator::createAdventureOptionsCleanBackground();
recActions = 0; recActions = 0;
auto setTimerPresetCallback = [this](int index){ auto setTimerPresetCallback = [this](int index){

View File

@ -38,7 +38,6 @@
#include "../widgets/VideoWidget.h" #include "../widgets/VideoWidget.h"
#include "../windows/InfoWindows.h" #include "../windows/InfoWindows.h"
#include "../CServerHandler.h" #include "../CServerHandler.h"
#include "../render/AssetGenerator.h"
#include "../CGameInfo.h" #include "../CGameInfo.h"
#include "../CPlayerInterface.h" #include "../CPlayerInterface.h"
@ -428,9 +427,6 @@ void CMainMenu::openCampaignScreen(std::string name)
{ {
auto const & config = CMainMenuConfig::get().getCampaigns(); auto const & config = CMainMenuConfig::get().getCampaigns();
AssetGenerator::createCampaignBackground();
AssetGenerator::createChroniclesCampaignImages();
if(!vstd::contains(config.Struct(), name)) if(!vstd::contains(config.Struct(), name))
{ {
logGlobal->error("Unknown campaign set: %s", name); logGlobal->error("Unknown campaign set: %s", name);

View File

@ -29,36 +29,60 @@
#include "../lib/RoadHandler.h" #include "../lib/RoadHandler.h"
#include "../lib/TerrainHandler.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) // clear to avoid non updated sprites after mod change (if base imnages are used)
if(boost::filesystem::is_directory(VCMIDirs::get().userDataPath() / "Generated")) if(boost::filesystem::is_directory(VCMIDirs::get().userDataPath() / "Generated"))
boost::filesystem::remove_all(VCMIDirs::get().userDataPath() / "Generated"); boost::filesystem::remove_all(VCMIDirs::get().userDataPath() / "Generated");
}
void AssetGenerator::generateAll() imageFiles[ImagePath::builtin("AdventureOptionsBackgroundClear.png")] = [this](){ return createAdventureOptionsCleanBackground();};
{ imageFiles[ImagePath::builtin("SpellBookLarge.png")] = [this](){ return createBigSpellBook();};
createBigSpellBook();
createAdventureOptionsCleanBackground(); imageFiles[ImagePath::builtin("combatUnitNumberWindowDefault.png")] = [this](){ return createCombatUnitNumberWindow(0.6f, 0.2f, 1.0f);};
for (int i = 0; i < PlayerColor::PLAYER_LIMIT_I; ++i) imageFiles[ImagePath::builtin("combatUnitNumberWindowNeutral.png")] = [this](){ return createCombatUnitNumberWindow(1.0f, 1.0f, 2.0f);};
createPlayerColoredBackground(PlayerColor(i)); imageFiles[ImagePath::builtin("combatUnitNumberWindowPositive.png")] = [this](){ return createCombatUnitNumberWindow(0.2f, 1.0f, 0.2f);};
createCombatUnitNumberWindow(); imageFiles[ImagePath::builtin("combatUnitNumberWindowNegative.png")] = [this](){ return createCombatUnitNumberWindow(1.0f, 0.2f, 0.2f);};
createCampaignBackground();
createChroniclesCampaignImages(); 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(); createPaletteShiftedSprites();
} }
void AssetGenerator::createAdventureOptionsCleanBackground() std::shared_ptr<ISharedImage> 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 std::map<ImagePath, std::shared_ptr<ISharedImage>> AssetGenerator::generateAllImages()
return; {
std::map<ImagePath, std::shared_ptr<ISharedImage>> result;
if(!CResourceHandler::get("local")->createResource(filename)) for (const auto & entry : imageFiles)
return; result[entry.first] = entry.second()->toSharedImage();
ResourcePath savePath(filename, EResType::IMAGE);
return result;
}
std::map<AnimationPath, AssetGenerator::AnimationLayoutMap> AssetGenerator::generateAllAnimations()
{
return animationFiles;
}
AssetGenerator::CanvasPtr AssetGenerator::createAdventureOptionsCleanBackground()
{
auto locator = ImageLocator(ImagePath::builtin("ADVOPTBK"), EImageBlitMode::OPAQUE); auto locator = ImageLocator(ImagePath::builtin("ADVOPTBK"), EImageBlitMode::OPAQUE);
std::shared_ptr<IImage> img = GH.renderHandler().loadImage(locator); std::shared_ptr<IImage> 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, 567), Rect(53, 520, 339, 3));
canvas.draw(img, Point(53, 520), Rect(53, 264, 339, 47)); 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); auto locator = ImageLocator(ImagePath::builtin("SpelBack"), EImageBlitMode::OPAQUE);
std::shared_ptr<IImage> img = GH.renderHandler().loadImage(locator); std::shared_ptr<IImage> 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(575, 465), Rect(417, 406, 37, 45));
canvas.draw(img, Point(667, 465), Rect(478, 406, 37, 47)); 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); auto locator = ImageLocator(ImagePath::builtin("DiBoxBck"), EImageBlitMode::OPAQUE);
std::shared_ptr<IImage> texture = GH.renderHandler().loadImage(locator); std::shared_ptr<IImage> texture = GH.renderHandler().loadImage(locator);
@ -169,71 +174,44 @@ void AssetGenerator::createPlayerColoredBackground(const PlayerColor & player)
assert(player.isValidPlayer()); assert(player.isValidPlayer());
if (!player.isValidPlayer()) if (!player.isValidPlayer())
{ throw std::runtime_error("Unable to colorize to invalid player color" + std::to_string(player.getNum()));
logGlobal->error("Unable to colorize to invalid player color %d!", player.getNum());
return;
}
texture->adjustPalette(filters[player.getNum()], 0); 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); auto locator = ImageLocator(ImagePath::builtin("CMNUMWIN"), EImageBlitMode::OPAQUE);
locator.layer = EImageBlitMode::OPAQUE; locator.layer = EImageBlitMode::OPAQUE;
std::shared_ptr<IImage> texture = GH.renderHandler().loadImage(locator); std::shared_ptr<IImage> texture = GH.renderHandler().loadImage(locator);
static const auto shifterNormal = ColorFilter::genRangeShifter( 0.f, 0.f, 0.f, 0.6f, 0.2f, 1.0f ); const auto shifter= ColorFilter::genRangeShifter(0.f, 0.f, 0.f, multR, multG, multB);
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 );
// do not change border color // do not change border color
static const int32_t ignoredMask = 1 << 26; static const int32_t ignoredMask = 1 << 26;
texture->adjustPalette(shifterNormal, ignoredMask); texture->adjustPalette(shifter, ignoredMask);
texture->exportBitmap(*CResourceHandler::get("local")->getResourceName(savePathDefault));
texture->adjustPalette(shifterPositive, ignoredMask); auto image = GH.renderHandler().createImage(texture->dimensions(), CanvasScalingPolicy::IGNORE);
texture->exportBitmap(*CResourceHandler::get("local")->getResourceName(savePathPositive)); Canvas canvas = image->getCanvas();
texture->adjustPalette(shifterNegative, ignoredMask); canvas.draw(texture, Point(0,0));
texture->exportBitmap(*CResourceHandler::get("local")->getResourceName(savePathNegative));
texture->adjustPalette(shifterNeutral, ignoredMask); return image;
texture->exportBitmap(*CResourceHandler::get("local")->getResourceName(savePathNeutral));
} }
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); auto locator = ImageLocator(ImagePath::builtin("CAMPBACK"), EImageBlitMode::OPAQUE);
std::shared_ptr<IImage> img = GH.renderHandler().loadImage(locator); std::shared_ptr<IImage> 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 canvas = image->getCanvas();
canvas.draw(img, Point(0, 0), Rect(0, 0, 800, 600)); canvas.draw(img, Point(0, 0), Rect(0, 0, 800, 600));
@ -264,58 +242,33 @@ void AssetGenerator::createCampaignBackground()
std::shared_ptr<IImage> imgSkull = GH.renderHandler().loadImage(locatorSkull); std::shared_ptr<IImage> imgSkull = GH.renderHandler().loadImage(locatorSkull);
canvas.draw(imgSkull, Point(562, 509), Rect(178, 108, 43, 19)); 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");
{
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); auto locator = ImageLocator(imgPathBg, EImageBlitMode::OPAQUE);
std::shared_ptr<IImage> img = GH.renderHandler().loadImage(locator); std::shared_ptr<IImage> img = GH.renderHandler().loadImage(locator);
auto image = GH.renderHandler().createImage(Point(800, 600), CanvasScalingPolicy::IGNORE); auto image = GH.renderHandler().createImage(Point(800, 600), CanvasScalingPolicy::IGNORE);
Canvas canvas = image->getCanvas(); Canvas canvas = image->getCanvas();
switch (i) std::array sourceRect = {
{ Rect(149, 144, 200, 116),
case 1: Rect(156, 150, 200, 116),
canvas.draw(img, Point(0, 0), Rect(149, 144, 200, 116)); Rect(171, 153, 200, 116),
break; Rect(35, 358, 200, 116),
case 2: Rect(216, 248, 200, 116),
canvas.draw(img, Point(0, 0), Rect(156, 150, 200, 116)); Rect(58, 234, 200, 116),
break; Rect(184, 219, 200, 116),
case 3: Rect(268, 210, 200, 116),
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));
canvas.draw(img, Point(0, 0), sourceRect.at(chronicle-1));
if (chronicle == 8)
{
//skull //skull
auto locatorSkull = ImageLocator(ImagePath::builtin("CampSP1"), EImageBlitMode::OPAQUE); auto locatorSkull = ImageLocator(ImagePath::builtin("CampSP1"), EImageBlitMode::OPAQUE);
std::shared_ptr<IImage> imgSkull = GH.renderHandler().loadImage(locatorSkull); std::shared_ptr<IImage> imgSkull = GH.renderHandler().loadImage(locatorSkull);
@ -324,111 +277,77 @@ void AssetGenerator::createChroniclesCampaignImages()
canvas.draw(img, Point(162, 98), Rect(424, 308, 10, 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(158, 102), Rect(424, 312, 10, 4));
canvas.draw(img, Point(154, 106), Rect(424, 316, 10, 4)); canvas.draw(img, Point(154, 106), Rect(424, 316, 10, 4));
break;
} }
image->exportBitmap(*CResourceHandler::get("local")->getResourceName(savePath)); return image;
}
} }
void AssetGenerator::createPaletteShiftedSprites() void AssetGenerator::createPaletteShiftedSprites()
{ {
std::vector<std::string> tiles;
std::vector<std::vector<std::variant<TerrainPaletteAnimation, RiverPaletteAnimation>>> paletteAnimations;
for(auto entity : VLC->terrainTypeHandler->objects) for(auto entity : VLC->terrainTypeHandler->objects)
{ {
if(entity->paletteAnimation.size()) if(entity->paletteAnimation.empty())
{ continue;
tiles.push_back(entity->tilesFilename.getName());
std::vector<std::variant<TerrainPaletteAnimation, RiverPaletteAnimation>> tmpAnim; std::vector<PaletteAnimation> paletteShifts;
for(auto & animEntity : entity->paletteAnimation) for(auto & animEntity : entity->paletteAnimation)
tmpAnim.push_back(animEntity); paletteShifts.push_back({animEntity.start, animEntity.length});
paletteAnimations.push_back(tmpAnim);
} generatePaletteShiftedAnimation(entity->tilesFilename, paletteShifts);
} }
for(auto entity : VLC->riverTypeHandler->objects) for(auto entity : VLC->riverTypeHandler->objects)
{ {
if(entity->paletteAnimation.size()) if(entity->paletteAnimation.empty())
{ continue;
tiles.push_back(entity->tilesFilename.getName());
std::vector<std::variant<TerrainPaletteAnimation, RiverPaletteAnimation>> tmpAnim; std::vector<PaletteAnimation> paletteShifts;
for(auto & animEntity : entity->paletteAnimation) for(auto & animEntity : entity->paletteAnimation)
tmpAnim.push_back(animEntity); paletteShifts.push_back({animEntity.start, animEntity.length});
paletteAnimations.push_back(tmpAnim);
generatePaletteShiftedAnimation(entity->tilesFilename, paletteShifts);
}
}
void AssetGenerator::generatePaletteShiftedAnimation(const AnimationPath & sprite, const std::vector<PaletteAnimation> & 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);};
} }
} }
for(int i = 0; i < tiles.size(); i++) AnimationPath shiftedPath = AnimationPath::builtin("SPRITES/" + sprite.getName() + "_SHIFTED");
{ animationFiles[shiftedPath] = layout;
auto sprite = tiles[i]; }
JsonNode config; AssetGenerator::CanvasPtr AssetGenerator::createPaletteShiftedImage(const AnimationPath & source, const std::vector<PaletteAnimation> & palette, int frameIndex, int paletteShiftCounter)
config["basepath"].String() = sprite + "_Shifted/"; {
config["images"].Vector(); auto animation = GH.renderHandler().loadAnimation(source, EImageBlitMode::COLORKEY);
auto filename = AnimationPath::builtin(sprite).addPrefix("SPRITES/"); auto imgLoc = animation->getImageLocator(frameIndex, 0);
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<TerrainPaletteAnimation>(element))
maxLen = std::lcm(maxLen, std::get<TerrainPaletteAnimation>(element).length);
else
maxLen = std::lcm(maxLen, std::get<RiverPaletteAnimation>(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); auto img = GH.renderHandler().loadImage(imgLoc);
for(int k = 0; k < paletteAnimations[i].size(); k++)
{ for(const auto & element : palette)
auto element = paletteAnimations[i][k]; img->shiftPalette(element.start, element.length, paletteShiftCounter % element.length);
if(std::holds_alternative<TerrainPaletteAnimation>(element))
{
auto tmp = std::get<TerrainPaletteAnimation>(element);
img->shiftPalette(tmp.start, tmp.length, l % tmp.length);
}
else
{
auto tmp = std::get<RiverPaletteAnimation>(element);
img->shiftPalette(tmp.start, tmp.length, l % tmp.length);
}
}
auto image = GH.renderHandler().createImage(Point(32, 32), CanvasScalingPolicy::IGNORE); auto image = GH.renderHandler().createImage(Point(32, 32), CanvasScalingPolicy::IGNORE);
Canvas canvas = image->getCanvas(); Canvas canvas = image->getCanvas();
canvas.draw(img, Point((32 - img->dimensions().x) / 2, (32 - img->dimensions().y) / 2)); canvas.draw(img, Point((32 - img->dimensions().x) / 2, (32 - img->dimensions().y) / 2));
image->exportBitmap(*CResourceHandler::get("local")->getResourceName(savePath));
JsonNode node(JsonMap{ return image;
{ "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();
}
} }

View File

@ -9,20 +9,53 @@
*/ */
#pragma once #pragma once
#include "ImageLocator.h"
VCMI_LIB_NAMESPACE_BEGIN VCMI_LIB_NAMESPACE_BEGIN
class PlayerColor; class PlayerColor;
VCMI_LIB_NAMESPACE_END VCMI_LIB_NAMESPACE_END
class ISharedImage;
class CanvasImage;
class AssetGenerator class AssetGenerator
{ {
public: public:
static void clear(); using AnimationLayoutMap = std::map<size_t, std::vector<ImageLocator>>;
static void generateAll(); using CanvasPtr = std::shared_ptr<CanvasImage>;
static void createAdventureOptionsCleanBackground();
static void createBigSpellBook(); AssetGenerator();
static void createPlayerColoredBackground(const PlayerColor & player);
static void createCombatUnitNumberWindow(); void initialize();
static void createCampaignBackground();
static void createChroniclesCampaignImages(); std::shared_ptr<ISharedImage> generateImage(const ImagePath & image);
static void createPaletteShiftedSprites();
std::map<ImagePath, std::shared_ptr<ISharedImage>> generateAllImages();
std::map<AnimationPath, AnimationLayoutMap> generateAllAnimations();
private:
using ImageGenerationFunctor = std::function<CanvasPtr()>;
struct PaletteAnimation
{
/// index of first color to cycle
int32_t start;
/// total numbers of colors to cycle
int32_t length;
};
std::map<ImagePath, ImageGenerationFunctor> imageFiles;
std::map<AnimationPath, AnimationLayoutMap> 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<PaletteAnimation> & animation, int frameIndex, int paletteShiftCounter);
void createPaletteShiftedSprites();
void generatePaletteShiftedAnimation(const AnimationPath & source, const std::vector<PaletteAnimation> & animation);
}; };

View File

@ -14,6 +14,7 @@
#include "../render/IScreenHandler.h" #include "../render/IScreenHandler.h"
#include "../renderSDL/SDL_Extensions.h" #include "../renderSDL/SDL_Extensions.h"
#include "../renderSDL/SDLImageScaler.h" #include "../renderSDL/SDLImageScaler.h"
#include "../renderSDL/SDLImage.h"
#include <SDL_image.h> #include <SDL_image.h>
#include <SDL_surface.h> #include <SDL_surface.h>
@ -61,3 +62,8 @@ Point CanvasImage::dimensions() const
{ {
return {surface->w, surface->h}; return {surface->w, surface->h};
} }
std::shared_ptr<ISharedImage> CanvasImage::toSharedImage()
{
return std::make_shared<SDLImageShared>(surface);
}

View File

@ -34,6 +34,8 @@ public:
void shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove) override{}; void shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove) override{};
void adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask) override{}; void adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask) override{};
std::shared_ptr<ISharedImage> toSharedImage();
private: private:
SDL_Surface * surface; SDL_Surface * surface;
CanvasScalingPolicy scalingPolicy; CanvasScalingPolicy scalingPolicy;

View File

@ -50,4 +50,6 @@ public:
/// Returns font with specified identifer /// Returns font with specified identifer
virtual std::shared_ptr<const IFont> loadFont(EFonts font) = 0; virtual std::shared_ptr<const IFont> loadFont(EFonts font) = 0;
virtual void exportGeneratedAssets() = 0;
}; };

View File

@ -16,6 +16,7 @@
#include "../gui/CGuiHandler.h" #include "../gui/CGuiHandler.h"
#include "../render/AssetGenerator.h"
#include "../render/CAnimation.h" #include "../render/CAnimation.h"
#include "../render/CanvasImage.h" #include "../render/CanvasImage.h"
#include "../render/CDefFile.h" #include "../render/CDefFile.h"
@ -43,6 +44,13 @@
#include <vcmi/SkillService.h> #include <vcmi/SkillService.h>
#include <vcmi/spells/Service.h> #include <vcmi/spells/Service.h>
RenderHandler::RenderHandler()
:assetGenerator(std::make_unique<AssetGenerator>())
{
}
RenderHandler::~RenderHandler() = default;
std::shared_ptr<CDefFile> RenderHandler::getAnimationFile(const AnimationPath & path) std::shared_ptr<CDefFile> RenderHandler::getAnimationFile(const AnimationPath & path)
{ {
AnimationPath actualPath = boost::starts_with(path.getName(), "SPRITES") ? path : path.addPrefix("SPRITES/"); AnimationPath actualPath = boost::starts_with(path.getName(), "SPRITES") ? path : path.addPrefix("SPRITES/");
@ -201,12 +209,28 @@ std::shared_ptr<ScalableImageShared> RenderHandler::loadImageImpl(const ImageLoc
return scaledImage; return scaledImage;
} }
std::shared_ptr<SDLImageShared> RenderHandler::loadImageFromFileUncached(const ImageLocator & locator) std::shared_ptr<ISharedImage> RenderHandler::loadImageFromFileUncached(const ImageLocator & locator)
{ {
if(locator.image) if(locator.image)
{ {
// TODO: create EmptySharedImage class that will be instantiated if image does not exists or fails to load auto imagePath = *locator.image;
return std::make_shared<SDLImageShared>(*locator.image); auto imagePathSprites = imagePath.addPrefix("SPRITES/");
auto imagePathData = imagePath.addPrefix("DATA/");
if(CResourceHandler::get()->existsResource(imagePathSprites))
return std::make_shared<SDLImageShared>(imagePathSprites);
if(CResourceHandler::get()->existsResource(imagePathData))
return std::make_shared<SDLImageShared>(imagePathData);
if(CResourceHandler::get()->existsResource(imagePath))
return std::make_shared<SDLImageShared>(imagePath);
auto generated = assetGenerator->generateImage(imagePath);
if (generated)
return generated;
return std::make_shared<SDLImageShared>(ImagePath::builtin("DEFAULT"));
} }
if(locator.defFile) if(locator.defFile)
@ -423,6 +447,10 @@ static void detectOverlappingBuildings(RenderHandler * renderHandler, const Fact
void RenderHandler::onLibraryLoadingFinished(const Services * services) void RenderHandler::onLibraryLoadingFinished(const Services * services)
{ {
assert(animationLayouts.empty());
assetGenerator->initialize();
animationLayouts = assetGenerator->generateAllAnimations();
addImageListEntries(services->creatures()); addImageListEntries(services->creatures());
addImageListEntries(services->heroTypes()); addImageListEntries(services->heroTypes());
addImageListEntries(services->artifacts()); addImageListEntries(services->artifacts());
@ -469,3 +497,9 @@ std::shared_ptr<const IFont> RenderHandler::loadFont(EFonts font)
fonts[font] = loadedFont; fonts[font] = loadedFont;
return loadedFont; return loadedFont;
} }
void RenderHandler::exportGeneratedAssets()
{
for (const auto & entry : assetGenerator->generateAllImages())
entry.second->exportBitmap(VCMIDirs::get().userDataPath() / "Generated" / (entry.first.getOriginalName() + ".png"), nullptr);
}

View File

@ -18,8 +18,9 @@ VCMI_LIB_NAMESPACE_END
class CDefFile; class CDefFile;
class SDLImageShared; class SDLImageShared;
class ScalableImageShared; class ScalableImageShared;
class AssetGenerator;
class RenderHandler : public IRenderHandler class RenderHandler final : public IRenderHandler
{ {
using AnimationLayoutMap = std::map<size_t, std::vector<ImageLocator>>; using AnimationLayoutMap = std::map<size_t, std::vector<ImageLocator>>;
@ -27,6 +28,7 @@ class RenderHandler : public IRenderHandler
std::map<AnimationPath, AnimationLayoutMap> animationLayouts; std::map<AnimationPath, AnimationLayoutMap> animationLayouts;
std::map<SharedImageLocator, std::shared_ptr<ScalableImageShared>> imageFiles; std::map<SharedImageLocator, std::shared_ptr<ScalableImageShared>> imageFiles;
std::map<EFonts, std::shared_ptr<const IFont>> fonts; std::map<EFonts, std::shared_ptr<const IFont>> fonts;
std::unique_ptr<AssetGenerator> assetGenerator;
std::shared_ptr<CDefFile> getAnimationFile(const AnimationPath & path); std::shared_ptr<CDefFile> getAnimationFile(const AnimationPath & path);
AnimationLayoutMap & getAnimationLayout(const AnimationPath & path, int scalingFactor, EImageBlitMode mode); AnimationLayoutMap & getAnimationLayout(const AnimationPath & path, int scalingFactor, EImageBlitMode mode);
@ -38,13 +40,15 @@ class RenderHandler : public IRenderHandler
std::shared_ptr<ScalableImageShared> loadImageImpl(const ImageLocator & config); std::shared_ptr<ScalableImageShared> loadImageImpl(const ImageLocator & config);
std::shared_ptr<SDLImageShared> loadImageFromFileUncached(const ImageLocator & locator); std::shared_ptr<ISharedImage> loadImageFromFileUncached(const ImageLocator & locator);
ImageLocator getLocatorForAnimationFrame(const AnimationPath & path, int frame, int group, int scaling, EImageBlitMode mode); ImageLocator getLocatorForAnimationFrame(const AnimationPath & path, int frame, int group, int scaling, EImageBlitMode mode);
int getScalingFactor() const; int getScalingFactor() const;
public: public:
RenderHandler();
~RenderHandler();
// IRenderHandler implementation // IRenderHandler implementation
void onLibraryLoadingFinished(const Services * services) override; void onLibraryLoadingFinished(const Services * services) override;
@ -61,4 +65,6 @@ public:
/// Returns font with specified identifer /// Returns font with specified identifer
std::shared_ptr<const IFont> loadFont(EFonts font) override; std::shared_ptr<const IFont> loadFont(EFonts font) override;
void exportGeneratedAssets() override;
}; };

View File

@ -306,6 +306,10 @@ std::shared_ptr<const ISharedImage> SDLImageShared::scaleTo(const Point & size,
void SDLImageShared::exportBitmap(const boost::filesystem::path& path, SDL_Palette * palette) const 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); assert(upscalingInProgress == false);
if (!surf) if (!surf)
return; return;

View File

@ -13,7 +13,6 @@
#include "MiscWidgets.h" #include "MiscWidgets.h"
#include "../gui/CGuiHandler.h" #include "../gui/CGuiHandler.h"
#include "../render/AssetGenerator.h"
#include "../render/IImage.h" #include "../render/IImage.h"
#include "../render/IRenderHandler.h" #include "../render/IRenderHandler.h"
#include "../render/CAnimation.h" #include "../render/CAnimation.h"
@ -184,8 +183,6 @@ FilledTexturePlayerColored::FilledTexturePlayerColored(Rect position)
void FilledTexturePlayerColored::setPlayerColor(PlayerColor player) void FilledTexturePlayerColored::setPlayerColor(PlayerColor player)
{ {
AssetGenerator::createPlayerColoredBackground(player);
ImagePath imagePath = ImagePath::builtin("DialogBoxBackground_" + player.toString() + ".bmp"); ImagePath imagePath = ImagePath::builtin("DialogBoxBackground_" + player.toString() + ".bmp");
texture = GH.renderHandler().loadImage(imagePath, EImageBlitMode::COLORKEY); texture = GH.renderHandler().loadImage(imagePath, EImageBlitMode::COLORKEY);

View File

@ -32,7 +32,6 @@
#include "../widgets/Buttons.h" #include "../widgets/Buttons.h"
#include "../widgets/VideoWidget.h" #include "../widgets/VideoWidget.h"
#include "../adventureMap/AdventureMapInterface.h" #include "../adventureMap/AdventureMapInterface.h"
#include "../render/AssetGenerator.h"
#include "../../CCallback.h" #include "../../CCallback.h"
@ -118,7 +117,6 @@ CSpellWindow::CSpellWindow(const CGHeroInstance * _myHero, CPlayerInterface * _m
if(isBigSpellbook) if(isBigSpellbook)
{ {
AssetGenerator::createBigSpellBook();
background = std::make_shared<CPicture>(ImagePath::builtin("SpellBookLarge"), 0, 0); background = std::make_shared<CPicture>(ImagePath::builtin("SpellBookLarge"), 0, 0);
updateShadow(); updateShadow();
} }

View File

@ -27,7 +27,6 @@
#include "../client/media/CMusicHandler.h" #include "../client/media/CMusicHandler.h"
#include "../client/media/CSoundHandler.h" #include "../client/media/CSoundHandler.h"
#include "../client/media/CVideoHandler.h" #include "../client/media/CVideoHandler.h"
#include "../client/render/AssetGenerator.h"
#include "../client/render/Graphics.h" #include "../client/render/Graphics.h"
#include "../client/render/IRenderHandler.h" #include "../client/render/IRenderHandler.h"
#include "../client/render/IScreenHandler.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("Creating console and configuring logger: %d ms", pomtime.getDiff());
logGlobal->info("The log file will be saved to %s", logPath); logGlobal->info("The log file will be saved to %s", logPath);
AssetGenerator::clear();
// Init filesystem and settings // Init filesystem and settings
try try
{ {