diff --git a/OpenRA.Game/Graphics/Minimap.cs b/OpenRA.Game/Graphics/Minimap.cs deleted file mode 100644 index 1802275968..0000000000 --- a/OpenRA.Game/Graphics/Minimap.cs +++ /dev/null @@ -1,149 +0,0 @@ -#region Copyright & License Information -/* - * Copyright 2007-2016 The OpenRA Developers (see AUTHORS) - * This file is part of OpenRA, which is free software. It is made - * available to you under the terms of the GNU General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. For more - * information, see COPYING. - */ -#endregion - -using System; -using System.Drawing; -using System.Drawing.Imaging; -using System.Linq; -using OpenRA.Traits; - -namespace OpenRA.Graphics -{ - public static class Minimap - { - public static Bitmap TerrainBitmap(TileSet tileset, Map map, bool actualSize = false) - { - var isRectangularIsometric = map.Grid.Type == MapGridType.RectangularIsometric; - var b = map.Bounds; - - // Fudge the heightmap offset by adding as much extra as we need / can. - // This tries to correct for our incorrect assumption that MPos == PPos - var heightOffset = Math.Min(map.Grid.MaximumTerrainHeight, map.MapSize.Y - b.Bottom); - var width = b.Width; - var height = b.Height + heightOffset; - - var bitmapWidth = width; - if (isRectangularIsometric) - bitmapWidth = 2 * bitmapWidth - 1; - - if (!actualSize) - bitmapWidth = height = Exts.NextPowerOf2(Math.Max(bitmapWidth, height)); - - var terrain = new Bitmap(bitmapWidth, height); - - var bitmapData = terrain.LockBits(terrain.Bounds(), - ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb); - - var mapTiles = map.MapTiles.Value; - - unsafe - { - var colors = (int*)bitmapData.Scan0; - var stride = bitmapData.Stride / 4; - for (var y = 0; y < height; y++) - { - for (var x = 0; x < width; x++) - { - var uv = new MPos(x + b.Left, y + b.Top); - var type = tileset.GetTileInfo(mapTiles[uv]); - var leftColor = type != null ? type.LeftColor : Color.Black; - - if (isRectangularIsometric) - { - // Odd rows are shifted right by 1px - var dx = uv.V & 1; - var rightColor = type != null ? type.RightColor : Color.Black; - if (x + dx > 0) - colors[y * stride + 2 * x + dx - 1] = leftColor.ToArgb(); - - if (2 * x + dx < stride) - colors[y * stride + 2 * x + dx] = rightColor.ToArgb(); - } - else - colors[y * stride + x] = leftColor.ToArgb(); - } - } - } - - terrain.UnlockBits(bitmapData); - return terrain; - } - - // Add the static resources defined in the map; if the map lives - // in a world use AddCustomTerrain instead - static Bitmap AddStaticResources(TileSet tileset, Map map, Ruleset resourceRules, Bitmap terrainBitmap) - { - var terrain = new Bitmap(terrainBitmap); - var isRectangularIsometric = map.Grid.Type == MapGridType.RectangularIsometric; - var b = map.Bounds; - - // Fudge the heightmap offset by adding as much extra as we need / can - // This tries to correct for our incorrect assumption that MPos == PPos - var heightOffset = Math.Min(map.Grid.MaximumTerrainHeight, map.MapSize.Y - b.Bottom); - var width = b.Width; - var height = b.Height + heightOffset; - - var resources = resourceRules.Actors["world"].TraitInfos() - .ToDictionary(r => r.ResourceType, r => r.TerrainType); - - var bitmapData = terrain.LockBits(terrain.Bounds(), - ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb); - - unsafe - { - var colors = (int*)bitmapData.Scan0; - var stride = bitmapData.Stride / 4; - for (var y = 0; y < height; y++) - { - for (var x = 0; x < width; x++) - { - var uv = new MPos(x + b.Left, y + b.Top); - if (map.MapResources.Value[uv].Type == 0) - continue; - - string res; - if (!resources.TryGetValue(map.MapResources.Value[uv].Type, out res)) - continue; - - var color = tileset[tileset.GetTerrainIndex(res)].Color.ToArgb(); - if (isRectangularIsometric) - { - // Odd rows are shifted right by 1px - var dx = uv.V & 1; - if (x + dx > 0) - colors[y * stride + 2 * x + dx - 1] = color; - - if (2 * x + dx < stride) - colors[y * stride + 2 * x + dx] = color; - } - else - colors[y * stride + x] = color; - } - } - } - - terrain.UnlockBits(bitmapData); - - return terrain; - } - - public static Bitmap RenderMapPreview(TileSet tileset, Map map, bool actualSize) - { - return RenderMapPreview(tileset, map, map.Rules, actualSize); - } - - public static Bitmap RenderMapPreview(TileSet tileset, Map map, Ruleset resourceRules, bool actualSize) - { - using (var terrain = TerrainBitmap(tileset, map, actualSize)) - return AddStaticResources(tileset, map, resourceRules, terrain); - } - } -} diff --git a/OpenRA.Game/Map/Map.cs b/OpenRA.Game/Map/Map.cs index e51cbd1c21..5a20f40088 100644 --- a/OpenRA.Game/Map/Map.cs +++ b/OpenRA.Game/Map/Map.cs @@ -12,6 +12,7 @@ using System; using System.Collections.Generic; using System.Drawing; +using System.Drawing.Imaging; using System.IO; using System.Linq; using System.Security.Cryptography; @@ -89,7 +90,7 @@ namespace OpenRA public string Type = "Conquest"; public string Author; public string Tileset; - public Bitmap CustomPreview; + public bool LockPreview; public bool InvalidCustomRules { get; private set; } public WVec OffsetOfSubCell(SubCell subCell) @@ -281,10 +282,6 @@ namespace OpenRA LastSubCell = (SubCell)(SubCellOffsets.Length - 1); DefaultSubCell = (SubCell)Grid.SubCellDefaultIndex; - if (Package.Contains("map.png")) - using (var dataStream = Package.GetStream("map.png")) - CustomPreview = new Bitmap(dataStream); - PostInit(); Uid = ComputeUID(Package); @@ -444,6 +441,10 @@ namespace OpenRA root.Add(new MiniYamlNode(field, FieldSaver.FormatValue(this, f))); } + // Save LockPreview field only if it's set + if (LockPreview) + root.Add(new MiniYamlNode("LockPreview", "True")); + root.Add(new MiniYamlNode("Players", null, PlayerDefinitions)); root.Add(new MiniYamlNode("Actors", null, ActorDefinitions)); @@ -468,6 +469,9 @@ namespace OpenRA foreach (var file in Package.Contents) toPackage.Update(file, Package.GetStream(file).ReadAllBytes()); + if (!LockPreview) + toPackage.Update("map.png", SavePreview()); + // Update the package with the new map data var s = root.WriteToString(); toPackage.Update("map.yaml", Encoding.UTF8.GetBytes(s)); @@ -615,6 +619,84 @@ namespace OpenRA return dataStream.ToArray(); } + public byte[] SavePreview() + { + var tileset = Rules.TileSets[Tileset]; + var resources = Rules.Actors["world"].TraitInfos() + .ToDictionary(r => r.ResourceType, r => r.TerrainType); + + using (var stream = new MemoryStream()) + { + var isRectangularIsometric = Grid.Type == MapGridType.RectangularIsometric; + + // Fudge the heightmap offset by adding as much extra as we need / can. + // This tries to correct for our incorrect assumption that MPos == PPos + var heightOffset = Math.Min(Grid.MaximumTerrainHeight, MapSize.Y - Bounds.Bottom); + var width = Bounds.Width; + var height = Bounds.Height + heightOffset; + + var bitmapWidth = width; + if (isRectangularIsometric) + bitmapWidth = 2 * bitmapWidth - 1; + + using (var bitmap = new Bitmap(bitmapWidth, height)) + { + var bitmapData = bitmap.LockBits(bitmap.Bounds(), + ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb); + + unsafe + { + var colors = (int*)bitmapData.Scan0; + var stride = bitmapData.Stride / 4; + Color leftColor, rightColor; + + for (var y = 0; y < height; y++) + { + for (var x = 0; x < width; x++) + { + var uv = new MPos(x + Bounds.Left, y + Bounds.Top); + var resourceType = MapResources.Value[uv].Type; + if (resourceType != 0) + { + // Cell contains resources + string res; + if (!resources.TryGetValue(resourceType, out res)) + continue; + + leftColor = rightColor = tileset[tileset.GetTerrainIndex(res)].Color; + } + else + { + // Cell contains terrain + var type = tileset.GetTileInfo(MapTiles.Value[uv]); + leftColor = type != null ? type.LeftColor : Color.Black; + rightColor = type != null ? type.RightColor : Color.Black; + } + + if (isRectangularIsometric) + { + // Odd rows are shifted right by 1px + var dx = uv.V & 1; + if (x + dx > 0) + colors[y * stride + 2 * x + dx - 1] = leftColor.ToArgb(); + + if (2 * x + dx < stride) + colors[y * stride + 2 * x + dx] = rightColor.ToArgb(); + } + else + colors[y * stride + x] = leftColor.ToArgb(); + } + } + } + + bitmap.UnlockBits(bitmapData); + bitmap.Save(stream, ImageFormat.Png); + } + + return stream.ToArray(); + } + } + public bool Contains(CPos cell) { // .ToMPos() returns the same result if the X and Y coordinates diff --git a/OpenRA.Game/Map/MapCache.cs b/OpenRA.Game/Map/MapCache.cs index e3722fc8d6..a2301bdb1a 100644 --- a/OpenRA.Game/Map/MapCache.cs +++ b/OpenRA.Game/Map/MapCache.cs @@ -193,28 +193,8 @@ namespace OpenRA // Render the minimap into the shared sheet foreach (var p in todo) { - // The rendering is thread safe because it only reads from the passed instances and writes to a new bitmap - var createdPreview = false; - var bitmap = p.CustomPreview; - if (bitmap == null) - { - createdPreview = true; - var map = new Map(modData, p.Package); - bitmap = Minimap.RenderMapPreview(modData.DefaultRules.TileSets[map.Tileset], map, modData.DefaultRules, true); - } - - Game.RunAfterTick(() => - { - try - { - p.SetMinimap(sheetBuilder.Add(bitmap)); - } - finally - { - if (createdPreview) - bitmap.Dispose(); - } - }); + if (p.Preview != null) + Game.RunAfterTick(() => p.SetMinimap(sheetBuilder.Add(p.Preview))); // Yuck... But this helps the UI Jank when opening the map selector significantly. Thread.Sleep(Environment.ProcessorCount == 1 ? 25 : 5); diff --git a/OpenRA.Game/Map/MapPreview.cs b/OpenRA.Game/Map/MapPreview.cs index 4eae134097..cbc0e79612 100644 --- a/OpenRA.Game/Map/MapPreview.cs +++ b/OpenRA.Game/Map/MapPreview.cs @@ -69,7 +69,7 @@ namespace OpenRA public CPos[] SpawnPoints { get; private set; } public MapGridType GridType { get; private set; } public Rectangle Bounds { get; private set; } - public Bitmap CustomPreview { get; private set; } + public Bitmap Preview { get; private set; } public MapStatus Status { get; private set; } public MapClassification Class { get; private set; } public MapVisibility Visibility { get; private set; } @@ -200,7 +200,7 @@ namespace OpenRA if (p.Contains("map.png")) using (var dataStream = p.GetStream("map.png")) - CustomPreview = new Bitmap(dataStream); + Preview = new Bitmap(dataStream); } bool EvaluateUserFriendliness(Dictionary players) @@ -253,11 +253,11 @@ namespace OpenRA SpawnPoints = spawns; GridType = r.map_grid_type; - CustomPreview = new Bitmap(new MemoryStream(Convert.FromBase64String(r.minimap))); + Preview = new Bitmap(new MemoryStream(Convert.FromBase64String(r.minimap))); } catch (Exception) { } - if (CustomPreview != null) + if (Preview != null) cache.CacheMinimap(this); } diff --git a/OpenRA.Game/OpenRA.Game.csproj b/OpenRA.Game/OpenRA.Game.csproj index 8b6134358c..983eeceb19 100644 --- a/OpenRA.Game/OpenRA.Game.csproj +++ b/OpenRA.Game/OpenRA.Game.csproj @@ -121,7 +121,6 @@ - diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj index 915b947708..3c894a8de3 100644 --- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj +++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj @@ -543,7 +543,6 @@ - diff --git a/OpenRA.Mods.Common/UtilityCommands/GenerateMinimapCommand.cs b/OpenRA.Mods.Common/UtilityCommands/GenerateMinimapCommand.cs deleted file mode 100644 index d2fcae348c..0000000000 --- a/OpenRA.Mods.Common/UtilityCommands/GenerateMinimapCommand.cs +++ /dev/null @@ -1,40 +0,0 @@ -#region Copyright & License Information -/* - * Copyright 2007-2016 The OpenRA Developers (see AUTHORS) - * This file is part of OpenRA, which is free software. It is made - * available to you under the terms of the GNU General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. For more - * information, see COPYING. - */ -#endregion - -using System; -using System.IO; -using OpenRA.Graphics; - -namespace OpenRA.Mods.Common.UtilityCommands -{ - class GenerateMinimapCommand : IUtilityCommand - { - public string Name { get { return "--map-preview"; } } - - public bool ValidateArguments(string[] args) - { - return args.Length >= 2; - } - - [Desc("MAPFILE", "Render PNG minimap of specified oramap file.")] - public void Run(ModData modData, string[] args) - { - Game.ModData = modData; - - var map = new Map(modData, modData.ModFiles.OpenPackage(args[1])); - var minimap = Minimap.RenderMapPreview(map.Rules.TileSets[map.Tileset], map, true); - - var dest = Path.GetFileNameWithoutExtension(args[1]) + ".png"; - minimap.Save(dest); - Console.WriteLine(dest + " saved."); - } - } -} diff --git a/OpenRA.Mods.Common/Widgets/Logic/Ingame/GameInfoLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Ingame/GameInfoLogic.cs index 38497b9e91..c2aea8c6e5 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Ingame/GameInfoLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Ingame/GameInfoLogic.cs @@ -54,7 +54,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic } // Briefing tab - if (world.Map.CustomPreview != null) + if (world.Map.Exists("map.png")) { numTabs++; var mapTabButton = widget.Get(string.Concat("BUTTON", numTabs.ToString()));