1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-06-02 23:07:36 +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/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");
}

View File

@ -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<AdventureMapShortcuts>(*this);
widget = std::make_shared<AdventureMapWidget>(shortcuts);

View File

@ -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);

View File

@ -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<SimturnsInfo> OptionsTabBase::getSimturnsPresets() const
OptionsTabBase::OptionsTabBase(const JsonPath & configPath)
{
AssetGenerator::createAdventureOptionsCleanBackground();
recActions = 0;
auto setTimerPresetCallback = [this](int index){

View File

@ -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);

View File

@ -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<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
return;
std::map<ImagePath, std::shared_ptr<ISharedImage>> AssetGenerator::generateAllImages()
{
std::map<ImagePath, std::shared_ptr<ISharedImage>> 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<AnimationPath, AssetGenerator::AnimationLayoutMap> AssetGenerator::generateAllAnimations()
{
return animationFiles;
}
AssetGenerator::CanvasPtr AssetGenerator::createAdventureOptionsCleanBackground()
{
auto locator = ImageLocator(ImagePath::builtin("ADVOPTBK"), EImageBlitMode::OPAQUE);
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, 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<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(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<IImage> 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<IImage> 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<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.draw(img, Point(0, 0), Rect(0, 0, 800, 600));
@ -264,171 +242,112 @@ void AssetGenerator::createCampaignBackground()
std::shared_ptr<IImage> 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<IImage> 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<IImage> 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<IImage> 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<IImage> 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<std::string> tiles;
std::vector<std::vector<std::variant<TerrainPaletteAnimation, RiverPaletteAnimation>>> paletteAnimations;
for(auto entity : VLC->terrainTypeHandler->objects)
{
if(entity->paletteAnimation.size())
{
tiles.push_back(entity->tilesFilename.getName());
std::vector<std::variant<TerrainPaletteAnimation, RiverPaletteAnimation>> tmpAnim;
for(auto & animEntity : entity->paletteAnimation)
tmpAnim.push_back(animEntity);
paletteAnimations.push_back(tmpAnim);
}
if(entity->paletteAnimation.empty())
continue;
std::vector<PaletteAnimation> 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<std::variant<TerrainPaletteAnimation, RiverPaletteAnimation>> 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<PaletteAnimation> 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<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);
for(int k = 0; k < paletteAnimations[i].size(); k++)
{
auto element = paletteAnimations[i][k];
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);
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<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);};
}
}
AnimationPath shiftedPath = AnimationPath::builtin("SPRITES/" + sprite.getName() + "_SHIFTED");
animationFiles[shiftedPath] = layout;
}
AssetGenerator::CanvasPtr AssetGenerator::createPaletteShiftedImage(const AnimationPath & source, const std::vector<PaletteAnimation> & 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;
}

View File

@ -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<size_t, std::vector<ImageLocator>>;
using CanvasPtr = std::shared_ptr<CanvasImage>;
AssetGenerator();
void initialize();
std::shared_ptr<ISharedImage> generateImage(const ImagePath & image);
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 "../renderSDL/SDL_Extensions.h"
#include "../renderSDL/SDLImageScaler.h"
#include "../renderSDL/SDLImage.h"
#include <SDL_image.h>
#include <SDL_surface.h>
@ -61,3 +62,8 @@ Point CanvasImage::dimensions() const
{
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 adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask) override{};
std::shared_ptr<ISharedImage> toSharedImage();
private:
SDL_Surface * surface;
CanvasScalingPolicy scalingPolicy;

View File

@ -50,4 +50,6 @@ public:
/// Returns font with specified identifer
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 "../render/AssetGenerator.h"
#include "../render/CAnimation.h"
#include "../render/CanvasImage.h"
#include "../render/CDefFile.h"
@ -43,6 +44,13 @@
#include <vcmi/SkillService.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)
{
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;
}
std::shared_ptr<SDLImageShared> RenderHandler::loadImageFromFileUncached(const ImageLocator & locator)
std::shared_ptr<ISharedImage> 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<SDLImageShared>(*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<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)
@ -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<const IFont> 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);
}

View File

@ -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<size_t, std::vector<ImageLocator>>;
@ -27,6 +28,7 @@ class RenderHandler : public IRenderHandler
std::map<AnimationPath, AnimationLayoutMap> animationLayouts;
std::map<SharedImageLocator, std::shared_ptr<ScalableImageShared>> imageFiles;
std::map<EFonts, std::shared_ptr<const IFont>> fonts;
std::unique_ptr<AssetGenerator> assetGenerator;
std::shared_ptr<CDefFile> getAnimationFile(const AnimationPath & path);
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<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);
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<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
{
auto directory = path;
directory.remove_filename();
boost::filesystem::create_directories(directory);
assert(upscalingInProgress == false);
if (!surf)
return;

View File

@ -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);

View File

@ -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<CPicture>(ImagePath::builtin("SpellBookLarge"), 0, 0);
updateShadow();
}

View File

@ -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
{