Explorar el Código

Merge pull request #4584 from IvanSavenko/xbrz_fixes

Improvements for xbrz support
Ivan Savenko hace 1 año
padre
commit
0b0f4a72c6

+ 1 - 2
client/adventureMap/AdventureMapWidget.cpp

@@ -309,9 +309,8 @@ std::shared_ptr<CIntObject> AdventureMapWidget::buildStatusBar(const JsonNode &
 std::shared_ptr<CIntObject> AdventureMapWidget::buildTexturePlayerColored(const JsonNode & input)
 {
 	logGlobal->debug("Building widget CFilledTexture");
-	auto image = ImagePath::fromJson(input["image"]);
 	Rect area = readTargetArea(input["area"]);
-	return std::make_shared<FilledTexturePlayerColored>(image, area);
+	return std::make_shared<FilledTexturePlayerColored>(area);
 }
 
 std::shared_ptr<CHeroList> AdventureMapWidget::getHeroList()

+ 6 - 17
client/battle/BattleStacksController.cpp

@@ -27,6 +27,7 @@
 #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"
@@ -79,24 +80,12 @@ BattleStacksController::BattleStacksController(BattleInterface & owner):
 	stackToActivate(nullptr),
 	animIDhelper(0)
 {
+	AssetGenerator::createCombatUnitNumberWindow();
 	//preparing graphics for displaying amounts of creatures
-	amountNormal     = GH.renderHandler().loadImage(ImagePath::builtin("CMNUMWIN.BMP"), EImageBlitMode::COLORKEY);
-	amountPositive   = GH.renderHandler().loadImage(ImagePath::builtin("CMNUMWIN.BMP"), EImageBlitMode::COLORKEY);
-	amountNegative   = GH.renderHandler().loadImage(ImagePath::builtin("CMNUMWIN.BMP"), EImageBlitMode::COLORKEY);
-	amountEffNeutral = GH.renderHandler().loadImage(ImagePath::builtin("CMNUMWIN.BMP"), EImageBlitMode::COLORKEY);
-
-	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 );
-
-	// do not change border color
-	static const int32_t ignoredMask = 1 << 26;
-
-	amountNormal->adjustPalette(shifterNormal, ignoredMask);
-	amountPositive->adjustPalette(shifterPositive, ignoredMask);
-	amountNegative->adjustPalette(shifterNegative, ignoredMask);
-	amountEffNeutral->adjustPalette(shifterNeutral, ignoredMask);
+	amountNormal     = GH.renderHandler().loadImage(ImagePath::builtin("combatUnitNumberWindowDefault"), EImageBlitMode::COLORKEY);
+	amountPositive   = GH.renderHandler().loadImage(ImagePath::builtin("combatUnitNumberWindowPositive"), EImageBlitMode::COLORKEY);
+	amountNegative   = GH.renderHandler().loadImage(ImagePath::builtin("combatUnitNumberWindowNegative"), EImageBlitMode::COLORKEY);
+	amountEffNeutral = GH.renderHandler().loadImage(ImagePath::builtin("combatUnitNumberWindowNeutral"), EImageBlitMode::COLORKEY);
 
 	std::vector<const CStack*> stacks = owner.getBattle()->battleGetAllStacks(true);
 	for(const CStack * s : stacks)

+ 1 - 1
client/globalLobby/GlobalLobbyInviteWindow.cpp

@@ -78,7 +78,7 @@ GlobalLobbyInviteWindow::GlobalLobbyInviteWindow()
 	pos.w = 236;
 	pos.h = 420;
 
-	filledBackground = std::make_shared<FilledTexturePlayerColored>(ImagePath::builtin("DiBoxBck"), Rect(0, 0, pos.w, pos.h));
+	filledBackground = std::make_shared<FilledTexturePlayerColored>(Rect(0, 0, pos.w, pos.h));
 	filledBackground->setPlayerColor(PlayerColor(1));
 	labelTitle = std::make_shared<CLabel>(
 		pos.w / 2, 20, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, MetaString::createFromTextID("vcmi.lobby.invite.header").toString()

+ 1 - 1
client/globalLobby/GlobalLobbyLoginWindow.cpp

@@ -40,7 +40,7 @@ GlobalLobbyLoginWindow::GlobalLobbyLoginWindow()
 	loginAs.appendTextID("vcmi.lobby.login.as");
 	loginAs.replaceRawString(CSH->getGlobalLobby().getAccountDisplayName());
 
-	filledBackground = std::make_shared<FilledTexturePlayerColored>(ImagePath::builtin("DiBoxBck"), Rect(0, 0, pos.w, pos.h));
+	filledBackground = std::make_shared<FilledTexturePlayerColored>(Rect(0, 0, pos.w, pos.h));
 	labelTitle = std::make_shared<CLabel>( pos.w / 2, 20, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->translate("vcmi.lobby.login.title"));
 	labelUsernameTitle = std::make_shared<CLabel>( 10, 65, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->translate("vcmi.lobby.login.username"));
 	labelUsername = std::make_shared<CLabel>( 10, 65, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, loginAs.toString(), 265);

+ 1 - 1
client/globalLobby/GlobalLobbyRoomWindow.cpp

@@ -152,7 +152,7 @@ GlobalLobbyRoomWindow::GlobalLobbyRoomWindow(GlobalLobbyWindow * window, const s
 	subtitleText.replaceRawString(roomDescription.description);
 	subtitleText.replaceRawString(roomDescription.hostAccountDisplayName);
 
-	filledBackground = std::make_shared<FilledTexturePlayerColored>(ImagePath::builtin("DiBoxBck"), Rect(0, 0, pos.w, pos.h));
+	filledBackground = std::make_shared<FilledTexturePlayerColored>(Rect(0, 0, pos.w, pos.h));
 	labelTitle = std::make_shared<CLabel>( pos.w / 2, 20, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, MetaString::createFromTextID("vcmi.lobby.preview.title").toString());
 	labelSubtitle = std::make_shared<CLabel>( pos.w / 2, 40, FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW, subtitleText.toString(), 400);
 

+ 1 - 1
client/globalLobby/GlobalLobbyServerSetup.cpp

@@ -34,7 +34,7 @@ GlobalLobbyServerSetup::GlobalLobbyServerSetup()
 	pos.w = 284;
 	pos.h = 340;
 
-	filledBackground = std::make_shared<FilledTexturePlayerColored>(ImagePath::builtin("DiBoxBck"), Rect(0, 0, pos.w, pos.h));
+	filledBackground = std::make_shared<FilledTexturePlayerColored>(Rect(0, 0, pos.w, pos.h));
 	labelTitle = std::make_shared<CLabel>( pos.w / 2, 20, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->translate("vcmi.lobby.room.create"));
 	labelPlayerLimit = std::make_shared<CLabel>( pos.w / 2, 48, FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->translate("vcmi.lobby.room.players.limit"));
 	labelRoomType = std::make_shared<CLabel>( pos.w / 2, 108, FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->translate("vcmi.lobby.room.type"));

+ 6 - 5
client/gui/CGuiHandler.cpp

@@ -181,17 +181,18 @@ Point CGuiHandler::screenDimensions() const
 
 void CGuiHandler::drawFPSCounter()
 {
-	int x = 7;
-	int y = screen->h-20;
-	int width3digitFPSIncludingPadding = 48;
-	int heightFPSTextIncludingPadding = 11;
+	int scaling = screenHandlerInstance->getScalingFactor();
+	int x = 7 * scaling;
+	int y = screen->h-20 * scaling;
+	int width3digitFPSIncludingPadding = 48 * scaling;
+	int heightFPSTextIncludingPadding = 11 * scaling;
 	SDL_Rect overlay = { x, y, width3digitFPSIncludingPadding, heightFPSTextIncludingPadding};
 	uint32_t black = SDL_MapRGB(screen->format, 10, 10, 10);
 	SDL_FillRect(screen, &overlay, black);
 
 	std::string fps = std::to_string(framerate().getFramerate())+" FPS";
 
-	graphics->fonts[FONT_SMALL]->renderTextLeft(screen, fps, Colors::WHITE, Point(8, screen->h-22));
+	graphics->fonts[FONT_SMALL]->renderTextLeft(screen, fps, Colors::WHITE, Point(8 * scaling, screen->h-22 * scaling));
 }
 
 bool CGuiHandler::amIGuiThread()

+ 4 - 3
client/gui/CursorHandler.cpp

@@ -17,6 +17,7 @@
 #include "../renderSDL/CursorHardware.h"
 #include "../render/CAnimation.h"
 #include "../render/IImage.h"
+#include "../render/IScreenHandler.h"
 #include "../render/IRenderHandler.h"
 
 #include "../../lib/CConfigHandler.h"
@@ -175,7 +176,7 @@ Point CursorHandler::getPivotOffsetMap(size_t index)
 
 	assert(offsets.size() == size_t(Cursor::Map::COUNT)); //Invalid number of pivot offsets for cursor
 	assert(index < offsets.size());
-	return offsets[index];
+	return offsets[index] * GH.screenHandler().getScalingFactor();
 }
 
 Point CursorHandler::getPivotOffsetCombat(size_t index)
@@ -205,12 +206,12 @@ Point CursorHandler::getPivotOffsetCombat(size_t index)
 
 	assert(offsets.size() == size_t(Cursor::Combat::COUNT)); //Invalid number of pivot offsets for cursor
 	assert(index < offsets.size());
-	return offsets[index];
+	return offsets[index] * GH.screenHandler().getScalingFactor();
 }
 
 Point CursorHandler::getPivotOffsetSpellcast()
 {
-	return { 18, 28};
+	return Point(18, 28) * GH.screenHandler().getScalingFactor();
 }
 
 Point CursorHandler::getPivotOffset()

+ 6 - 3
client/gui/InterfaceObjectConfigurable.cpp

@@ -566,16 +566,19 @@ std::shared_ptr<CAnimImage> InterfaceObjectConfigurable::buildImage(const JsonNo
 std::shared_ptr<CFilledTexture> InterfaceObjectConfigurable::buildTexture(const JsonNode & config) const
 {
 	logGlobal->debug("Building widget CFilledTexture");
-	auto image = ImagePath::fromJson(config["image"]);
 	auto rect = readRect(config["rect"]);
 	auto playerColor = readPlayerColor(config["color"]);
 	if(playerColor.isValidPlayer())
 	{
-		auto result = std::make_shared<FilledTexturePlayerColored>(image, rect);
+		auto result = std::make_shared<FilledTexturePlayerColored>(rect);
 		result->setPlayerColor(playerColor);
 		return result;
 	}
-	return std::make_shared<CFilledTexture>(image, rect);
+	else
+	{
+		auto image = ImagePath::fromJson(config["image"]);
+		return std::make_shared<CFilledTexture>(image, rect);
+	}
 }
 
 std::shared_ptr<ComboBox> InterfaceObjectConfigurable::buildComboBox(const JsonNode & config)

+ 1 - 1
client/lobby/CSelectionBase.cpp

@@ -399,7 +399,7 @@ PvPBox::PvPBox(const Rect & rect)
 	pos += rect.topLeft();
 	setRedrawParent(true);
 
-	backgroundTexture = std::make_shared<FilledTexturePlayerColored>(ImagePath::builtin("DiBoxBck"), Rect(0, 0, rect.w, rect.h));
+	backgroundTexture = std::make_shared<FilledTexturePlayerColored>(Rect(0, 0, rect.w, rect.h));
 	backgroundTexture->setPlayerColor(PlayerColor(1));
 	backgroundBorder = std::make_shared<TransparentFilledRectangle>(Rect(0, 0, rect.w, rect.h), ColorRGBA(0, 0, 0, 64), ColorRGBA(96, 96, 96, 255), 1);
 

+ 2 - 2
client/lobby/OptionsTab.cpp

@@ -521,7 +521,7 @@ void OptionsTab::SelectionWindow::recreate(int sliderPos)
 	int sliderWidth = ((amountLines > MAX_LINES) ? 16 : 0);
 
 	pos = Rect(pos.x, pos.y, x + sliderWidth, y);
-	backgroundTexture = std::make_shared<FilledTexturePlayerColored>(ImagePath::builtin("DiBoxBck"), Rect(0, 0, pos.w - sliderWidth, pos.h));
+	backgroundTexture = std::make_shared<FilledTexturePlayerColored>(Rect(0, 0, pos.w - sliderWidth, pos.h));
 	backgroundTexture->setPlayerColor(PlayerColor(1));
 	updateShadow();
 
@@ -803,7 +803,7 @@ OptionsTab::HandicapWindow::HandicapWindow()
 
 	pos = Rect(0, 0, 660, 100 + SEL->getStartInfo()->playerInfos.size() * 30);
 
-	backgroundTexture = std::make_shared<FilledTexturePlayerColored>(ImagePath::builtin("DiBoxBck"), pos);
+	backgroundTexture = std::make_shared<FilledTexturePlayerColored>(pos);
 	backgroundTexture->setPlayerColor(PlayerColor(1));
 
 	labels.push_back(std::make_shared<CLabel>(pos.w / 2 + 8, 15, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->translate("vcmi.lobby.handicap")));

+ 2 - 2
client/mainmenu/CStatisticScreen.cpp

@@ -47,7 +47,7 @@ CStatisticScreen::CStatisticScreen(const StatisticDataSet & stat)
 {
 	OBJECT_CONSTRUCTION;
 	pos = center(Rect(0, 0, 800, 600));
-	filledBackground = std::make_shared<FilledTexturePlayerColored>(ImagePath::builtin("DiBoxBck"), Rect(0, 0, pos.w, pos.h));
+	filledBackground = std::make_shared<FilledTexturePlayerColored>(Rect(0, 0, pos.w, pos.h));
 	filledBackground->setPlayerColor(PlayerColor(1));
 
 	contentArea = Rect(10, 40, 780, 510);
@@ -225,7 +225,7 @@ StatisticSelector::StatisticSelector(const std::vector<std::string> & texts, con
 {
 	OBJECT_CONSTRUCTION;
 	pos = center(Rect(0, 0, 128 + 16, std::min(static_cast<int>(texts.size()), LINES) * 40));
-	filledBackground = std::make_shared<FilledTexturePlayerColored>(ImagePath::builtin("DiBoxBck"), Rect(0, 0, pos.w, pos.h));
+	filledBackground = std::make_shared<FilledTexturePlayerColored>(Rect(0, 0, pos.w, pos.h));
 	filledBackground->setPlayerColor(PlayerColor(1));
 
 	slider = std::make_shared<CSlider>(Point(pos.w - 16, 0), pos.h, [this](int to){ update(to); redraw(); }, LINES, texts.size(), 0, Orientation::VERTICAL, CSlider::BLUE);

+ 13 - 5
client/mapView/MapRenderer.cpp

@@ -479,13 +479,21 @@ void MapRendererObjects::renderImage(IMapRendererContext & context, Canvas & tar
 
 	image->setAlpha(transparency);
 	image->setShadowEnabled(true);
-	image->setOverlayEnabled(object->getOwner().isValidPlayer() || object->getOwner() == PlayerColor::NEUTRAL);
+	if (object->ID != Obj::HERO)
+	{
+		image->setOverlayEnabled(object->getOwner().isValidPlayer() || object->getOwner() == PlayerColor::NEUTRAL);
 
-	if (object->getOwner().isValidPlayer())
-		image->setOverlayColor(graphics->playerColors[object->getOwner().getNum()]);
+		if (object->getOwner().isValidPlayer())
+			image->setOverlayColor(graphics->playerColors[object->getOwner().getNum()]);
 
-	if (object->getOwner() == PlayerColor::NEUTRAL)
-		image->setOverlayColor(graphics->neutralColor);
+		if (object->getOwner() == PlayerColor::NEUTRAL)
+			image->setOverlayColor(graphics->neutralColor);
+	}
+	else
+	{
+		// heroes use separate image with flag instead of player-colored palette
+		image->setOverlayEnabled(false);
+	}
 
 	Point offsetPixels = context.objectImageOffset(object->id, coordinates);
 

+ 92 - 6
client/render/AssetGenerator.cpp

@@ -14,6 +14,7 @@
 #include "../render/IImage.h"
 #include "../render/IImageLoader.h"
 #include "../render/Canvas.h"
+#include "../render/ColorFilter.h"
 #include "../render/IRenderHandler.h"
 
 #include "../lib/filesystem/Filesystem.h"
@@ -22,11 +23,13 @@ void AssetGenerator::generateAll()
 {
 	createBigSpellBook();
 	createAdventureOptionsCleanBackground();
+	for (int i = 0; i < PlayerColor::PLAYER_LIMIT_I; ++i)
+		createPlayerColoredBackground(PlayerColor(i));
 }
 
 void AssetGenerator::createAdventureOptionsCleanBackground()
 {
-	std::string filename = "data/AdventureOptionsBackgroundClear.bmp";
+	std::string filename = "data/AdventureOptionsBackgroundClear.png";
 
 	if(CResourceHandler::get()->existsResource(ResourcePath(filename))) // overridden by mod, no generation
 		return;
@@ -35,9 +38,10 @@ void AssetGenerator::createAdventureOptionsCleanBackground()
 		return;
 	ResourcePath savePath(filename, EResType::IMAGE);
 
-	auto res = ImagePath::builtin("ADVOPTBK");
+	auto locator = ImageLocator(ImagePath::builtin("ADVOPTBK"));
+	locator.scalingFactor = 1;
 
-	std::shared_ptr<IImage> img = GH.renderHandler().loadImage(res, EImageBlitMode::OPAQUE);
+	std::shared_ptr<IImage> img = GH.renderHandler().loadImage(locator, EImageBlitMode::OPAQUE);
 
 	Canvas canvas = Canvas(Point(575, 585), CanvasScalingPolicy::IGNORE);
 	canvas.draw(img, Point(0, 0), Rect(0, 0, 575, 585));
@@ -55,7 +59,7 @@ void AssetGenerator::createAdventureOptionsCleanBackground()
 
 void AssetGenerator::createBigSpellBook()
 {
-	std::string filename = "data/SpellBookLarge.bmp";
+	std::string filename = "data/SpellBookLarge.png";
 
 	if(CResourceHandler::get()->existsResource(ResourcePath(filename))) // overridden by mod, no generation
 		return;
@@ -64,9 +68,10 @@ void AssetGenerator::createBigSpellBook()
 		return;
 	ResourcePath savePath(filename, EResType::IMAGE);
 
-	auto res = ImagePath::builtin("SpelBack");
+	auto locator = ImageLocator(ImagePath::builtin("SpelBack"));
+	locator.scalingFactor = 1;
 
-	std::shared_ptr<IImage> img = GH.renderHandler().loadImage(res, EImageBlitMode::OPAQUE);
+	std::shared_ptr<IImage> img = GH.renderHandler().loadImage(locator, EImageBlitMode::OPAQUE);
 	Canvas canvas = Canvas(Point(800, 600), CanvasScalingPolicy::IGNORE);
 	// edges
 	canvas.draw(img, Point(0, 0), Rect(15, 38, 90, 45));
@@ -114,3 +119,84 @@ void AssetGenerator::createBigSpellBook()
 
 	image->exportBitmap(*CResourceHandler::get("local")->getResourceName(savePath));
 }
+
+void 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"));
+	locator.scalingFactor = 1;
+
+	std::shared_ptr<IImage> texture = GH.renderHandler().loadImage(locator, EImageBlitMode::OPAQUE);
+
+	// Color transform to make color of brown DIBOX.PCX texture match color of specified player
+	static const std::array<ColorFilter, PlayerColor::PLAYER_LIMIT_I> filters = {
+		ColorFilter::genRangeShifter(  0.25,  0,     0,     1.25, 0.00, 0.00 ), // red
+		ColorFilter::genRangeShifter(  0,     0,     0,     0.45, 1.20, 4.50 ), // blue
+		ColorFilter::genRangeShifter(  0.40,  0.27,  0.23,  1.10, 1.20, 1.15 ), // tan
+		ColorFilter::genRangeShifter( -0.27,  0.10, -0.27,  0.70, 1.70, 0.70 ), // green
+		ColorFilter::genRangeShifter(  0.47,  0.17, -0.27,  1.60, 1.20, 0.70 ), // orange
+		ColorFilter::genRangeShifter(  0.12, -0.1,   0.25,  1.15, 1.20, 2.20 ), // purple
+		ColorFilter::genRangeShifter( -0.13,  0.23,  0.23,  0.90, 1.20, 2.20 ), // teal
+		ColorFilter::genRangeShifter(  0.44,  0.15,  0.25,  1.00, 1.00, 1.75 )  // pink
+	};
+
+	assert(player.isValidPlayer());
+	if (!player.isValidPlayer())
+	{
+		logGlobal->error("Unable to colorize to invalid player color %d!", static_cast<int>(player.getNum()));
+		return;
+	}
+
+	texture->adjustPalette(filters[player.getNum()], 0);
+	texture->exportBitmap(*CResourceHandler::get("local")->getResourceName(savePath));
+}
+
+void AssetGenerator::createCombatUnitNumberWindow()
+{
+	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"));
+	locator.scalingFactor = 1;
+
+	std::shared_ptr<IImage> texture = GH.renderHandler().loadImage(locator, EImageBlitMode::OPAQUE);
+
+	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 );
+
+	// 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));
+}

+ 9 - 3
client/render/AssetGenerator.h

@@ -9,10 +9,16 @@
  */
 #pragma once
 
+VCMI_LIB_NAMESPACE_BEGIN
+class PlayerColor;
+VCMI_LIB_NAMESPACE_END
+
 class AssetGenerator
 {
 public:
-    static void generateAll();
-    static void createAdventureOptionsCleanBackground();
-    static void createBigSpellBook();
+	static void generateAll();
+	static void createAdventureOptionsCleanBackground();
+	static void createBigSpellBook();
+	static void createPlayerColoredBackground(const PlayerColor & player);
+	static void createCombatUnitNumberWindow();
 };

+ 1 - 1
client/render/IImage.h

@@ -90,7 +90,7 @@ class ISharedImage
 {
 public:
 	virtual Point dimensions() const = 0;
-	virtual void exportBitmap(const boost::filesystem::path & path) const = 0;
+	virtual void exportBitmap(const boost::filesystem::path & path, SDL_Palette * palette) const = 0;
 	virtual bool isTransparent(const Point & coords) const = 0;
 	virtual void draw(SDL_Surface * where, SDL_Palette * palette, const Point & dest, const Rect * src, const ColorRGBA & colorMultiplier, uint8_t alpha, EImageBlitMode mode) const = 0;
 

+ 39 - 0
client/render/ImageLocator.cpp

@@ -70,6 +70,7 @@ bool ImageLocator::empty() const
 ImageLocator ImageLocator::copyFile() const
 {
 	ImageLocator result;
+	result.scalingFactor = 1;
 	result.image = image;
 	result.defFile = defFile;
 	result.defFrame = defFrame;
@@ -89,3 +90,41 @@ ImageLocator ImageLocator::copyFileTransformScale() const
 {
 	return *this; // full copy
 }
+
+std::string ImageLocator::toString() const
+{
+	std::string result;
+	if (empty())
+		return "invalid";
+
+	if (image)
+	{
+		result += image->getOriginalName();
+		assert(!result.empty());
+	}
+
+	if (defFile)
+	{
+		result += defFile->getOriginalName();
+		assert(!result.empty());
+		result += "-" + std::to_string(defGroup);
+		result += "-" + std::to_string(defFrame);
+	}
+
+	if (verticalFlip)
+		result += "-vflip";
+
+	if (horizontalFlip)
+		result += "-hflip";
+
+	if (scalingFactor > 1)
+		result += "-scale" + std::to_string(scalingFactor);
+
+	if (playerColored.isValidPlayer())
+		result += "-player" + playerColored.toString();
+
+	if (layer != EImageLayer::ALL)
+		result += "-layer" + std::to_string(static_cast<int>(layer));
+
+	return result;
+}

+ 6 - 1
client/render/ImageLocator.h

@@ -32,7 +32,7 @@ struct ImageLocator
 
 	bool verticalFlip = false;
 	bool horizontalFlip = false;
-	int8_t scalingFactor = 1;
+	int8_t scalingFactor = 0; // 0 = auto / use default scaling
 	EImageLayer layer = EImageLayer::ALL;
 
 	ImageLocator() = default;
@@ -46,4 +46,9 @@ struct ImageLocator
 	ImageLocator copyFile() const;
 	ImageLocator copyFileTransform() const;
 	ImageLocator copyFileTransformScale() const;
+
+	// generates string representation of this image locator
+	// guaranteed to be a valid file path with no extension
+	// but may contain '/' if source file is in directory
+	std::string toString() const;
 };

+ 1 - 2
client/renderSDL/ImageScaled.cpp

@@ -28,7 +28,6 @@ ImageScaled::ImageScaled(const ImageLocator & inputLocator, const std::shared_pt
 	, alphaValue(SDL_ALPHA_OPAQUE)
 	, blitMode(mode)
 {
-	locator.scalingFactor = GH.screenHandler().getScalingFactor();
 	setBodyEnabled(true);
 	if (mode == EImageBlitMode::ALPHA)
 		setShadowEnabled(true);
@@ -52,7 +51,7 @@ void ImageScaled::scaleTo(const Point & size)
 
 void ImageScaled::exportBitmap(const boost::filesystem::path &path) const
 {
-	source->exportBitmap(path);
+	source->exportBitmap(path, nullptr);
 }
 
 bool ImageScaled::isTransparent(const Point &coords) const

+ 49 - 17
client/renderSDL/RenderHandler.cpp

@@ -23,6 +23,7 @@
 
 #include "../../lib/json/JsonUtils.h"
 #include "../../lib/filesystem/Filesystem.h"
+#include "../../lib/VCMIDirs.h"
 
 #include <vcmi/ArtifactService.h>
 #include <vcmi/CreatureService.h>
@@ -136,14 +137,6 @@ int RenderHandler::getScalingFactor() const
 	return GH.screenHandler().getScalingFactor();
 }
 
-std::shared_ptr<IImage> RenderHandler::createImageReference(const ImageLocator & locator, std::shared_ptr<ISharedImage> input, EImageBlitMode mode)
-{
-	if (getScalingFactor() == 1 || locator.scalingFactor != 1 || locator.empty())
-		return input->createImageReference(mode);
-	else
-		return std::make_shared<ImageScaled>(locator, input, mode);
-}
-
 ImageLocator RenderHandler::getLocatorForAnimationFrame(const AnimationPath & path, int frame, int group)
 {
 	const auto & layout = getAnimationLayout(path);
@@ -196,13 +189,26 @@ std::shared_ptr<ISharedImage> RenderHandler::loadImageFromFileUncached(const Ima
 	throw std::runtime_error("Invalid image locator received!");
 }
 
+void RenderHandler::storeCachedImage(const ImageLocator & locator, std::shared_ptr<ISharedImage> image)
+{
+	imageFiles[locator] = image;
+
+#if 0
+	const boost::filesystem::path outPath = VCMIDirs::get().userExtractedPath() / "imageCache" / (locator.toString() + ".png");
+	boost::filesystem::path outDir = outPath;
+	outDir.remove_filename();
+	boost::filesystem::create_directories(outDir);
+	image->exportBitmap(outPath , nullptr);
+#endif
+}
+
 std::shared_ptr<ISharedImage> RenderHandler::loadImageFromFile(const ImageLocator & locator)
 {
 	if (imageFiles.count(locator))
 		return imageFiles.at(locator);
 
 	auto result = loadImageFromFileUncached(locator);
-	imageFiles[locator] = result;
+	storeCachedImage(locator, result);
 	return result;
 }
 
@@ -219,7 +225,7 @@ std::shared_ptr<ISharedImage> RenderHandler::transformImage(const ImageLocator &
 	if (locator.horizontalFlip)
 		result = result->horizontalFlip();
 
-	imageFiles[locator] = result;
+	storeCachedImage(locator, result);
 	return result;
 }
 
@@ -232,9 +238,12 @@ std::shared_ptr<ISharedImage> RenderHandler::scaleImage(const ImageLocator & loc
 
 	assert(locator.scalingFactor != 1); // should be filtered-out before
 
-	handle->setOverlayEnabled(locator.layer == EImageLayer::ALL || locator.layer == EImageLayer::OVERLAY);
 	handle->setBodyEnabled(locator.layer == EImageLayer::ALL || locator.layer == EImageLayer::BODY);
-	handle->setShadowEnabled(locator.layer == EImageLayer::ALL || locator.layer == EImageLayer::SHADOW);
+	if (locator.layer != EImageLayer::ALL)
+	{
+		handle->setOverlayEnabled(locator.layer == EImageLayer::OVERLAY);
+		handle->setShadowEnabled( locator.layer == EImageLayer::SHADOW);
+	}
 	if (locator.layer == EImageLayer::ALL && locator.playerColored != PlayerColor::CANNOT_DETERMINE)
 		handle->playerColored(locator.playerColored);
 
@@ -242,29 +251,52 @@ std::shared_ptr<ISharedImage> RenderHandler::scaleImage(const ImageLocator & loc
 
 	// TODO: try to optimize image size (possibly even before scaling?) - trim image borders if they are completely transparent
 	auto result = handle->getSharedImage();
-	imageFiles[locator] = result;
+	storeCachedImage(locator, result);
 	return result;
 }
 
 std::shared_ptr<IImage> RenderHandler::loadImage(const ImageLocator & locator, EImageBlitMode mode)
 {
-	return createImageReference(locator, loadImageImpl(locator), mode);
+	if (locator.scalingFactor == 0 && getScalingFactor() != 1 )
+	{
+		auto unscaledLocator = locator;
+		auto scaledLocator = locator;
+
+		unscaledLocator.scalingFactor = 1;
+		scaledLocator.scalingFactor = getScalingFactor();
+		auto unscaledImage = loadImageImpl(unscaledLocator);
+
+		return std::make_shared<ImageScaled>(scaledLocator, unscaledImage, mode);
+	}
+
+	if (locator.scalingFactor == 0)
+	{
+		auto scaledLocator = locator;
+		scaledLocator.scalingFactor = getScalingFactor();
+
+		return loadImageImpl(scaledLocator)->createImageReference(mode);
+	}
+	else
+	{
+		return loadImageImpl(locator)->createImageReference(mode);
+	}
 }
 
 std::shared_ptr<IImage> RenderHandler::loadImage(const AnimationPath & path, int frame, int group, EImageBlitMode mode)
 {
-	auto locator = getLocatorForAnimationFrame(path, frame, group);
+	ImageLocator locator = getLocatorForAnimationFrame(path, frame, group);
 	return loadImage(locator, mode);
 }
 
 std::shared_ptr<IImage> RenderHandler::loadImage(const ImagePath & path, EImageBlitMode mode)
 {
-	return loadImage(ImageLocator(path), mode);
+	ImageLocator locator(path);
+	return loadImage(locator, mode);
 }
 
 std::shared_ptr<IImage> RenderHandler::createImage(SDL_Surface * source)
 {
-	return createImageReference(ImageLocator(), std::make_shared<SDLImageShared>(source), EImageBlitMode::ALPHA);
+	return std::make_shared<SDLImageShared>(source)->createImageReference(EImageBlitMode::ALPHA);
 }
 
 std::shared_ptr<CAnimation> RenderHandler::loadAnimation(const AnimationPath & path, EImageBlitMode mode)

+ 1 - 1
client/renderSDL/RenderHandler.h

@@ -33,6 +33,7 @@ class RenderHandler : public IRenderHandler
 
 	void addImageListEntry(size_t index, size_t group, const std::string & listName, const std::string & imageName);
 	void addImageListEntries(const EntityService * service);
+	void storeCachedImage(const ImageLocator & locator, std::shared_ptr<ISharedImage> image);
 
 	std::shared_ptr<ISharedImage> loadImageImpl(const ImageLocator & config);
 
@@ -46,7 +47,6 @@ class RenderHandler : public IRenderHandler
 
 	int getScalingFactor() const;
 
-	std::shared_ptr<IImage> createImageReference(const ImageLocator & locator, std::shared_ptr<ISharedImage> input, EImageBlitMode mode);
 public:
 
 	// IRenderHandler implementation

+ 26 - 7
client/renderSDL/SDLImage.cpp

@@ -22,6 +22,7 @@
 
 #include <tbb/parallel_for.h>
 #include <SDL_surface.h>
+#include <SDL_image.h>
 
 class SDLImageLoader;
 
@@ -327,9 +328,16 @@ std::shared_ptr<ISharedImage> SDLImageShared::scaleTo(const Point & size, SDL_Pa
 	return ret;
 }
 
-void SDLImageShared::exportBitmap(const boost::filesystem::path& path) const
+void SDLImageShared::exportBitmap(const boost::filesystem::path& path, SDL_Palette * palette) const
 {
-	SDL_SaveBMP(surf, path.string().c_str());
+	if (!surf)
+		return;
+
+	if (palette && surf->format->palette)
+		SDL_SetSurfacePalette(surf, palette);
+	IMG_SavePNG(surf, path.string().c_str());
+	if (palette && surf->format->palette)
+		SDL_SetSurfacePalette(surf, originalPalette);
 }
 
 void SDLImageIndexed::playerColored(PlayerColor player)
@@ -428,9 +436,11 @@ SDLImageIndexed::SDLImageIndexed(const std::shared_ptr<ISharedImage> & image, SD
 	currentPalette = SDL_AllocPalette(originalPalette->ncolors);
 	SDL_SetPaletteColors(currentPalette, originalPalette->colors, 0, originalPalette->ncolors);
 
-	setOverlayColor(Colors::TRANSPARENCY);
 	if (mode == EImageBlitMode::ALPHA)
+	{
+		setOverlayColor(Colors::TRANSPARENCY);
 		setShadowTransparency(1.0);
+	}
 }
 
 SDLImageIndexed::~SDLImageIndexed()
@@ -468,7 +478,9 @@ void SDLImageIndexed::setShadowTransparency(float factor)
 
 void SDLImageIndexed::setOverlayColor(const ColorRGBA & color)
 {
-	for (int i : {5,6,7})
+	currentPalette->colors[5] = CSDL_Ext::toSDL(addColors(targetPalette[5], color));
+
+	for (int i : {6,7})
 	{
 		if (colorsSimilar(originalPalette->colors[i], sourcePalette[i]))
 			currentPalette->colors[i] = CSDL_Ext::toSDL(addColors(targetPalette[i], color));
@@ -500,8 +512,10 @@ void SDLImageIndexed::setOverlayEnabled(bool on)
 {
 	if (on)
 		setOverlayColor(Colors::WHITE_TRUE);
-	else
+
+	if (!on && blitMode == EImageBlitMode::ALPHA)
 		setOverlayColor(Colors::TRANSPARENCY);
+
 	overlayEnabled = on;
 }
 
@@ -532,6 +546,11 @@ void SDLImageIndexed::draw(SDL_Surface * where, const Point & pos, const Rect *
 	image->draw(where, currentPalette, pos, src, Colors::WHITE_TRUE, alphaValue, blitMode);
 }
 
+void SDLImageIndexed::exportBitmap(const boost::filesystem::path & path) const
+{
+	image->exportBitmap(path, currentPalette);
+}
+
 void SDLImageIndexed::scaleTo(const Point & size)
 {
 	image = image->scaleTo(size, currentPalette);
@@ -552,9 +571,9 @@ void SDLImageRGB::scaleInteger(int factor)
 	image = image->scaleInteger(factor, nullptr);
 }
 
-void SDLImageBase::exportBitmap(const boost::filesystem::path & path) const
+void SDLImageRGB::exportBitmap(const boost::filesystem::path & path) const
 {
-	image->exportBitmap(path);
+	image->exportBitmap(path, nullptr);
 }
 
 bool SDLImageBase::isTransparent(const Point & coords) const

+ 3 - 2
client/renderSDL/SDLImage.h

@@ -51,7 +51,7 @@ public:
 
 	void draw(SDL_Surface * where, SDL_Palette * palette, const Point & dest, const Rect * src, const ColorRGBA & colorMultiplier, uint8_t alpha, EImageBlitMode mode) const override;
 
-	void exportBitmap(const boost::filesystem::path & path) const override;
+	void exportBitmap(const boost::filesystem::path & path, SDL_Palette * palette) const override;
 	Point dimensions() const override;
 	bool isTransparent(const Point & coords) const override;
 	std::shared_ptr<IImage> createImageReference(EImageBlitMode mode) override;
@@ -74,7 +74,6 @@ protected:
 public:
 	SDLImageBase(const std::shared_ptr<ISharedImage> & image, EImageBlitMode mode);
 
-	void exportBitmap(const boost::filesystem::path & path) const override;
 	bool isTransparent(const Point & coords) const override;
 	Point dimensions() const override;
 	void setAlpha(uint8_t value) override;
@@ -103,6 +102,7 @@ public:
 	void adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask) override;
 	void scaleInteger(int factor) override;
 	void scaleTo(const Point & size) override;
+	void exportBitmap(const boost::filesystem::path & path) const override;
 
 	void setShadowEnabled(bool on) override;
 	void setBodyEnabled(bool on) override;
@@ -121,6 +121,7 @@ public:
 	void adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask) override;
 	void scaleInteger(int factor) override;
 	void scaleTo(const Point & size) override;
+	void exportBitmap(const boost::filesystem::path & path) const override;
 
 	void setShadowEnabled(bool on) override;
 	void setBodyEnabled(bool on) override;

+ 9 - 18
client/widgets/Images.cpp

@@ -14,6 +14,7 @@
 
 #include "../gui/CGuiHandler.h"
 #include "../renderSDL/SDL_Extensions.h"
+#include "../render/AssetGenerator.h"
 #include "../render/IImage.h"
 #include "../render/IRenderHandler.h"
 #include "../render/CAnimation.h"
@@ -172,28 +173,18 @@ void FilledTexturePlayerIndexed::setPlayerColor(PlayerColor player)
 	texture->playerColored(player);
 }
 
+FilledTexturePlayerColored::FilledTexturePlayerColored(Rect position)
+	:CFilledTexture(ImagePath::builtin("DiBoxBck"), position)
+{
+}
+
 void FilledTexturePlayerColored::setPlayerColor(PlayerColor player)
 {
-	// Color transform to make color of brown DIBOX.PCX texture match color of specified player
-	std::array<ColorFilter, PlayerColor::PLAYER_LIMIT_I> filters = {
-		ColorFilter::genRangeShifter(  0.25,  0,     0,     1.25, 0.00, 0.00 ), // red
-		ColorFilter::genRangeShifter(  0,     0,     0,     0.45, 1.20, 4.50 ), // blue
-		ColorFilter::genRangeShifter(  0.40,  0.27,  0.23,  1.10, 1.20, 1.15 ), // tan
-		ColorFilter::genRangeShifter( -0.27,  0.10, -0.27,  0.70, 1.70, 0.70 ), // green
-		ColorFilter::genRangeShifter(  0.47,  0.17, -0.27,  1.60, 1.20, 0.70 ), // orange
-		ColorFilter::genRangeShifter(  0.12, -0.1,   0.25,  1.15, 1.20, 2.20 ), // purple
-		ColorFilter::genRangeShifter( -0.13,  0.23,  0.23,  0.90, 1.20, 2.20 ), // teal
-		ColorFilter::genRangeShifter(  0.44,  0.15,  0.25,  1.00, 1.00, 1.75 )  // pink
-	};
+	AssetGenerator::createPlayerColoredBackground(player);
 
-	assert(player.isValidPlayer());
-	if (!player.isValidPlayer())
-	{
-		logGlobal->error("Unable to colorize to invalid player color %d!", static_cast<int>(player.getNum()));
-		return;
-	}
+	ImagePath imagePath = ImagePath::builtin("DialogBoxBackground_" + player.toString() + ".bmp");
 
-	texture->adjustPalette(filters[player.getNum()], 0);
+	texture = GH.renderHandler().loadImage(imagePath, EImageBlitMode::COLORKEY);
 }
 
 CAnimImage::CAnimImage(const AnimationPath & name, size_t Frame, size_t Group, int x, int y, ui8 Flags):

+ 1 - 1
client/widgets/Images.h

@@ -92,7 +92,7 @@ public:
 class FilledTexturePlayerColored : public CFilledTexture
 {
 public:
-	using CFilledTexture::CFilledTexture;
+	FilledTexturePlayerColored(Rect position);
 
 	void setPlayerColor(PlayerColor player);
 };

+ 2 - 1
client/windows/GUIClasses.cpp

@@ -744,8 +744,9 @@ CShipyardWindow::CShipyardWindow(const TResources & cost, int state, BoatId boat
 		AnimationPath boatFilename = boatConstructor->getBoatAnimationName();
 
 		Point waterCenter = Point(bgWater->pos.x+bgWater->pos.w/2, bgWater->pos.y+bgWater->pos.h/2);
-		bgShip = std::make_shared<CAnimImage>(boatFilename, 0, 7, 120, 96, 0);
+		bgShip = std::make_shared<CShowableAnim>(120, 96, boatFilename, CShowableAnim::CREATURE_MODE, 100, 7);
 		bgShip->center(waterCenter);
+		bgWater->needRefresh = true;
 	}
 
 	// Create resource icons and costs.

+ 1 - 1
client/windows/GUIClasses.h

@@ -296,7 +296,7 @@ public:
 class CShipyardWindow : public CStatusbarWindow
 {
 	std::shared_ptr<CPicture> bgWater;
-	std::shared_ptr<CAnimImage> bgShip;
+	std::shared_ptr<CShowableAnim> bgShip;
 
 	std::shared_ptr<CLabel> title;
 	std::shared_ptr<CLabel> costLabel;