2023-06-09 11:44:39 +02:00

332 lines
14 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using MobiusEditor.Interface;
using MobiusEditor.Model;
using MobiusEditor.Tools;
using MobiusEditor.Utility;
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Globalization;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;
namespace MobiusEditor.Dialogs
{
public partial class ImageExportDialog : Form, IHasStatusLabel
{
private SimpleMultiThreading multiThreader;
private string boundsLabel;
IGamePlugin gamePlugin;
private MapLayerFlag renderLayers;
public MapLayerFlag RenderLayers
{
get { return renderLayers; }
private set { renderLayers = value; }
}
public string Filename
{
get { return txtPath.Text; }
set { txtPath.Text = value; }
}
public bool SmoothScale
{
get { return chkSmooth.Checked; }
set { chkSmooth.Checked = value; }
}
public Label StatusLabel { get; set; }
private string inputFilename;
public ImageExportDialog(IGamePlugin gamePlugin, MapLayerFlag layers, string filename)
{
InitializeComponent();
// Store to adapt later
this.boundsLabel = chkBoundsOnly.Text;
this.gamePlugin = gamePlugin;
inputFilename = filename;
txtScale.Text = Globals.ExportTileScale.ToString(CultureInfo.InvariantCulture);
chkSmooth.Checked = Globals.ExportSmoothScale;
// For multiplayer maps, default to only exporting the bounds.
chkBoundsOnly.Checked = !gamePlugin.Map.BasicSection.SoloMission;
SetSizeLabel();
SetLayers(layers);
txtScale.Select(0, 0);
// Could make this at the moment of the call, too, but it also has a
// system to ignore further calls if the running one isn't finished.
multiThreader = new SimpleMultiThreading(this);
multiThreader.ProcessingLabelBorder = BorderStyle.Fixed3D;
}
private void SetSizeLabel()
{
if (Double.TryParse(txtScale.Text, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out double scale))
{
int scaleWidth = Math.Max(1, (int)(Globals.OriginalTileWidth * scale));
int scaleHeight = Math.Max(1, (int)(Globals.OriginalTileHeight * scale));
int width = gamePlugin.Map.Metrics.Width * scaleWidth;
int height = gamePlugin.Map.Metrics.Height * scaleHeight;
lblSize.Text = String.Format("(Size: {0}×{1})", width, height);
int boundsWidth = gamePlugin.Map.Bounds.Width * scaleWidth;
int boundsHeight = gamePlugin.Map.Bounds.Height * scaleHeight;
chkBoundsOnly.Text = boundsLabel + String.Format(" ({0}×{1})", boundsWidth, boundsHeight);
}
}
private void SetLayers(MapLayerFlag layers)
{
layersListBox.Items.Clear();
indicatorsListBox.Items.Clear();
String[] names = Map.MapLayerNames;
int len = names.Length;
for (int i = 1; i < len; ++i)
{
// Special rules per game. These should be kept identical to those in MainForm.EnableDisableMenuItems
MapLayerFlag mlf = (MapLayerFlag)(1 << i);
if (gamePlugin.GameType != GameType.RedAlert && mlf == MapLayerFlag.BuildingFakes
|| gamePlugin.GameType != GameType.RedAlert && mlf == MapLayerFlag.EffectRadius
|| gamePlugin.GameType == GameType.SoleSurvivor && mlf == MapLayerFlag.Buildings && Globals.NoOwnedObjectsInSole
|| gamePlugin.GameType == GameType.SoleSurvivor && mlf == MapLayerFlag.Units && Globals.NoOwnedObjectsInSole
|| gamePlugin.GameType == GameType.SoleSurvivor && mlf == MapLayerFlag.Infantry && Globals.NoOwnedObjectsInSole
|| gamePlugin.GameType == GameType.SoleSurvivor && mlf == MapLayerFlag.BuildingRebuild
|| gamePlugin.GameType != GameType.SoleSurvivor && mlf == MapLayerFlag.FootballArea
|| gamePlugin.GameType == GameType.SoleSurvivor && mlf == MapLayerFlag.CrateOutlines)
{
continue;
}
ListItem<MapLayerFlag> mli = new ListItem<MapLayerFlag>(mlf, names[i]);
int index;
if ((MapLayerFlag.MapLayers & mlf) != MapLayerFlag.None)
{
index = layersListBox.Items.Add(mli);
if ((layers & mlf) != MapLayerFlag.None)
{
layersListBox.SetSelected(index, true);
}
}
if ((MapLayerFlag.Indicators & mlf) != MapLayerFlag.None)
{
index = indicatorsListBox.Items.Add(new ListItem<MapLayerFlag>(mlf, names[i]));
if ((layers & mlf) != MapLayerFlag.None)
{
indicatorsListBox.SetSelected(index, true);
}
}
}
}
public MapLayerFlag GetLayers()
{
MapLayerFlag value = MapLayerFlag.None;
foreach (ListItem<MapLayerFlag> mli in layersListBox.SelectedItems)
{
value |= mli.Value;
}
foreach (ListItem<MapLayerFlag> mli in indicatorsListBox.SelectedItems)
{
value |= mli.Value;
}
return value;
}
private void txtScale_TextChanged(Object sender, EventArgs e)
{
TextBox textBox = sender as TextBox;
if (textBox == null)
{
return;
}
String pattern = "^\\d*(\\.\\d*)?$";
if (Regex.IsMatch(textBox.Text, pattern))
{
SetSizeLabel();
return;
}
// something snuck in, probably with ctrl+v. Remove it.
System.Media.SystemSounds.Beep.Play();
StringBuilder text = new StringBuilder();
String txt = textBox.Text.ToUpperInvariant();
Int32 txtLen = txt.Length;
Int32 firstIllegalChar = -1;
Int32 firstDot = txt.IndexOf(".");
for (Int32 i = 0; i < txtLen; ++i)
{
Char c = txt[i];
Boolean isNumRange = c >= '0' && c <= '9';
Boolean isLegalDot = c == '.' && i == firstDot;
if (!isNumRange && !isLegalDot)
{
if (firstIllegalChar == -1)
firstIllegalChar = i;
continue;
}
text.Append(c);
}
String filteredText = text.ToString();
Decimal value;
NumberStyles ns = NumberStyles.Number | NumberStyles.AllowDecimalPoint;
// Setting "this.Text" will trigger this function again, but that's okay, it'll immediately succeed in the regex and abort.
if (Decimal.TryParse(filteredText, ns, NumberFormatInfo.CurrentInfo, out value))
{
textBox.Text = value.ToString(CultureInfo.InvariantCulture);
}
else
{
textBox.Text = filteredText;
}
if (firstIllegalChar == -1)
firstIllegalChar = 0;
textBox.Select(firstIllegalChar, 0);
SetSizeLabel();
}
private void BtnSetDimensions_Click(Object sender, EventArgs e)
{
double scale;
if (!Double.TryParse(txtScale.Text, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out scale))
{
return;
}
int curSize = gamePlugin.Map.Metrics.Width * Math.Max(1, (int)(Globals.OriginalTileWidth * scale));
using (ImagetExportSetSizeDialog dimdialog = new ImagetExportSetSizeDialog(curSize))
{
dimdialog.StartPosition = FormStartPosition.CenterParent;
if (DialogResult.OK == dimdialog.ShowDialog(this))
{
// Can never be less than 1 pixel per cell.
curSize = Math.Max(dimdialog.Dimension, gamePlugin.Map.Metrics.Width);
scale = curSize / (double)(Globals.OriginalTileWidth * gamePlugin.Map.Metrics.Width);
txtScale.Text = scale.ToString(CultureInfo.InvariantCulture);
}
}
}
private void btnPickFile_Click(Object sender, EventArgs e)
{
SelectPath();
}
private void btnExport_Click(Object sender, EventArgs e)
{
if (String.IsNullOrEmpty(txtPath.Text))
{
//MessageBox.Show("Please select a filename to export to.", "Error");
if (!SelectPath())
{
return;
}
}
if (!Double.TryParse(txtScale.Text, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out double scale))
{
MessageBox.Show("Could not parse scale factor!", "Error");
return;
}
MapLayerFlag layers = GetLayers() | MapLayerFlag.Template;
bool smooth = chkSmooth.Checked;
bool inBounds = chkBoundsOnly.Checked;
string path = txtPath.Text;
Func<String> saveOperation = () => SaveImage(gamePlugin, layers, scale, smooth, inBounds, path);
multiThreader.ExecuteThreaded(saveOperation, ShowResult, true, EnableControls, "Exporting image");
}
private bool SelectPath()
{
using (SaveFileDialog sfd = new SaveFileDialog())
{
sfd.AutoUpgradeEnabled = false;
sfd.RestoreDirectory = true;
sfd.AddExtension = true;
sfd.Filter = "PNG files (*.png)|*.png|JPEG files (*.jpg)|*.jpg";
string current = string.IsNullOrEmpty(txtPath.Text) ? inputFilename : txtPath.Text;
if (!String.IsNullOrEmpty(current))
{
sfd.InitialDirectory = Path.GetDirectoryName(current);
bool isJpeg = "jpg".Equals(Path.GetExtension(current), StringComparison.OrdinalIgnoreCase);
sfd.FilterIndex = isJpeg ? 2 : 1;
sfd.FileName = Path.ChangeExtension(current, isJpeg ? "jpg" : "png");
}
if (sfd.ShowDialog(this) == DialogResult.OK)
{
txtPath.Text = sfd.FileName;
return true;
}
return false;
}
}
private static String SaveImage(IGamePlugin gamePlugin, MapLayerFlag layers, double scale, bool smooth, bool inBounds, string outputPath)
{
int tileWidth = Math.Max(1, (int)(Globals.OriginalTileWidth * scale));
int tileHeight = Math.Max(1, (int)(Globals.OriginalTileHeight * scale));
int fullWidth = gamePlugin.Map.Metrics.Width;
int fullHeight = gamePlugin.Map.Metrics.Height;
int width = inBounds ? gamePlugin.Map.Bounds.Width : fullWidth;
int height = inBounds ? gamePlugin.Map.Bounds.Height : fullHeight;
Size fullSize = new Size(fullWidth * tileWidth, fullHeight * tileHeight);
Size size = new Size(width * tileWidth, height * tileHeight);
using (Bitmap exportImage = gamePlugin.Map.GeneratePreview(size, gamePlugin.GameType, layers, smooth, inBounds, false).ToBitmap())
{
if ((layers & MapLayerFlag.Indicators) != MapLayerFlag.None)
{
// Draw on new transparent layer, then paint over image.
using (Graphics gExportImage = Graphics.FromImage(exportImage))
using (Bitmap overlaysImage = new Bitmap(fullSize.Width, fullSize.Height))
{
overlaysImage.SetResolution(96, 96);
using (Graphics gOverlaysImage = Graphics.FromImage(overlaysImage))
{
ViewTool.PostRenderMap(gOverlaysImage, gamePlugin, gamePlugin.Map, scale, layers, MapLayerFlag.None, false);
}
Rectangle fullRect = new Rectangle(new Point(0, 0), size);
Rectangle boundsRect = inBounds ? new Rectangle(new Point(gamePlugin.Map.Bounds.X * tileWidth, gamePlugin.Map.Bounds.Y * tileHeight), size) : fullRect;
gExportImage.DrawImage(overlaysImage, fullRect, boundsRect, GraphicsUnit.Pixel);
}
}
exportImage.Save(outputPath, ImageFormat.Png);
}
return outputPath;
}
private void ShowResult(String path)
{
if (path == null)
{
MessageBox.Show("Image saving failed!", "Image Export", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
using (ImageExportedDialog imexd = new ImageExportedDialog(path))
{
if (imexd.ShowDialog(this) == DialogResult.OK)
{
this.DialogResult = DialogResult.OK;
}
}
}
private void EnableControls(Boolean enabled, String processingLabel)
{
txtScale.Enabled = enabled;
btnSetDimensions.Enabled = enabled;
chkSmooth.Enabled = enabled;
chkBoundsOnly.Enabled = enabled;
layersListBox.Enabled = enabled;
indicatorsListBox.Enabled = enabled;
btnPickFile.Enabled = enabled;
btnExport.Enabled = enabled;
btnCancel.Enabled = enabled;
if (enabled)
{
SimpleMultiThreading.RemoveBusyLabel(this);
}
else
{
this.multiThreader.CreateBusyLabel(this, processingLabel);
}
}
}
}