1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-03-19 21:10:12 +02:00

Merge pull request from IvanSavenko/assets_generation

[1.6.5] In-memory assets generation
This commit is contained in:
Ivan Savenko 2025-02-01 14:23:06 +02:00 committed by GitHub
commit c5a75b20ad
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 257 additions and 272 deletions

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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