mirror of
https://git.do.srb2.org/STJr/UltimateZoneBuilder.git
synced 2024-11-26 13:51:40 +00:00
Export selection to image: added progress bar and button to cancel export
This commit is contained in:
parent
676a8c0a26
commit
7f1442ce86
4 changed files with 457 additions and 155 deletions
|
@ -672,29 +672,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());
|
||||
|
||||
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
|
||||
|
|
|
@ -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 System.Threading.Tasks;
|
||||
|
@ -69,12 +70,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<Sector> sectors;
|
||||
private ImageExportSettings settings;
|
||||
private int numitems;
|
||||
private int doneitems;
|
||||
private int donepercent;
|
||||
private Action addprogress;
|
||||
private Action<string> showphase;
|
||||
private Func<bool> checkcanelexport;
|
||||
private bool cancelexport;
|
||||
|
||||
#endregion
|
||||
|
||||
|
@ -86,10 +104,14 @@ namespace CodeImp.DoomBuilder.BuilderModes.IO
|
|||
|
||||
#region ================== Constructors
|
||||
|
||||
public ImageExporter(ICollection<Sector> sectors, ImageExportSettings settings)
|
||||
public ImageExporter(ICollection<Sector> sectors, ImageExportSettings settings, Action addprogress, Action<string> showphase, Func<bool> checkcanelexport)
|
||||
{
|
||||
this.sectors = sectors;
|
||||
this.settings = settings;
|
||||
this.addprogress = addprogress;
|
||||
this.showphase = showphase;
|
||||
this.checkcanelexport = checkcanelexport;
|
||||
cancelexport = false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
@ -106,26 +128,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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -139,122 +198,131 @@ namespace CodeImp.DoomBuilder.BuilderModes.IO
|
|||
/// <returns>The image to be exported</returns>
|
||||
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.0;
|
||||
Vector2D v2 = (s.Triangles.Vertices[i * 3 + 1] - offset) * scale; v2.y *= -1.0;
|
||||
Vector2D v3 = (s.Triangles.Vertices[i * 3 + 2] - offset) * scale; v3.y *= -1.0;
|
||||
|
||||
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
|
||||
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)
|
||||
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.LocalGetBitmap());
|
||||
imagedata.UseColorCorrection = true;
|
||||
float rotation = (float)s.Fields.GetValue("rotationfloor", 0.0);
|
||||
|
||||
textureoffset.x = s.Fields.GetValue("xpanningfloor", 0.0) * scale;
|
||||
textureoffset.y = s.Fields.GetValue("ypanningfloor", 0.0) * 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.0 / s.Fields.GetValue("xscalefloor", 1.0);
|
||||
texturescale.y = 1.0 / s.Fields.GetValue("yscalefloor", 1.0);
|
||||
// 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.0;
|
||||
Vector2D v2 = (s.Triangles.Vertices[i * 3 + 1] - offset) * scale; v2.y *= -1.0;
|
||||
Vector2D v3 = (s.Triangles.Vertices[i * 3 + 2] - offset) * scale; v3.y *= -1.0;
|
||||
|
||||
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.LocalGetBitmap());
|
||||
imagedata.UseColorCorrection = true;
|
||||
|
||||
textureoffset.x = s.Fields.GetValue("xpanningfloor", 0.0) * scale;
|
||||
textureoffset.y = s.Fields.GetValue("ypanningfloor", 0.0) * 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.0 / s.Fields.GetValue("xscalefloor", 1.0);
|
||||
texturescale.y = 1.0 / s.Fields.GetValue("yscalefloor", 1.0);
|
||||
}
|
||||
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.LocalGetBitmap());
|
||||
imagedata.UseColorCorrection = true;
|
||||
|
||||
textureoffset.x = s.Fields.GetValue("xpanningceiling", 0.0) * scale;
|
||||
textureoffset.y = s.Fields.GetValue("ypanningceiling", 0.0) * 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.0 / s.Fields.GetValue("xscaleceiling", 1.0);
|
||||
texturescale.y = 1.0 / s.Fields.GetValue("yscaleceiling", 1.0);
|
||||
}
|
||||
|
||||
// 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.LocalGetBitmap());
|
||||
imagedata.UseColorCorrection = true;
|
||||
|
||||
textureoffset.x = s.Fields.GetValue("xpanningceiling", 0.0) * scale;
|
||||
textureoffset.y = s.Fields.GetValue("ypanningceiling", 0.0) * 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.0 / s.Fields.GetValue("xscaleceiling", 1.0);
|
||||
texturescale.y = 1.0 / s.Fields.GetValue("yscaleceiling", 1.0);
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
// Dispose unneeded objects
|
||||
gpath.Dispose();
|
||||
gtexture.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -282,7 +350,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))
|
||||
{
|
||||
|
@ -293,6 +360,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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -330,7 +412,23 @@ namespace CodeImp.DoomBuilder.BuilderModes.IO
|
|||
}
|
||||
|
||||
return imagenames;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total number of tiles
|
||||
/// </summary>
|
||||
/// <returns>Number of tiles</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -30,11 +30,21 @@ using System.Data;
|
|||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
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
|
||||
|
@ -48,6 +58,21 @@ 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
|
||||
|
||||
Thread exportthread;
|
||||
bool exporting;
|
||||
bool cancelexport;
|
||||
|
||||
#endregion
|
||||
|
||||
#region ================== Constructor
|
||||
|
||||
|
@ -57,6 +82,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());
|
||||
|
||||
|
@ -105,6 +132,157 @@ namespace CodeImp.DoomBuilder.BuilderModes.Interface
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts exporting the image(s). Disables all controls and starts the thread that does the actual exporting.
|
||||
/// </summary>
|
||||
private void StartExport()
|
||||
{
|
||||
ICollection<Sector> 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());
|
||||
|
||||
exportthread = new Thread(() => RunExport(settings));
|
||||
exportthread.Name = "Image export";
|
||||
exportthread.Priority = ThreadPriority.Normal;
|
||||
exportthread.Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables all controls. This has to be called when the export is finished (either successfully or unsuccessfully)
|
||||
/// </summary>
|
||||
/// <param name="ier">Image export result</param>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows the current phase in textual form. Is called by the exporter
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
private void ShowPhase(string text)
|
||||
{
|
||||
if (this.InvokeRequired)
|
||||
{
|
||||
CallStringMethodDeletage d = ShowPhase;
|
||||
this.Invoke(d, text);
|
||||
}
|
||||
else
|
||||
{
|
||||
lbPhase.Text = text;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds progress to the progress bar. Is called by the exporter
|
||||
/// </summary>
|
||||
private void AddProgress()
|
||||
{
|
||||
if (progress.InvokeRequired)
|
||||
{
|
||||
CallVoidMethodDeletage d = AddProgress;
|
||||
try { progress.Invoke(d); }
|
||||
catch (ThreadInterruptedException) { }
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs the actual exporter
|
||||
/// </summary>
|
||||
/// <param name="settings">Export settings</param>
|
||||
private void RunExport(ImageExportSettings settings)
|
||||
{
|
||||
ICollection<Sector> 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)
|
||||
|
@ -125,9 +303,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();
|
||||
|
@ -135,13 +311,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)
|
||||
|
@ -160,5 +350,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
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue