From e65cc329914482afd1b2bdba4c5eb7cc04ad0c6d Mon Sep 17 00:00:00 2001 From: biwa <6475593+biwa@users.noreply.github.com> Date: Thu, 24 Dec 2020 15:00:16 +0100 Subject: [PATCH] Export selection to image: added progress bar and button to cancel export --- .../BuilderModes/General/BuilderPlug.cs | 24 +- .../Plugins/BuilderModes/IO/ImageExporter.cs | 312 ++++++++++++------ .../ImageExportSettingsForm.Designer.cs | 52 ++- .../Interface/ImageExportSettingsForm.cs | 213 +++++++++++- 4 files changed, 451 insertions(+), 150 deletions(-) diff --git a/Source/Plugins/BuilderModes/General/BuilderPlug.cs b/Source/Plugins/BuilderModes/General/BuilderPlug.cs index 94131b1..0be47dd 100644 --- a/Source/Plugins/BuilderModes/General/BuilderPlug.cs +++ b/Source/Plugins/BuilderModes/General/BuilderPlug.cs @@ -890,29 +890,7 @@ namespace CodeImp.DoomBuilder.BuilderModes } ImageExportSettingsForm form = new ImageExportSettingsForm(); - if (form.ShowDialog() == DialogResult.OK) - { - ImageExportSettings settings = new ImageExportSettings(Path.GetDirectoryName(form.FilePath), Path.GetFileNameWithoutExtension(form.FilePath), Path.GetExtension(form.FilePath), form.Floor, form.Fullbright, form.Brightmap, form.Tiles, form.ImageScale, form.GetPixelFormat(), form.GetImageFormat()); - ImageExporter exporter = new ImageExporter(sectors, settings); - - string text = "The following images will be created:\n\n" + string.Join("\n", exporter.GetImageNames().ToArray()); - - DialogResult result = MessageBox.Show(text, "Export to image", MessageBoxButtons.OKCancel); - - if (result == DialogResult.OK) - { - try - { - exporter.Export(); - - MessageBox.Show("Export successful.", "Export to image", MessageBoxButtons.OK, MessageBoxIcon.Information); - } - catch (ArgumentException e) // Happens if there's not enough consecutive memory to create the file - { - MessageBox.Show("Exporting failed. There's likely not enough consecutive free memory to create the image. Try a lower color depth or file format", "Export failed", MessageBoxButtons.OK, MessageBoxIcon.Error); - } - } - } + form.ShowDialog(); } #endregion diff --git a/Source/Plugins/BuilderModes/IO/ImageExporter.cs b/Source/Plugins/BuilderModes/IO/ImageExporter.cs index 9906a1f..e4646fd 100644 --- a/Source/Plugins/BuilderModes/IO/ImageExporter.cs +++ b/Source/Plugins/BuilderModes/IO/ImageExporter.cs @@ -27,6 +27,7 @@ using System.Drawing; using System.Drawing.Imaging; using System.Drawing.Drawing2D; using System.IO; +using System.Runtime.InteropServices; using System.Linq; using System.Text; using CodeImp.DoomBuilder.Data; @@ -68,12 +69,29 @@ namespace CodeImp.DoomBuilder.BuilderModes.IO #endregion + #region ================== Exceptions + + [Serializable] + public class ImageExportCanceledException : Exception { } + + [Serializable] + public class ImageExportImageTooBigException : Exception { } + + #endregion + internal class ImageExporter { #region ================== Variables private ICollection sectors; private ImageExportSettings settings; + private int numitems; + private int doneitems; + private int donepercent; + private Action addprogress; + private Action showphase; + private Func checkcanelexport; + private bool cancelexport; #endregion @@ -85,10 +103,14 @@ namespace CodeImp.DoomBuilder.BuilderModes.IO #region ================== Constructors - public ImageExporter(ICollection sectors, ImageExportSettings settings) + public ImageExporter(ICollection sectors, ImageExportSettings settings, Action addprogress, Action showphase, Func checkcanelexport) { this.sectors = sectors; this.settings = settings; + this.addprogress = addprogress; + this.showphase = showphase; + this.checkcanelexport = checkcanelexport; + cancelexport = false; } #endregion @@ -105,26 +127,63 @@ namespace CodeImp.DoomBuilder.BuilderModes.IO GetSizeAndOffset(out size, out offset); + // Count the number of triangles for reporting progress + numitems = 0; + doneitems = 0; + donepercent = 0; + foreach (Sector s in sectors) + numitems += s.Triangles.Vertices.Count / 3; + + if (settings.Tiles) + numitems += GetNumTiles(); + + // If exporting a brightmap everything has to be done twice + if (settings.Brightmap) + numitems *= 2; + // Use the same image for the normal texture and the brightmap because of memory concerns + showphase("Preparing"); using (Bitmap image = new Bitmap((int)(size.x * settings.Scale), (int)(size.y * settings.Scale), settings.PixelFormat)) { + showphase("Creating normal image"); // Normal texture image CreateImage(image, offset, settings.Scale, false); if (settings.Tiles) + { + showphase("Saving 64x64 tile images (" + GetNumTiles() + ")"); SaveImageAsTiles(image); + } else - image.Save(Path.Combine(settings.Path, settings.Name) + settings.Extension, settings.ImageFormat); + { + showphase("Saving normal image"); + try + { + image.Save(Path.Combine(settings.Path, settings.Name) + settings.Extension, settings.ImageFormat); + } + catch(ExternalException) + { + throw new ImageExportImageTooBigException(); + } + } // The brightmap if (settings.Brightmap) { + showphase("Creating brightmap image"); CreateImage(image, offset, settings.Scale, true); + showphase("Saving brightmap image"); if (settings.Tiles) + { + showphase("Saving 64x64 tile images (" + GetNumTiles() + ")"); SaveImageAsTiles(image, "_brightmap"); + } else + { image.Save(Path.Combine(settings.Path, settings.Name) + "_brightmap" + settings.Extension, settings.ImageFormat); + showphase("Saving normal image"); + } } } } @@ -138,118 +197,131 @@ namespace CodeImp.DoomBuilder.BuilderModes.IO /// The image to be exported private void CreateImage(Bitmap texturebitmap, Vector2D offset, float scale, bool asbrightmap) { - Graphics gtexture = null; - // The texture - gtexture = Graphics.FromImage(texturebitmap); - gtexture.Clear(Color.Black); // If we don't clear to black we'll see seams where the sectors touch, due to the AA - gtexture.InterpolationMode = InterpolationMode.HighQualityBilinear; - gtexture.CompositingQuality = CompositingQuality.HighQuality; - gtexture.PixelOffsetMode = PixelOffsetMode.HighQuality; - gtexture.SmoothingMode = SmoothingMode.AntiAlias; // Without AA the sector edges will be quite rough - - GraphicsPath gpath = new GraphicsPath(); - - foreach (Sector s in sectors) + using (Graphics gtexture = Graphics.FromImage(texturebitmap)) { - float rotation = (float)s.Fields.GetValue("rotationfloor", 0.0); + gtexture.Clear(Color.Black); // If we don't clear to black we'll see seams where the sectors touch, due to the AA + gtexture.InterpolationMode = InterpolationMode.HighQualityBilinear; + gtexture.CompositingQuality = CompositingQuality.HighQuality; + gtexture.PixelOffsetMode = PixelOffsetMode.HighQuality; + gtexture.SmoothingMode = SmoothingMode.AntiAlias; // Without AA the sector edges will be quite rough - // If a sector is rotated any offset is on the rotated axes. But we need to offset by - // map coordinates. We'll use this vector to compute that offset - Vector2D rotationvector = Vector2D.FromAngle(Angle2D.DegToRad(rotation) + Angle2D.PIHALF); - - // Sectors are triangulated, so draw every triangle - for (int i = 0; i < s.Triangles.Vertices.Count / 3; i++) + using (GraphicsPath gpath = new GraphicsPath()) { - // The GDI image has the 0/0 coordinate in the top left, so invert the y component - Vector2D v1 = (s.Triangles.Vertices[i * 3] - offset) * scale; v1.y *= -1.0f; - Vector2D v2 = (s.Triangles.Vertices[i * 3 + 1] - offset) * scale; v2.y *= -1.0f; - Vector2D v3 = (s.Triangles.Vertices[i * 3 + 2] - offset) * scale; v3.y *= -1.0f; - - gpath.AddLine((float)v1.x, (float)v1.y, (float)v2.x, (float)v2.y); - gpath.AddLine((float)v2.x, (float)v2.y, (float)v3.x, (float)v3.y); - gpath.CloseFigure(); - } - - if (asbrightmap) - { - // Create the brightmap based on the sector brightness - using (SolidBrush sbrush = new SolidBrush(Color.FromArgb(255, s.Brightness, s.Brightness, s.Brightness))) - gtexture.FillPath(sbrush, gpath); - } - else - { - Bitmap brushtexture; - Vector2D textureoffset = new Vector2D(); - Vector2D texturescale = new Vector2D(); - - if (settings.Floor) + foreach (Sector s in sectors) { - // The image might have a color correction applied, but we need it without. So we use LocalGetBitmap, because it reloads the image, - // but doesn't applie the color correction if we set UseColorCorrection to false first - ImageData imagedata = General.Map.Data.GetFlatImage(s.FloorTexture); - imagedata.UseColorCorrection = false; - brushtexture = new Bitmap(imagedata.GetBitmap()); - imagedata.UseColorCorrection = true; + float rotation = (float)s.Fields.GetValue("rotationfloor", 0.0); - textureoffset.x = s.Fields.GetValue("xpanningfloor", 0.0f) * scale; - textureoffset.y = s.Fields.GetValue("ypanningfloor", 0.0f) * scale; + // If a sector is rotated any offset is on the rotated axes. But we need to offset by + // map coordinates. We'll use this vector to compute that offset + Vector2D rotationvector = Vector2D.FromAngle(Angle2D.DegToRad(rotation) + Angle2D.PIHALF); - // GZDoom uses bigger numbers for smaller scales (i.e. a scale of 2 will halve the size), so we need to change the scale - texturescale.x = 1.0f / s.Fields.GetValue("xscalefloor", 1.0f); - texturescale.y = 1.0f / s.Fields.GetValue("yscalefloor", 1.0f); + // Sectors are triangulated, so draw every triangle + for (int i = 0; i < s.Triangles.Vertices.Count / 3; i++) + { + // The GDI image has the 0/0 coordinate in the top left, so invert the y component + Vector2D v1 = (s.Triangles.Vertices[i * 3] - offset) * scale; v1.y *= -1.0f; + Vector2D v2 = (s.Triangles.Vertices[i * 3 + 1] - offset) * scale; v2.y *= -1.0f; + Vector2D v3 = (s.Triangles.Vertices[i * 3 + 2] - offset) * scale; v3.y *= -1.0f; + + gpath.AddLine((float)v1.x, (float)v1.y, (float)v2.x, (float)v2.y); + gpath.AddLine((float)v2.x, (float)v2.y, (float)v3.x, (float)v3.y); + gpath.CloseFigure(); + + doneitems++; + + int newpercent = (int)(((double)doneitems / numitems) * 100); + if (newpercent > donepercent) + { + donepercent = newpercent; + addprogress(); + + if (checkcanelexport()) + throw new ImageExportCanceledException(); + } + } + + if (asbrightmap) + { + // Create the brightmap based on the sector brightness + int brightness = General.Clamp(s.Brightness, 0, 255); + using (SolidBrush sbrush = new SolidBrush(Color.FromArgb(255, brightness, brightness, brightness))) + gtexture.FillPath(sbrush, gpath); + } + else + { + Bitmap brushtexture; + Vector2D textureoffset = new Vector2D(); + Vector2D texturescale = new Vector2D(); + + if (settings.Floor) + { + // The image might have a color correction applied, but we need it without. So we use LocalGetBitmap, because it reloads the image, + // but doesn't applie the color correction if we set UseColorCorrection to false first + ImageData imagedata = General.Map.Data.GetFlatImage(s.FloorTexture); + imagedata.UseColorCorrection = false; + brushtexture = new Bitmap(imagedata.GetBitmap()); + imagedata.UseColorCorrection = true; + + textureoffset.x = s.Fields.GetValue("xpanningfloor", 0.0f) * scale; + textureoffset.y = s.Fields.GetValue("ypanningfloor", 0.0f) * scale; + + // GZDoom uses bigger numbers for smaller scales (i.e. a scale of 2 will halve the size), so we need to change the scale + texturescale.x = 1.0f / s.Fields.GetValue("xscalefloor", 1.0f); + texturescale.y = 1.0f / s.Fields.GetValue("yscalefloor", 1.0f); + } + else + { + // The image might have a color correction applied, but we need it without. So we use LocalGetBitmap, because it reloads the image, + // but doesn't applie the color correction if we set UseColorCorrection to false first + ImageData imagedata = General.Map.Data.GetFlatImage(s.CeilTexture); + imagedata.UseColorCorrection = false; + brushtexture = new Bitmap(imagedata.GetBitmap()); + imagedata.UseColorCorrection = true; + + textureoffset.x = s.Fields.GetValue("xpanningceiling", 0.0f) * scale; + textureoffset.y = s.Fields.GetValue("ypanningceiling", 0.0f) * scale; + + // GZDoom uses bigger numbers for smaller scales (i.e. a scale of 2 will halve the size), so we need to change the scale + texturescale.x = 1.0f / s.Fields.GetValue("xscaleceiling", 1.0f); + texturescale.y = 1.0f / s.Fields.GetValue("yscaleceiling", 1.0f); + } + + // Create the transformation matrix + Matrix matrix = new Matrix(); + matrix.Rotate(rotation); + matrix.Translate((float)(-offset.x * scale * rotationvector.x), (float)(offset.x * scale * rotationvector.y)); // Left/right offset from the map origin + matrix.Translate((float)(offset.y * scale * rotationvector.y), (float)(offset.y * scale * rotationvector.x)); // Up/down offset from the map origin + matrix.Translate(-(float)textureoffset.x, -(float)textureoffset.y); // Texture offset + matrix.Scale((float)texturescale.x, (float)texturescale.y); + + if (!settings.Fullbright) + { + int brightness = General.Clamp(s.Brightness, 0, 255); + AdjustBrightness(ref brushtexture, brightness > 0 ? brightness / 255.0f : 0.0f); + } + + if (scale > 1.0f) + ResizeImage(ref brushtexture, brushtexture.Width * (int)scale, brushtexture.Height * (int)scale); + + // Create the texture brush and apply the matrix + TextureBrush tbrush = new TextureBrush(brushtexture); + tbrush.Transform = matrix; + + // Draw the islands of the sector + gtexture.FillPath(tbrush, gpath); + + // Dispose unneeded objects + brushtexture.Dispose(); + tbrush.Dispose(); + matrix.Dispose(); + } + + // Reset the graphics path + gpath.Reset(); } - else - { - // The image might have a color correction applied, but we need it without. So we use LocalGetBitmap, because it reloads the image, - // but doesn't applie the color correction if we set UseColorCorrection to false first - ImageData imagedata = General.Map.Data.GetFlatImage(s.CeilTexture); - imagedata.UseColorCorrection = false; - brushtexture = new Bitmap(imagedata.GetBitmap()); - imagedata.UseColorCorrection = true; - - textureoffset.x = s.Fields.GetValue("xpanningceiling", 0.0f) * scale; - textureoffset.y = s.Fields.GetValue("ypanningceiling", 0.0f) * scale; - - // GZDoom uses bigger numbers for smaller scales (i.e. a scale of 2 will halve the size), so we need to change the scale - texturescale.x = 1.0f / s.Fields.GetValue("xscaleceiling", 1.0f); - texturescale.y = 1.0f / s.Fields.GetValue("yscaleceiling", 1.0f); - } - - // Create the transformation matrix - Matrix matrix = new Matrix(); - matrix.Rotate(rotation); - matrix.Translate((float)(-offset.x*scale * rotationvector.x), (float)(offset.x*scale * rotationvector.y)); // Left/right offset from the map origin - matrix.Translate((float)(offset.y*scale * rotationvector.y), (float)(offset.y*scale * rotationvector.x)); // Up/down offset from the map origin - matrix.Translate(-(float)textureoffset.x, -(float)textureoffset.y); // Texture offset - matrix.Scale((float)texturescale.x, (float)texturescale.y); - - if (!settings.Fullbright) - AdjustBrightness(ref brushtexture, s.Brightness > 0 ? s.Brightness / 255.0f : 0.0f); - - if (scale > 1.0f) - ResizeImage(ref brushtexture, brushtexture.Width * (int)scale, brushtexture.Height * (int)scale); - - // Create the texture brush and apply the matrix - TextureBrush tbrush = new TextureBrush(brushtexture); - tbrush.Transform = matrix; - - // Draw the islands of the sector - gtexture.FillPath(tbrush, gpath); - - // Dispose unneeded objects - brushtexture.Dispose(); - tbrush.Dispose(); - matrix.Dispose(); } - - // Reset the graphics path - gpath.Reset(); } - - // Dispose unneeded objects - gpath.Dispose(); - gtexture.Dispose(); } /// @@ -277,7 +349,6 @@ namespace CodeImp.DoomBuilder.BuilderModes.IO if(y * TILE_SIZE + TILE_SIZE > image.Size.Height) height = image.Size.Height - (y * TILE_SIZE); - using (Bitmap bitmap = new Bitmap(TILE_SIZE, TILE_SIZE)) using (Graphics g = Graphics.FromImage(bitmap)) { @@ -288,6 +359,21 @@ namespace CodeImp.DoomBuilder.BuilderModes.IO } imagenum++; + + doneitems++; + + int newpercent = (int)(((double)doneitems / numitems) * 100); + if (newpercent > donepercent) + { + donepercent = newpercent; + addprogress(); + + if (checkcanelexport()) + throw new ImageExportCanceledException(); + } + + if (checkcanelexport()) + throw new ImageExportCanceledException(); } } } @@ -325,7 +411,23 @@ namespace CodeImp.DoomBuilder.BuilderModes.IO } return imagenames; + } + /// + /// Gets the total number of tiles + /// + /// Number of tiles + public int GetNumTiles() + { + Vector2D size; + Vector2D offset; + + GetSizeAndOffset(out size, out offset); + + int xnum = (int)Math.Ceiling(size.x * settings.Scale / (double)TILE_SIZE); + int ynum = (int)Math.Ceiling(size.y * settings.Scale / (double)TILE_SIZE); + + return xnum * ynum; } /// diff --git a/Source/Plugins/BuilderModes/Interface/ImageExportSettingsForm.Designer.cs b/Source/Plugins/BuilderModes/Interface/ImageExportSettingsForm.Designer.cs index b23fde0..17fcb3f 100644 --- a/Source/Plugins/BuilderModes/Interface/ImageExportSettingsForm.Designer.cs +++ b/Source/Plugins/BuilderModes/Interface/ImageExportSettingsForm.Designer.cs @@ -31,7 +31,7 @@ this.tbExportPath = new System.Windows.Forms.TextBox(); this.browse = new System.Windows.Forms.Button(); this.label1 = new System.Windows.Forms.Label(); - this.cancel = new System.Windows.Forms.Button(); + this.close = new System.Windows.Forms.Button(); this.export = new System.Windows.Forms.Button(); this.saveFileDialog = new System.Windows.Forms.SaveFileDialog(); this.cbImageFormat = new System.Windows.Forms.ComboBox(); @@ -45,6 +45,8 @@ this.cbTiles = new System.Windows.Forms.CheckBox(); this.cbScale = new System.Windows.Forms.ComboBox(); this.label4 = new System.Windows.Forms.Label(); + this.progress = new System.Windows.Forms.ProgressBar(); + this.lbPhase = new System.Windows.Forms.Label(); this.SuspendLayout(); // // tbExportPath @@ -73,16 +75,16 @@ this.label1.TabIndex = 4; this.label1.Text = "Path:"; // - // cancel + // close // - this.cancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; - this.cancel.Location = new System.Drawing.Point(360, 153); - this.cancel.Name = "cancel"; - this.cancel.Size = new System.Drawing.Size(75, 23); - this.cancel.TabIndex = 7; - this.cancel.Text = "Cancel"; - this.cancel.UseVisualStyleBackColor = true; - this.cancel.Click += new System.EventHandler(this.cancel_Click); + this.close.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.close.Location = new System.Drawing.Point(360, 153); + this.close.Name = "close"; + this.close.Size = new System.Drawing.Size(75, 23); + this.close.TabIndex = 7; + this.close.Text = "Close"; + this.close.UseVisualStyleBackColor = true; + this.close.Click += new System.EventHandler(this.close_Click); // // export // @@ -219,13 +221,34 @@ this.label4.TabIndex = 18; this.label4.Text = "Scale:"; // + // progress + // + this.progress.Location = new System.Drawing.Point(12, 153); + this.progress.Name = "progress"; + this.progress.Size = new System.Drawing.Size(261, 23); + this.progress.Step = 1; + this.progress.TabIndex = 19; + this.progress.Visible = false; + // + // lbPhase + // + this.lbPhase.AutoSize = true; + this.lbPhase.Location = new System.Drawing.Point(14, 127); + this.lbPhase.Name = "lbPhase"; + this.lbPhase.Size = new System.Drawing.Size(45, 13); + this.lbPhase.TabIndex = 20; + this.lbPhase.Text = "lbPhase"; + this.lbPhase.Visible = false; + // // ImageExportSettingsForm // this.AcceptButton = this.export; this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.CancelButton = this.cancel; + this.CancelButton = this.close; this.ClientSize = new System.Drawing.Size(447, 188); + this.Controls.Add(this.lbPhase); + this.Controls.Add(this.progress); this.Controls.Add(this.label4); this.Controls.Add(this.cbScale); this.Controls.Add(this.cbTiles); @@ -237,7 +260,7 @@ this.Controls.Add(this.label2); this.Controls.Add(this.cbPixelFormat); this.Controls.Add(this.cbImageFormat); - this.Controls.Add(this.cancel); + this.Controls.Add(this.close); this.Controls.Add(this.export); this.Controls.Add(this.label1); this.Controls.Add(this.browse); @@ -247,6 +270,7 @@ this.MinimizeBox = false; this.Name = "ImageExportSettingsForm"; this.Text = "Image export settings"; + this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.ImageExportSettingsForm_FormClosing); this.ResumeLayout(false); this.PerformLayout(); @@ -257,7 +281,7 @@ private System.Windows.Forms.Button browse; private System.Windows.Forms.TextBox tbExportPath; private System.Windows.Forms.Label label1; - private System.Windows.Forms.Button cancel; + private System.Windows.Forms.Button close; private System.Windows.Forms.Button export; private System.Windows.Forms.SaveFileDialog saveFileDialog; private System.Windows.Forms.ComboBox cbImageFormat; @@ -271,5 +295,7 @@ private System.Windows.Forms.CheckBox cbTiles; private System.Windows.Forms.ComboBox cbScale; private System.Windows.Forms.Label label4; + private System.Windows.Forms.ProgressBar progress; + private System.Windows.Forms.Label lbPhase; } } \ No newline at end of file diff --git a/Source/Plugins/BuilderModes/Interface/ImageExportSettingsForm.cs b/Source/Plugins/BuilderModes/Interface/ImageExportSettingsForm.cs index 1a37d75..3f7c02f 100644 --- a/Source/Plugins/BuilderModes/Interface/ImageExportSettingsForm.cs +++ b/Source/Plugins/BuilderModes/Interface/ImageExportSettingsForm.cs @@ -31,9 +31,19 @@ using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; +using CodeImp.DoomBuilder.Map; +using CodeImp.DoomBuilder.BuilderModes.IO; namespace CodeImp.DoomBuilder.BuilderModes.Interface { + enum ImageExportResult + { + OK, + Canceled, + OutOfMemory, + ImageTooBig + } + public partial class ImageExportSettingsForm : Form { #region ================== Properties @@ -47,6 +57,20 @@ namespace CodeImp.DoomBuilder.BuilderModes.Interface #endregion + #region ================== Delegates + + private delegate void CallVoidMethodDeletage(); + private delegate void CallStringMethodDeletage(string s); + private delegate void CallImageExportResultMethodDeletage(ImageExportResult ier); + + #endregion + + #region ================== Variables + + bool exporting; + bool cancelexport; + + #endregion #region ================== Constructor @@ -56,6 +80,8 @@ namespace CodeImp.DoomBuilder.BuilderModes.Interface cbImageFormat.SelectedIndex = 0; cbPixelFormat.SelectedIndex = 0; + exporting = false; + cancelexport = false; string name = Path.GetFileNameWithoutExtension(General.Map.FileTitle) + "_" + General.Map.Options.LevelName + "_" + Path.GetFileNameWithoutExtension(Path.GetRandomFileName()); @@ -104,6 +130,153 @@ namespace CodeImp.DoomBuilder.BuilderModes.Interface } } + /// + /// Starts exporting the image(s). Disables all controls and starts the thread that does the actual exporting. + /// + private void StartExport() + { + ICollection sectors = General.Map.Map.SelectedSectorsCount == 0 ? General.Map.Map.Sectors : General.Map.Map.GetSelectedSectors(true); + + exporting = true; + cancelexport = false; + + progress.Maximum = 100; //sectors.Count * (Brightmap ? 2 : 1); + progress.Value = 0; + progress.Visible = true; + + lbPhase.Text = ""; + lbPhase.Visible = true; + + foreach (Control c in Controls) + { + if (!(c is ProgressBar || c is Label)) + c.Enabled = false; + } + + export.Enabled = true; + export.Text = "Cancel"; + + ImageExportSettings settings = new ImageExportSettings(Path.GetDirectoryName(FilePath), Path.GetFileNameWithoutExtension(FilePath), Path.GetExtension(FilePath), Floor, Fullbright, Brightmap, Tiles, ImageScale, GetPixelFormat(), GetImageFormat()); + + RunExport(settings); + } + + /// + /// Enables all controls. This has to be called when the export is finished (either successfully or unsuccessfully) + /// + /// Image export result + private void StopExport(ImageExportResult ier) + { + if (this.InvokeRequired) + { + CallImageExportResultMethodDeletage d = StopExport; + this.Invoke(d, ier); + } + else + { + progress.Visible = false; + lbPhase.Visible = false; + + foreach (Control c in Controls) + { + if (!(c is ProgressBar || c is Label)) + c.Enabled = true; + } + + export.Text = "Export"; + + if (ier == ImageExportResult.OK) + MessageBox.Show("Export successful.", "Export to image", MessageBoxButtons.OK, MessageBoxIcon.Information); + else if(ier == ImageExportResult.Canceled) + MessageBox.Show("Export canceled.", "Export to image", MessageBoxButtons.OK, MessageBoxIcon.Information); + else if (ier == ImageExportResult.OutOfMemory) + MessageBox.Show("Exporting failed. There's likely not enough consecutive free memory to create the image. Try a lower color depth or file format", "Export failed", MessageBoxButtons.OK, MessageBoxIcon.Error); + else if(ier == ImageExportResult.ImageTooBig) + MessageBox.Show("Exporting failed. The image is likely too big for the current settings. Try a lower color depth or file format", "Export failed", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + + exporting = false; + } + + private bool CheckCancelExport() + { + return cancelexport; + } + + /// + /// Shows the current phase in textual form. Is called by the exporter + /// + /// + private void ShowPhase(string text) + { + if (this.InvokeRequired) + { + CallStringMethodDeletage d = ShowPhase; + this.Invoke(d, text); + } + else + { + lbPhase.Text = text; + } + } + + /// + /// Adds progress to the progress bar. Is called by the exporter + /// + private void AddProgress() + { + if (progress.InvokeRequired) + { + CallVoidMethodDeletage d = AddProgress; + progress.Invoke(d); + } + else + { + // Just winforms things to make the progress bar animation not lag behind + int value = progress.Value + 1; + progress.Value = value; + progress.Value = value - 1; + progress.Value = value; + } + } + + /// + /// Runs the actual exporter + /// + /// Export settings + private void RunExport(ImageExportSettings settings) + { + ICollection sectors = General.Map.Map.SelectedSectorsCount == 0 ? General.Map.Map.Sectors : General.Map.Map.GetSelectedSectors(true); + + ImageExporter exporter = new ImageExporter(sectors, settings, AddProgress, ShowPhase, CheckCancelExport); + + try + { + exporter.Export(); + } + catch (ArgumentException) // Happens if there's not enough consecutive memory to create the file + { + StopExport(ImageExportResult.OutOfMemory); + return; + } + catch(ImageExportCanceledException) + { + StopExport(ImageExportResult.Canceled); + return; + } + catch(ImageExportImageTooBigException) + { + StopExport(ImageExportResult.ImageTooBig); + return; + } + + StopExport(ImageExportResult.OK); + } + + #endregion + + #region ================== Events + private void browse_Click(object sender, EventArgs e) { if (saveFileDialog.ShowDialog() == DialogResult.OK) @@ -124,9 +297,7 @@ namespace CodeImp.DoomBuilder.BuilderModes.Interface } } - #endregion - - private void cancel_Click(object sender, EventArgs e) + private void close_Click(object sender, EventArgs e) { this.DialogResult = DialogResult.Cancel; this.Close(); @@ -134,13 +305,27 @@ namespace CodeImp.DoomBuilder.BuilderModes.Interface private void export_Click(object sender, EventArgs e) { - General.Settings.WritePluginSetting("imageexportfullbright", cbFullbright.Checked); - General.Settings.WritePluginSetting("imageexportbrightmap", cbBrightmap.Checked); - General.Settings.WritePluginSetting("imageexporttiles", cbTiles.Checked); - General.Settings.WritePluginSetting("imageexportscale", cbScale.SelectedIndex); + if (exporting) + { + cancelexport = true; + export.Enabled = false; + } + else + { + General.Settings.WritePluginSetting("imageexportfullbright", cbFullbright.Checked); + General.Settings.WritePluginSetting("imageexportbrightmap", cbBrightmap.Checked); + General.Settings.WritePluginSetting("imageexporttiles", cbTiles.Checked); + General.Settings.WritePluginSetting("imageexportscale", cbScale.SelectedIndex); - this.DialogResult = DialogResult.OK; - this.Close(); + // Exporting works like this: + // In here StartExport() is called + // StartExport() disables all controls and creates a thread that runs RunExport() in the background; then the StartExport method ends + // RunExport() creates an instance of ImageExporter and starts the actual export + // When ImageExporter finishes its job it runs StopExport() + // StopExport() enables all controls again + + StartExport(); + } } private void cbImageFormat_SelectedIndexChanged(object sender, EventArgs e) @@ -159,5 +344,15 @@ namespace CodeImp.DoomBuilder.BuilderModes.Interface tbExportPath.Text = Path.ChangeExtension(tbExportPath.Text, newextension); } + + + private void ImageExportSettingsForm_FormClosing(object sender, FormClosingEventArgs e) + { + // Do not allow closing the form while the export is running + if (exporting) + e.Cancel = true; + } + + #endregion } }