* Progress on classic color management

* Added cleanup routines for tilesets and object type previews.
This commit is contained in:
Nyerguds 2023-05-31 23:51:28 +02:00
parent 02d1992055
commit 303762eb31
22 changed files with 450 additions and 192 deletions

View File

@ -618,6 +618,8 @@
<Compile Include="Utility\SteamworksUGC.cs" /> <Compile Include="Utility\SteamworksUGC.cs" />
<Compile Include="Utility\TeamColor.cs" /> <Compile Include="Utility\TeamColor.cs" />
<Compile Include="Utility\TeamColorManager.cs" /> <Compile Include="Utility\TeamColorManager.cs" />
<Compile Include="Utility\TeamRemap.cs" />
<Compile Include="Utility\TeamRemapManager.cs" />
<Compile Include="Utility\TextureManager.cs" /> <Compile Include="Utility\TextureManager.cs" />
<Compile Include="Utility\TGASharpLib.cs" /> <Compile Include="Utility\TGASharpLib.cs" />
<Compile Include="Utility\Tileset.cs" /> <Compile Include="Utility\Tileset.cs" />

View File

@ -12,6 +12,8 @@
// distributed with this program. You should have received a copy of the // distributed with this program. You should have received a copy of the
// GNU General Public License along with permitted additional restrictions // GNU General Public License along with permitted additional restrictions
// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection // with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
using MobiusEditor.Model;
using System;
using System.Drawing; using System.Drawing;
namespace MobiusEditor.Interface namespace MobiusEditor.Interface
@ -20,5 +22,6 @@ namespace MobiusEditor.Interface
{ {
string DisplayName { get; } string DisplayName { get; }
Bitmap Thumbnail { get; } Bitmap Thumbnail { get; }
void Reset();
} }
} }

View File

@ -14,8 +14,6 @@ namespace MobiusEditor.Interface
{ {
/// <summary>Name of the color, used as identifier.</summary> /// <summary>Name of the color, used as identifier.</summary>
string Name { get; } string Name { get; }
/// <summary>A general color representing this team color.</summary>
Color BaseColor { get; }
/// <summary> /// <summary>
/// Apply this color to a given image. /// Apply this color to a given image.

View File

@ -15,7 +15,12 @@ namespace MobiusEditor.Interface
Color RemapBaseColor { get; } Color RemapBaseColor { get; }
ITeamColor this[string key] { get; } ITeamColor this[string key] { get; }
/// <summary>Gets a general color representing this team color.</summary>
/// <param name="key">Color key</param>
/// <returns>The basic color for this team color.</returns>
Color GetBaseColor(string key);
void Load(string path); void Load(string path);
void Reset(GameType gameType); void Reset(GameType gameType, string theater);
} }
} }

View File

@ -1059,7 +1059,7 @@ namespace MobiusEditor
{ {
var fileInfo = new FileInfo(fileName); var fileInfo = new FileInfo(fileName);
String name = fileInfo.FullName; String name = fileInfo.FullName;
if (!IdentifyMap(name, out FileType fileType, out GameType gameType, out bool isTdMegaMap)) if (!IdentifyMap(name, out FileType fileType, out GameType gameType, out bool isTdMegaMap, out string theater))
{ {
string extension = Path.GetExtension(name).TrimStart('.'); string extension = Path.GetExtension(name).TrimStart('.');
// No point in supporting jpeg here; the mapping needs distinct colours without fades. // No point in supporting jpeg here; the mapping needs distinct colours without fades.
@ -1092,7 +1092,7 @@ namespace MobiusEditor
ModPaths.TryGetValue(gameType, out modPaths); ModPaths.TryGetValue(gameType, out modPaths);
} }
loadMultiThreader.ExecuteThreaded( loadMultiThreader.ExecuteThreaded(
() => LoadFile(name, fileType, gameType, isTdMegaMap, modPaths), () => LoadFile(name, fileType, gameType, theater, isTdMegaMap, modPaths),
PostLoad, true, PostLoad, true,
(e,l) => LoadUnloadUi(e, l, loadMultiThreader), (e,l) => LoadUnloadUi(e, l, loadMultiThreader),
"Loading map"); "Loading map");
@ -1138,10 +1138,11 @@ namespace MobiusEditor
"Saving map"); "Saving map");
} }
private Boolean IdentifyMap(String loadFilename, out FileType fileType, out GameType gameType, out bool isTdMegaMap) private Boolean IdentifyMap(String loadFilename, out FileType fileType, out GameType gameType, out bool isTdMegaMap, out string theater)
{ {
fileType = FileType.None; fileType = FileType.None;
gameType = GameType.None; gameType = GameType.None;
theater = null;
isTdMegaMap = false; isTdMegaMap = false;
try try
{ {
@ -1212,6 +1213,7 @@ namespace MobiusEditor
{ {
return false; return false;
} }
theater = (iniContents["Map"].TryGetValue("Theater") ?? "temperate").ToLower();
switch (fileType) switch (fileType)
{ {
case FileType.INI: case FileType.INI:
@ -1308,20 +1310,20 @@ namespace MobiusEditor
} }
} }
private static IGamePlugin LoadNewPlugin(GameType gameType, bool isTdMegaMap, string[] modPaths) private static IGamePlugin LoadNewPlugin(GameType gameType, string theater, bool isTdMegaMap, string[] modPaths)
{ {
return LoadNewPlugin(gameType, isTdMegaMap, modPaths, false); return LoadNewPlugin(gameType, theater, isTdMegaMap, modPaths, false);
} }
private static IGamePlugin LoadNewPlugin(GameType gameType, bool isTdMegaMap, string[] modPaths, bool noImage) private static IGamePlugin LoadNewPlugin(GameType gameType, string theater, bool isTdMegaMap, string[] modPaths, bool noImage)
{ {
// Resetting to a specific game type will take care of classic mode. // Resetting to a specific game type will take care of classic mode.
Globals.TheArchiveManager.ExpandModPaths = modPaths; Globals.TheArchiveManager.ExpandModPaths = modPaths;
Globals.TheArchiveManager.Reset(gameType); Globals.TheArchiveManager.Reset(gameType);
Globals.TheGameTextManager.Reset(gameType); Globals.TheGameTextManager.Reset(gameType);
Globals.TheTextureManager.Reset(); Globals.TheTextureManager.Reset(gameType, theater);
Globals.TheTilesetManager.Reset(); Globals.TheTilesetManager.Reset();
Globals.TheTeamColorManager.Reset(gameType); Globals.TheTeamColorManager.Reset(gameType, theater);
IGamePlugin plugin = null; IGamePlugin plugin = null;
if (gameType == GameType.TiberianDawn) if (gameType == GameType.TiberianDawn)
{ {
@ -1334,6 +1336,7 @@ namespace MobiusEditor
else if (gameType == GameType.RedAlert) else if (gameType == GameType.RedAlert)
{ {
Globals.TheTeamColorManager.Load(@"DATA\XML\CNCRATEAMCOLORS.XML"); Globals.TheTeamColorManager.Load(@"DATA\XML\CNCRATEAMCOLORS.XML");
Globals.TheTeamColorManager.Load("palette.cps");
plugin = new RedAlert.GamePluginRA(!noImage); plugin = new RedAlert.GamePluginRA(!noImage);
} }
else if (gameType == GameType.SoleSurvivor) else if (gameType == GameType.SoleSurvivor)
@ -1358,35 +1361,26 @@ namespace MobiusEditor
new Vector3(0, 1, 1), new Vector2(0, 1), Color.FromArgb(61, 61, 59)); new Vector3(0, 1, 1), new Vector2(0, 1), Color.FromArgb(61, 61, 59));
tcm.AddTeamColor(teamColorNone); tcm.AddTeamColor(teamColorNone);
} }
else // if (teamColorManager is TeamRemapManager tcc)
{
// TODO classic team color
//TeamRemap teamColorNone = new TeamRemap(tcc);
//teamColorNone.Load(/*TODO*/);
//tcc.AddTeamColor(teamColorNone);
}
} }
private static void AddTeamColorPurple(ITeamColorManager teamColorManager) private static void AddTeamColorPurple(ITeamColorManager teamColorManager)
{ {
if (teamColorManager is TeamColorManager tcm) if (teamColorManager is TeamColorManager tcm)
{ {
// Add extra purple for flag.. // Add extra colors for flags.
TeamColor teamColorPurple = new TeamColor(tcm); TeamColor teamColorSeven = new TeamColor(tcm);
teamColorPurple.Load("PURPLE", "BASE_TEAM", teamColorSeven.Load(tcm.GetItem("BAD_UNIT"), "MULTI7");
tcm.AddTeamColor(teamColorSeven);
TeamColor teamColorEight = new TeamColor(tcm);
teamColorEight.Load("MULTI8", "BASE_TEAM",
Color.FromArgb(66, 255, 0), Color.FromArgb(0, 255, 56), 0, Color.FromArgb(66, 255, 0), Color.FromArgb(0, 255, 56), 0,
new Vector3(0.410f, 0.300f, 0.000f), new Vector3(0f, 1f, 1f), new Vector2(0.0f, 1.0f), new Vector3(0.410f, 0.300f, 0.000f), new Vector3(0f, 1f, 1f), new Vector2(0.0f, 1.0f),
new Vector3(0, 1, 1), new Vector2(0, 1), Color.FromArgb(77, 13, 255)); new Vector3(0, 1, 1), new Vector2(0, 1), Color.FromArgb(77, 13, 255));
tcm.AddTeamColor(teamColorPurple); tcm.AddTeamColor(teamColorEight);
}
else // if (teamColorManager is TeamRemapManager tcc)
{
// TODO classic team color
//TeamRemap teamColorPurple = new TeamRemap(tcc);
//teamColorPurple.Load(/*TODO*/);
//tcc.AddTeamColor(teamColorPurple);
} }
} }
/// <summary> /// <summary>
/// The separate-threaded part for making a new map. /// The separate-threaded part for making a new map.
/// </summary> /// </summary>
@ -1424,7 +1418,7 @@ namespace MobiusEditor
} }
try try
{ {
IGamePlugin plugin = LoadNewPlugin(gameType, isTdMegaMap, modPaths); IGamePlugin plugin = LoadNewPlugin(gameType, theater, isTdMegaMap, modPaths);
// This initialises the theater // This initialises the theater
plugin.New(theater); plugin.New(theater);
if (SteamworksUGC.IsInit) if (SteamworksUGC.IsInit)
@ -1488,11 +1482,11 @@ namespace MobiusEditor
/// <param name="isTdMegaMap"></param> /// <param name="isTdMegaMap"></param>
/// <param name="modPaths"></param> /// <param name="modPaths"></param>
/// <returns></returns> /// <returns></returns>
private static MapLoadInfo LoadFile(string loadFilename, FileType fileType, GameType gameType, bool isTdMegaMap, string[] modPaths) private static MapLoadInfo LoadFile(string loadFilename, FileType fileType, GameType gameType, string theater, bool isTdMegaMap, string[] modPaths)
{ {
try try
{ {
IGamePlugin plugin = LoadNewPlugin(gameType, isTdMegaMap, modPaths); IGamePlugin plugin = LoadNewPlugin(gameType, theater, isTdMegaMap, modPaths);
string[] errors = plugin.Load(loadFilename, fileType).ToArray(); string[] errors = plugin.Load(loadFilename, fileType).ToArray();
return new MapLoadInfo(loadFilename, fileType, plugin, errors); return new MapLoadInfo(loadFilename, fileType, plugin, errors);
} }
@ -1650,7 +1644,7 @@ namespace MobiusEditor
} }
// Unload graphics // Unload graphics
Globals.TheTilesetManager.Reset(); Globals.TheTilesetManager.Reset();
Globals.TheTextureManager.Reset(); Globals.TheTextureManager.Reset(GameType.None, null);
// Clean up loaded file status // Clean up loaded file status
filename = null; filename = null;
loadedFileType = FileType.None; loadedFileType = FileType.None;

View File

@ -64,7 +64,7 @@ namespace MobiusEditor.Model
public int Storage { get; set; } public int Storage { get; set; }
public Rectangle OverlapBounds => new Rectangle(Point.Empty, new Size(OccupyMask.GetLength(1), OccupyMask.GetLength(0))); public Rectangle OverlapBounds => new Rectangle(Point.Empty, new Size(this.OccupyMask.GetLength(1), this.OccupyMask.GetLength(0)));
public bool[,] OpaqueMask { get; private set; } public bool[,] OpaqueMask { get; private set; }
public bool[,] OccupyMask { get; private set; } public bool[,] OccupyMask { get; private set; }
@ -76,19 +76,19 @@ namespace MobiusEditor.Model
public bool HasBib public bool HasBib
{ {
get { return (Flag & BuildingTypeFlag.Bib) == BuildingTypeFlag.Bib; } get { return (this.Flag & BuildingTypeFlag.Bib) == BuildingTypeFlag.Bib; }
set set
{ {
// Bibs are only supported for widths 2 to 4 // Bibs are only supported for widths 2 to 4
if (value && Size.Width >= 2 && Size.Width <= 4) if (value && this.Size.Width >= 2 && this.Size.Width <= 4)
{ {
Flag |= BuildingTypeFlag.Bib; this.Flag |= BuildingTypeFlag.Bib;
} }
else else
{ {
Flag &= ~BuildingTypeFlag.Bib; this.Flag &= ~BuildingTypeFlag.Bib;
} }
RecalculateBibs(); this.RecalculateBibs();
} }
} }
@ -101,14 +101,14 @@ namespace MobiusEditor.Model
public bool IsAircraft => false; public bool IsAircraft => false;
public bool IsFixedWing => false; public bool IsFixedWing => false;
public bool IsFake => (Flag & BuildingTypeFlag.Fake) == BuildingTypeFlag.Fake; public bool IsFake => (this.Flag & BuildingTypeFlag.Fake) == BuildingTypeFlag.Fake;
public bool HasTurret => (Flag & BuildingTypeFlag.Turret) == BuildingTypeFlag.Turret; public bool HasTurret => (this.Flag & BuildingTypeFlag.Turret) == BuildingTypeFlag.Turret;
public bool IsSingleFrame => (Flag & BuildingTypeFlag.SingleFrame) == BuildingTypeFlag.SingleFrame; public bool IsSingleFrame => (this.Flag & BuildingTypeFlag.SingleFrame) == BuildingTypeFlag.SingleFrame;
public bool CanRemap => (Flag & BuildingTypeFlag.NoRemap) != BuildingTypeFlag.NoRemap; public bool CanRemap => (this.Flag & BuildingTypeFlag.NoRemap) != BuildingTypeFlag.NoRemap;
/// <summary> /// <summary>
/// Indicates buildings that have pieces sticking out at the top that should not overlap the objects on these cells. /// Indicates buildings that have pieces sticking out at the top that should not overlap the objects on these cells.
/// </summary> /// </summary>
public bool IsFlat => (Flag & BuildingTypeFlag.Flat) == BuildingTypeFlag.Flat; public bool IsFlat => (this.Flag & BuildingTypeFlag.Flat) == BuildingTypeFlag.Flat;
public BuildingType(sbyte id, string name, string textId, int powerProd, int powerUse, int storage, int width, int height, string occupyMask, string ownerHouse, TheaterType[] theaters, string factoryOverlay, int frameOffset, String graphicsSource, BuildingTypeFlag flag) public BuildingType(sbyte id, string name, string textId, int powerProd, int powerUse, int storage, int width, int height, string occupyMask, string ownerHouse, TheaterType[] theaters, string factoryOverlay, int frameOffset, String graphicsSource, BuildingTypeFlag flag)
{ {
@ -118,7 +118,7 @@ namespace MobiusEditor.Model
this.Name = name; this.Name = name;
this.GraphicsSource = graphicsSource ?? name; this.GraphicsSource = graphicsSource ?? name;
this.DisplayName = !String.IsNullOrEmpty(textId) && !String.IsNullOrEmpty(Globals.TheGameTextManager[textId]) this.DisplayName = !String.IsNullOrEmpty(textId) && !String.IsNullOrEmpty(Globals.TheGameTextManager[textId])
? Globals.TheGameTextManager[textId] + " (" + Name.ToUpperInvariant() + ")" ? Globals.TheGameTextManager[textId] + " (" + this.Name.ToUpperInvariant() + ")"
: name.ToUpperInvariant(); : name.ToUpperInvariant();
this.PowerProduction = powerProd; this.PowerProduction = powerProd;
this.PowerUsage = powerUse; this.PowerUsage = powerUse;
@ -184,30 +184,30 @@ namespace MobiusEditor.Model
private void RecalculateBibs() private void RecalculateBibs()
{ {
int maskY = BaseOccupyMask.GetLength(0); int maskY = this.BaseOccupyMask.GetLength(0);
int maskX = BaseOccupyMask.GetLength(1); int maskX = this.BaseOccupyMask.GetLength(1);
if (HasBib) if (this.HasBib)
{ {
OccupyMask = new bool[maskY + 1, maskX]; this.OccupyMask = new bool[maskY + 1, maskX];
for (var y = 0; y < maskY; ++y) for (int y = 0; y < maskY; ++y)
{ {
for (var x = 0; x < maskX; ++x) for (int x = 0; x < maskX; ++x)
{ {
OccupyMask[y, x] = BaseOccupyMask[y, x]; this.OccupyMask[y, x] = this.BaseOccupyMask[y, x];
} }
} }
if (Globals.BlockingBibs) if (Globals.BlockingBibs)
{ {
for (var x = 0; x < maskX; ++x) for (int x = 0; x < maskX; ++x)
{ {
OccupyMask[maskY, x] = true; this.OccupyMask[maskY, x] = true;
OccupyMask[maskY - 1, x] = true; this.OccupyMask[maskY - 1, x] = true;
} }
} }
} }
else else
{ {
OccupyMask = BaseOccupyMask; this.OccupyMask = this.BaseOccupyMask;
} }
} }
@ -218,15 +218,15 @@ namespace MobiusEditor.Model
int baseMaskX = this.BaseOccupyMask.GetLength(1); int baseMaskX = this.BaseOccupyMask.GetLength(1);
string occupyMask = GeneralUtils.GetStringFromMask(this.BaseOccupyMask); string occupyMask = GeneralUtils.GetStringFromMask(this.BaseOccupyMask);
TheaterType[] theaters = null; TheaterType[] theaters = null;
if (Theaters != null) if (this.Theaters != null)
{ {
int thLen = Theaters.Length; int thLen = this.Theaters.Length;
theaters = new TheaterType[thLen]; theaters = new TheaterType[thLen];
Array.Copy(Theaters, theaters, thLen); Array.Copy(this.Theaters, theaters, thLen);
} }
// Don't do lookup of the UI name. We don't have the original lookup ID at this point, so don't bother, and just restore the name afterwards. // Don't do lookup of the UI name. We don't have the original lookup ID at this point, so don't bother, and just restore the name afterwards.
BuildingType newBld = new BuildingType(ID, Name, null, PowerProduction, PowerUsage, Storage, baseMaskX, baseMaskY, occupyMask, OwnerHouse, theaters, FactoryOverlay, FrameOFfset, GraphicsSource, Flag); BuildingType newBld = new BuildingType(this.ID, this.Name, null, this.PowerProduction, this.PowerUsage, this.Storage, baseMaskX, baseMaskY, occupyMask, this.OwnerHouse, theaters, this.FactoryOverlay, this.FrameOFfset, this.GraphicsSource, this.Flag);
newBld.DisplayName = DisplayName; newBld.DisplayName = this.DisplayName;
return newBld; return newBld;
} }
@ -238,11 +238,11 @@ namespace MobiusEditor.Model
} }
else if (obj is sbyte) else if (obj is sbyte)
{ {
return ID == (sbyte)obj; return this.ID == (sbyte)obj;
} }
else if (obj is string) else if (obj is string)
{ {
return string.Equals(Name, obj as string, StringComparison.OrdinalIgnoreCase); return string.Equals(this.Name, obj as string, StringComparison.OrdinalIgnoreCase);
} }
return base.Equals(obj); return base.Equals(obj);
@ -250,44 +250,44 @@ namespace MobiusEditor.Model
public override int GetHashCode() public override int GetHashCode()
{ {
return ID.GetHashCode(); return this.ID.GetHashCode();
} }
public override string ToString() public override string ToString()
{ {
return (Name ?? String.Empty).ToUpperInvariant(); return (this.Name ?? String.Empty).ToUpperInvariant();
} }
public void Init(GameType gameType, TheaterType theater, HouseType house, DirectionType direction) public void Init(GameType gameType, TheaterType theater, HouseType house, DirectionType direction)
{ {
var oldImage = Thumbnail; Bitmap oldImage = this.Thumbnail;
var mockBuilding = new Building() Building mockBuilding = new Building()
{ {
Type = this, Type = this,
House = house, House = house,
Strength = 256, Strength = 256,
Direction = direction Direction = direction
}; };
var render = MapRenderer.RenderBuilding(gameType, theater, Point.Empty, Globals.PreviewTileSize, Globals.PreviewTileScale, mockBuilding); (Rectangle, Action<Graphics>, bool) render = MapRenderer.RenderBuilding(gameType, theater, Point.Empty, Globals.PreviewTileSize, Globals.PreviewTileScale, mockBuilding);
if (!render.Item1.IsEmpty) if (!render.Item1.IsEmpty)
{ {
var th = new Bitmap(render.Item1.Width, render.Item1.Height); Bitmap th = new Bitmap(render.Item1.Width, render.Item1.Height);
th.SetResolution(96, 96); th.SetResolution(96, 96);
using (var g = Graphics.FromImage(th)) using (Graphics g = Graphics.FromImage(th))
{ {
MapRenderer.SetRenderSettings(g, Globals.PreviewSmoothScale); MapRenderer.SetRenderSettings(g, Globals.PreviewSmoothScale);
render.Item2(g); render.Item2(g);
if (IsFake) if (this.IsFake)
{ {
MapRenderer.RenderFakeBuildingLabel(g, mockBuilding, Point.Empty, Globals.PreviewTileSize, Globals.PreviewTileScale, false); MapRenderer.RenderFakeBuildingLabel(g, mockBuilding, Point.Empty, Globals.PreviewTileSize, Globals.PreviewTileScale, false);
} }
} }
Thumbnail = th; this.Thumbnail = th;
OpaqueMask = GeneralUtils.FindOpaqueCells(th, Size, 10, 25, 0x80); this.OpaqueMask = GeneralUtils.FindOpaqueCells(th, this.Size, 10, 25, 0x80);
} }
else else
{ {
Thumbnail = null; this.Thumbnail = null;
} }
if (oldImage != null) if (oldImage != null)
{ {
@ -295,5 +295,16 @@ namespace MobiusEditor.Model
catch { /* ignore */ } catch { /* ignore */ }
} }
} }
public void Reset()
{
Bitmap oldImage = this.Thumbnail;
this.Thumbnail = null;
if (oldImage != null)
{
try { oldImage.Dispose(); }
catch { /* ignore */ }
}
}
} }
} }

View File

@ -122,5 +122,15 @@ namespace MobiusEditor.Model
catch { /* ignore */ } catch { /* ignore */ }
} }
} }
public void Reset()
{
Bitmap oldImage = this.Thumbnail;
this.Thumbnail = null;
if (oldImage != null)
{
try { oldImage.Dispose(); }
catch { /* ignore */ }
}
}
} }
} }

View File

@ -1987,5 +1987,46 @@ namespace MobiusEditor.Model
} }
return info; return info;
} }
public void ResetCachedGraphics()
{
// Dispose of cached images. This is non-destructive; the type objects themselves
// don't actually get disposed. Their thumbnail simply gets disposed and cleared.
foreach (ITechnoType technoType in this.AllTeamTechnoTypes)
{
// units, boats, aircraft, infantry
technoType.Reset();
}
// probably not needed since it's in the team techno types.
foreach (UnitType unitType in this.AllUnitTypes)
{
unitType.Reset();
}
// probably not needed since it's in the team techno types.
foreach (InfantryType infantryType in this.AllInfantryTypes)
{
infantryType.Reset();
}
foreach (BuildingType buildingType in this.BuildingTypes)
{
buildingType.Reset();
}
foreach (TemplateType template in this.TemplateTypes)
{
template.Reset();
}
foreach (TerrainType terrainType in this.TerrainTypes)
{
terrainType.Reset();
}
foreach (OverlayType overlayType in this.OverlayTypes)
{
overlayType.Reset();
}
foreach (SmudgeType smudgeType in this.SmudgeTypes)
{
smudgeType.Reset();
}
}
} }
} }

View File

@ -191,5 +191,15 @@ namespace MobiusEditor.Model
catch { /* ignore */ } catch { /* ignore */ }
} }
} }
public void Reset()
{
Bitmap oldImage = this.Thumbnail;
this.Thumbnail = null;
if (oldImage != null)
{
try { oldImage.Dispose(); }
catch { /* ignore */ }
}
}
} }
} }

View File

@ -196,5 +196,15 @@ namespace MobiusEditor.Model
return null; return null;
} }
} }
public void Reset()
{
Bitmap oldImage = this.Thumbnail;
this.Thumbnail = null;
if (oldImage != null)
{
try { oldImage.Dispose(); }
catch { /* ignore */ }
}
}
} }
} }

View File

@ -484,5 +484,15 @@ namespace MobiusEditor.Model
// Should never happen. // Should never happen.
return new Point(0, 0); return new Point(0, 0);
} }
public void Reset()
{
Bitmap oldImage = this.Thumbnail;
this.Thumbnail = null;
if (oldImage != null)
{
try { oldImage.Dispose(); }
catch { /* ignore */ }
}
}
} }
} }

View File

@ -219,5 +219,15 @@ namespace MobiusEditor.Model
catch { /* ignore */ } catch { /* ignore */ }
} }
} }
public void Reset()
{
Bitmap oldImage = this.Thumbnail;
this.Thumbnail = null;
if (oldImage != null)
{
try { oldImage.Dispose(); }
catch { /* ignore */ }
}
}
} }
} }

View File

@ -184,5 +184,15 @@ namespace MobiusEditor.Model
catch { /* ignore */ } catch { /* ignore */ }
} }
} }
public void Reset()
{
Bitmap oldImage = this.Thumbnail;
this.Thumbnail = null;
if (oldImage != null)
{
try { oldImage.Dispose(); }
catch { /* ignore */ }
}
}
} }
} }

View File

@ -3803,7 +3803,10 @@ namespace MobiusEditor.RedAlert
{ {
if (disposing) if (disposing)
{ {
MapImage?.Dispose(); try { MapImage?.Dispose(); }
catch { /* ignore */ }
// Dispose of cached images in type objects. This is non-destructive; the type objects themselves don't actually get disposed.
Map.ResetCachedGraphics();
} }
disposedValue = true; disposedValue = true;
} }

View File

@ -1573,7 +1573,7 @@ namespace MobiusEditor.Render
return; return;
} }
ITeamColor tc = Globals.TheTeamColorManager[building.House.BuildingTeamColor]; ITeamColor tc = Globals.TheTeamColorManager[building.House.BuildingTeamColor];
Color circleColor = tc?.BaseColor ?? Globals.TheTeamColorManager.RemapBaseColor; Color circleColor = Globals.TheTeamColorManager.GetBaseColor(tc.Name);
bool[,] cells = building.Type.BaseOccupyMask; bool[,] cells = building.Type.BaseOccupyMask;
int maskY = cells.GetLength(0); int maskY = cells.GetLength(0);
int maskX = cells.GetLength(1); int maskX = cells.GetLength(1);
@ -1601,7 +1601,7 @@ namespace MobiusEditor.Render
return; return;
} }
ITeamColor tc = Globals.TheTeamColorManager[unit.House.BuildingTeamColor]; ITeamColor tc = Globals.TheTeamColorManager[unit.House.BuildingTeamColor];
Color circleColor = tc?.BaseColor ?? Globals.TheTeamColorManager.RemapBaseColor; Color circleColor = Globals.TheTeamColorManager.GetBaseColor(tc.Name);
Color alphacorr = Color.FromArgb(unit.Tint.A * 128 / 256, circleColor); Color alphacorr = Color.FromArgb(unit.Tint.A * 128 / 256, circleColor);
if (isJammer) if (isJammer)
{ {

View File

@ -394,9 +394,9 @@ namespace MobiusEditor.TiberianDawn
flagColors[mpId] = Globals.TheTeamColorManager[house.UnitTeamColor]; flagColors[mpId] = Globals.TheTeamColorManager[house.UnitTeamColor];
} }
// Metallic light blue // Metallic light blue
flagColors[6] = Globals.TheTeamColorManager["BAD_UNIT"]; flagColors[6] = Globals.TheTeamColorManager["MULTI7"];
// RA Purple // RA Purple
flagColors[7] = Globals.TheTeamColorManager["PURPLE"]; flagColors[7] = Globals.TheTeamColorManager["MULTI8"];
Size mapSize = !megaMap ? Constants.MaxSize : Constants.MaxSizeMega; Size mapSize = !megaMap ? Constants.MaxSize : Constants.MaxSizeMega;
Map = new Map(basicSection, null, mapSize, typeof(House), houseTypes, Map = new Map(basicSection, null, mapSize, typeof(House), houseTypes,
flagColors, TheaterTypes.GetTypes(), TemplateTypes.GetTypes(), flagColors, TheaterTypes.GetTypes(), TemplateTypes.GetTypes(),
@ -3284,7 +3284,10 @@ namespace MobiusEditor.TiberianDawn
{ {
if (disposing) if (disposing)
{ {
MapImage?.Dispose(); try { MapImage?.Dispose(); }
catch { /* ignore */ }
// Dispose of cached images in type objects. This is non-destructive; the type objects themselves don't actually get disposed.
Map.ResetCachedGraphics();
} }
disposedValue = true; disposedValue = true;
} }

View File

@ -85,14 +85,13 @@ namespace MobiusEditor.Utility
public TeamColor(TeamColorManager teamColorManager, TeamColor col, string newName, Vector3 hsvShiftOverride) public TeamColor(TeamColorManager teamColorManager, TeamColor col, string newName, Vector3 hsvShiftOverride)
{ {
this.teamColorManager = teamColorManager; this.teamColorManager = teamColorManager;
this.Load(col); this.Load(col, newName);
this.Name = newName;
this.hsvShift = hsvShiftOverride; this.hsvShift = hsvShiftOverride;
} }
public void Load(TeamColor col) public void Load(TeamColor col, string newName)
{ {
this.Name = col.Name; this.Name = newName ?? col.Name;
this.Variant = col.Variant; this.Variant = col.Variant;
this.lowerBounds = col.LowerBounds; this.lowerBounds = col.LowerBounds;
this.upperBounds = col.UpperBounds; this.upperBounds = col.UpperBounds;

View File

@ -28,8 +28,16 @@ namespace MobiusEditor.Utility
private readonly IArchiveManager megafileManager; private readonly IArchiveManager megafileManager;
public Color RemapBaseColor => Color.FromArgb(0x00, 0xFF, 0x00); public Color RemapBaseColor => Color.FromArgb(0x00, 0xFF, 0x00);
public ITeamColor this[string key] => !string.IsNullOrEmpty(key) ? (ITeamColor)teamColors[key]: null; public ITeamColor this[string key] => !string.IsNullOrEmpty(key) && teamColors.ContainsKey(key) ? (ITeamColor)teamColors[key]: null;
public Color GetBaseColor(string key)
{
if (!string.IsNullOrEmpty(key) && teamColors.ContainsKey(key))
{
return teamColors[key].BaseColor;
}
return RemapBaseColor;
}
public TeamColor GetItem(string key) => !string.IsNullOrEmpty(key) ? teamColors[key] : null; public TeamColor GetItem(string key) => !string.IsNullOrEmpty(key) ? teamColors[key] : null;
public void RemoveTeamColor(string col) public void RemoveTeamColor(string col)
@ -56,12 +64,12 @@ namespace MobiusEditor.Utility
} }
} }
public TeamColorManager(IArchiveManager megafileManager, params string[] expandModPaths) public TeamColorManager(IArchiveManager megafileManager)
{ {
this.megafileManager = megafileManager; this.megafileManager = megafileManager;
} }
public void Reset(GameType gameType) public void Reset(GameType gameType, string theater)
{ {
teamColors.Clear(); teamColors.Clear();
} }

View File

@ -0,0 +1,56 @@
using MobiusEditor.Interface;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MobiusEditor.Utility
{
class TeamRemap : ITeamColor
{
public string Name { get; private set; }
public byte UnitRadarColor { get; private set; }
public byte BuildingRadarColor { get; private set; }
private byte[] remapTable;
public TeamRemap(string name, byte unitRadarColor, byte buildingRadarColor, byte remapstart, byte[] remapValues)
{
this.Name = name;
this.UnitRadarColor = unitRadarColor;
this.BuildingRadarColor = buildingRadarColor;
this.remapTable = Enumerable.Range(0, 0x100).Cast<byte>().ToArray();
int max = Math.Max(0x100, remapstart + remapValues.Length);
for (int i = 0; i < max; ++i)
{
remapTable[remapstart + i] = remapValues[i];
}
}
public TeamRemap(string name, byte unitRadarColor, byte buildingRadarColor, byte[] remapOrigins, byte[] remapValues)
{
this.Name = name;
this.UnitRadarColor = unitRadarColor;
this.BuildingRadarColor = buildingRadarColor;
this.remapTable = Enumerable.Range(0, 0x100).Cast<byte>().ToArray();
int max = Math.Max(remapOrigins.Length, remapValues.Length);
for (int i = 0; i < max; ++i)
{
remapTable[remapOrigins[i]] = remapValues[i];
}
}
public void ApplyToImage(Bitmap image)
{
throw new NotImplementedException();
}
public void ApplyToImage(Bitmap image, out Rectangle opaqueBounds)
{
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,137 @@
using MobiusEditor.Interface;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace MobiusEditor.Utility
{
class TeamRemapManager : ITeamColorManager
{
// TD remap
public static readonly TeamRemap RemapTdGoodGuy = new TeamRemap("GOOD", 176, 180, 176, new byte[] { 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191 });
public static readonly TeamRemap RemapTdBadGuyRed = new TeamRemap("BAD_STRUCTURE", 127, 123, 176, new byte[] { 127, 126, 125, 124, 122, 46, 120, 47, 125, 124, 123, 122, 42, 121, 120, 120 });
public static readonly TeamRemap RemapTdBadGuyGray = new TeamRemap("BAD_UNIT", 127, 123, 176, new byte[] { 161, 200, 201, 202, 204, 205, 206, 12, 201, 202, 203, 204, 205, 115, 198, 114 });
// The color name identifiers in the remaster are all out of wack.
public static readonly TeamRemap RemapTdBlue = new TeamRemap("MULTI2", 2, 135, 176, new byte[] {2, 119, 118, 135, 136, 138, 112, 12, 118, 135, 136, 137, 138, 139, 114, 112});
public static readonly TeamRemap RemapTdOrange = new TeamRemap("MULTI5", 24, 26, 176, new byte[] { 24, 25, 26, 27, 29, 31, 46, 47, 26, 27, 28, 29, 30, 31, 43, 47 });
public static readonly TeamRemap RemapTdGreen = new TeamRemap("MULTI4", 167, 159, 176, new byte[] { 5, 165, 166, 167, 159, 142, 140, 199, 166, 167, 157, 3, 159, 143, 142, 141 });
public static readonly TeamRemap RemapTdLtBlue = new TeamRemap("MULTI6", 201, 203, 176, new byte[] { 161, 200, 201, 202, 204, 205, 206, 12, 201, 202, 203, 204, 205, 115, 198, 114 });
public static readonly TeamRemap RemapTdYellow = new TeamRemap("MULTI1", 5, 157, 176, new byte[] { 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191 });
public static readonly TeamRemap RemapTdRed = new TeamRemap("MULTI3", 127, 123, 176, new byte[] { 127, 126, 125, 124, 122, 46, 120, 47, 125, 124, 123, 122, 42, 121, 120, 120 });
// Added extra colours for flags
public static readonly TeamRemap RemapTdMul7 = new TeamRemap("MULTI7", 146, 209, 176, new byte[] { 146, 152, 209, 151, 173, 150, 173, 183, 146, 152, 209, 151, 173, 150, 173, 183 }); // Brown
public static readonly TeamRemap RemapTdMul8 = new TeamRemap("MULTI8", 214, 213, 176, new byte[] { 132, 133, 134, 213, 214, 121, 120, 12, 133, 134, 213, 214, 121, 174, 120, 199 }); // Burgundy
public static readonly TeamRemap RemapTdNone = new TeamRemap("NONE", 199, 199, 176, new byte[] { 14, 195, 196, 13, 169, 198, 199, 112, 14, 195, 196, 13, 169, 198, 199, 112 }); // Black
private static readonly Dictionary<string, TeamRemap> RemapsTd;
public const Byte MaxValueSixBit = 63;
private static readonly Byte[] ConvertToEightBit = new Byte[64];
static TeamRemapManager()
{
RemapsTd = (from field in typeof(ITeamColorManager).GetFields(BindingFlags.Static | BindingFlags.Public)
where field.IsInitOnly && typeof(TeamRemap).IsAssignableFrom(field.FieldType) && field.Name.StartsWith("RemapTd")
select field.GetValue(null) as TeamRemap).ToDictionary(trm => trm.Name);
// Build easy lookup tables for this, so no calculations are ever needed for this later.
for (Int32 i = 0; i < 64; ++i)
ConvertToEightBit[i] = (Byte)Math.Round(i * 255.0 / 63.0, MidpointRounding.ToEven);
}
private Dictionary<string, TeamRemap> remapsRa = new Dictionary<string, TeamRemap>();
private GameType currentlyLoadedGameType;
private Color[] currentlyLoadedPalette;
private readonly IArchiveManager mixfileManager;
public ITeamColor this[string key] => GetforCurrentGame(key);
public Color GetBaseColor(string key)
{
TeamRemap tc = GetforCurrentGame(key);
if (tc != null)
{
return currentlyLoadedPalette[tc.UnitRadarColor];
}
return RemapBaseColor;
}
private TeamRemap GetforCurrentGame(string key)
{
if (string.IsNullOrEmpty(key))
{
return null;
}
Dictionary<string, TeamRemap> currentRemaps;
switch (currentlyLoadedGameType)
{
case GameType.TiberianDawn:
case GameType.SoleSurvivor:
currentRemaps = RemapsTd;
break;
case GameType.RedAlert:
currentRemaps = this.remapsRa;
break;
default:
return null;
}
return currentRemaps.ContainsKey(key) ? currentRemaps[key] : null;
}
private Color remapBaseColor = Color.Black;
public Color RemapBaseColor => remapBaseColor;
public void Load(string path)
{
// Check if CPS, if so, fill in "this.remapsRa"
}
public TeamRemapManager(IArchiveManager fileManager)
{
this.mixfileManager = fileManager;
}
public void Reset(GameType gameType, string theater)
{
currentlyLoadedGameType = gameType;
currentlyLoadedPalette = new Color[0x100];
// file manager should already be reset to read from the correct game at this point.
using (Stream palette = mixfileManager.OpenFile(theater + ".pal"))
{
if (palette != null)
{
byte[] pal;
using (BinaryReader sr = new BinaryReader(palette))
{
pal = GeneralUtils.ReadAllBytes(sr);
}
int len = Math.Min(pal.Length / 3, 0x100);
int offs = 0;
for (int i = 0; i < len; ++i)
{
byte r = ConvertToEightBit[pal[offs + 0] & 0x3F];
byte g = ConvertToEightBit[pal[offs + 1] & 0x3F];
byte b = ConvertToEightBit[pal[offs + 2] & 0x3F];
currentlyLoadedPalette[i] = Color.FromArgb(r, g, b);
offs += 3;
}
}
}
if (gameType == GameType.TiberianDawn || gameType == GameType.SoleSurvivor)
{
}
}
public void setTheater()
{
}
}
}

View File

@ -33,25 +33,16 @@ namespace MobiusEditor.Utility
private static string MissingTexture = "DATA\\ART\\TEXTURES\\SRGB\\COMMON\\MISC\\MISSING.TGA"; private static string MissingTexture = "DATA\\ART\\TEXTURES\\SRGB\\COMMON\\MISC\\MISSING.TGA";
private bool processedMissingTexture = false; private bool processedMissingTexture = false;
#if false
private class ImageData
{
public TGA TGA;
public JObject Metadata;
}
#endif
private readonly IArchiveManager megafileManager; private readonly IArchiveManager megafileManager;
private Dictionary<string, Bitmap> cachedTextures = new Dictionary<string, Bitmap>(); private Dictionary<string, Bitmap> cachedTextures = new Dictionary<string, Bitmap>();
private Dictionary<(string, ITeamColor), (Bitmap, Rectangle)> teamColorTextures = new Dictionary<(string, ITeamColor), (Bitmap, Rectangle)>();
public TextureManager(IArchiveManager megafileManager) public TextureManager(IArchiveManager megafileManager)
{ {
this.megafileManager = megafileManager; this.megafileManager = megafileManager;
} }
public void Reset() public void Reset(GameType gameType, string theater)
{ {
Bitmap[] cachedImages = cachedTextures.Values.ToArray(); Bitmap[] cachedImages = cachedTextures.Values.ToArray();
cachedTextures.Clear(); cachedTextures.Clear();
@ -67,20 +58,6 @@ namespace MobiusEditor.Utility
// Ignore. // Ignore.
} }
} }
(Bitmap, Rectangle)[] cachedTeamImages = teamColorTextures.Values.ToArray();
teamColorTextures.Clear();
for (int i = 0; i < cachedTeamImages.Length; ++i)
{
try
{
(Bitmap bitmap, Rectangle opaqueBounds) = cachedTeamImages[i];
bitmap.Dispose();
}
catch
{
// Ignore.
}
}
} }
public (Bitmap, Rectangle) GetTexture(string filename, ITeamColor teamColor, bool generateFallback) public (Bitmap, Rectangle) GetTexture(string filename, ITeamColor teamColor, bool generateFallback)
@ -101,13 +78,9 @@ namespace MobiusEditor.Utility
return (retCopy, bounds); return (retCopy, bounds);
} }
} }
if (teamColorTextures.TryGetValue((filename, teamColor), out (Bitmap bitmap, Rectangle opaqueBounds) result)) Bitmap resBitmap;
{ Rectangle resBounds = Rectangle.Empty;
Bitmap retCopy = new Bitmap(result.bitmap); if (!cachedTextures.TryGetValue(filename, out _))
retCopy.SetResolution(96, 96);
return (retCopy, result.opaqueBounds);
}
if (!cachedTextures.TryGetValue(filename, out result.bitmap))
{ {
if (Path.GetExtension(filename).ToLower() == ".tga") if (Path.GetExtension(filename).ToLower() == ".tga")
{ {
@ -181,71 +154,8 @@ namespace MobiusEditor.Utility
cachedTextures[filename] = bitmap; cachedTextures[filename] = bitmap;
} }
} }
#if false
// Attempt to load parent directory as archive
var archiveDir = Path.GetDirectoryName(filename);
var archivePath = archiveDir + ".ZIP";
using (var fileStream = megafileManager.Open(archivePath))
{
if (fileStream != null)
{
using (var archive = new ZipArchive(fileStream, ZipArchiveMode.Read))
{
var images = new Dictionary<string, ImageData>();
foreach (var entry in archive.Entries)
{
var name = Path.GetFileNameWithoutExtension(entry.Name);
if (!images.TryGetValue(name, out ImageData imageData))
{
imageData = images[name] = new ImageData { TGA = null, Metadata = null };
}
if ((imageData.TGA == null) && (Path.GetExtension(entry.Name).ToLower() == ".tga"))
{
using (var stream = entry.Open())
using (var memStream = new MemoryStream())
{
stream.CopyTo(memStream);
imageData.TGA = new TGA(memStream);
}
}
else if ((imageData.Metadata == null) && (Path.GetExtension(entry.Name).ToLower() == ".meta"))
{
using (var stream = entry.Open())
using (var reader = new StreamReader(stream))
{
imageData.Metadata = JObject.Parse(reader.ReadToEnd());
}
}
if ((imageData.TGA != null) && (imageData.Metadata != null))
{
var bitmap = imageData.TGA.ToBitmap(true);
var size = new Size(imageData.Metadata["size"][0].ToObject<int>(), imageData.Metadata["size"][1].ToObject<int>());
var crop = Rectangle.FromLTRB(
imageData.Metadata["crop"][0].ToObject<int>(),
imageData.Metadata["crop"][1].ToObject<int>(),
imageData.Metadata["crop"][2].ToObject<int>(),
imageData.Metadata["crop"][3].ToObject<int>()
);
var uncroppedBitmap = new Bitmap(size.Width, size.Height, bitmap.PixelFormat);
uncroppedBitmap.SetResolution(96, 96);
using (var g = Graphics.FromImage(uncroppedBitmap))
{
g.DrawImage(bitmap, crop, new Rectangle(Point.Empty, bitmap.Size), GraphicsUnit.Pixel);
}
cachedTextures[Path.Combine(archiveDir, name) + ".tga"] = uncroppedBitmap;
images.Remove(name);
}
}
foreach (var item in images.Where(x => x.Value.TGA != null))
{
cachedTextures[Path.Combine(archiveDir, item.Key) + ".tga"] = item.Value.TGA.ToBitmap(true);
}
}
}
}
#endif
} }
if (!cachedTextures.TryGetValue(filename, out result.bitmap)) if (!cachedTextures.TryGetValue(filename, out resBitmap))
{ {
// Try loading as a DDS // Try loading as a DDS
var ddsFilename = Path.ChangeExtension(filename, ".DDS"); var ddsFilename = Path.ChangeExtension(filename, ".DDS");
@ -260,26 +170,25 @@ namespace MobiusEditor.Utility
} }
} }
} }
if (!cachedTextures.TryGetValue(filename, out result.bitmap)) if (!cachedTextures.TryGetValue(filename, out resBitmap))
{ {
return result; return (resBitmap, resBounds);
} }
Bitmap resBm = new Bitmap(result.bitmap); // Clone returned image.
Bitmap resBm = new Bitmap(resBitmap);
resBm.SetResolution(96, 96); resBm.SetResolution(96, 96);
result.bitmap = resBm; resBitmap = resBm;
if (teamColor != null) if (teamColor != null)
{ {
Rectangle opaqueBounds; Rectangle opaqueBounds;
teamColor.ApplyToImage(resBm, out opaqueBounds); teamColor.ApplyToImage(resBm, out opaqueBounds);
result.opaqueBounds = opaqueBounds; resBounds = opaqueBounds;
// EXPERIMENTAL: might be better not to cache this?
//teamColorTextures[(filename, teamColor)] = (new Bitmap(result.bitmap), result.opaqueBounds);
} }
else else
{ {
result.opaqueBounds = ImageUtils.CalculateOpaqueBounds(resBm); resBounds = ImageUtils.CalculateOpaqueBounds(resBm);
} }
return result; return (resBitmap, resBounds);
} }
private void LoadTgaFromZipFileStream(Stream fileStream, String name, ref TGA tga, ref JObject metadata) private void LoadTgaFromZipFileStream(Stream fileStream, String name, ref TGA tga, ref JObject metadata)

View File

@ -23,7 +23,7 @@ using System.Xml;
namespace MobiusEditor.Utility namespace MobiusEditor.Utility
{ {
public class Tile public class Tile: IDisposable
{ {
public Bitmap Image { get; private set; } public Bitmap Image { get; private set; }
@ -39,6 +39,24 @@ namespace MobiusEditor.Utility
: this(image, new Rectangle(0, 0, image.Width, image.Height)) : this(image, new Rectangle(0, 0, image.Width, image.Height))
{ {
} }
public void Dispose()
{
Bitmap image = this.Image;
this.Image = null;
try
{
if (image != null)
{
image.Dispose();
}
}
catch
{
// ignore.
}
}
} }
public class Tileset public class Tileset
@ -77,6 +95,17 @@ namespace MobiusEditor.Utility
{ {
foreach (var tileItem in item.Value) foreach (var tileItem in item.Value)
{ {
// no need to dispose the images; the Reset of TextureManager handles that.
foreach (KeyValuePair<string, Tile[]> tileinfo in tileItem.Value.TeamColorTiles)
{
if (tileinfo.Value != null) {
foreach (Tile tile in tileinfo.Value)
{
// clean up bitmap
tile.Dispose();
}
}
}
tileItem.Value.TeamColorTiles.Clear(); tileItem.Value.TeamColorTiles.Clear();
} }
} }