diff --git a/Source/Plugins/BuilderModes/General/BuilderPlug.cs b/Source/Plugins/BuilderModes/General/BuilderPlug.cs index f8e81a7..94131b1 100644 --- a/Source/Plugins/BuilderModes/General/BuilderPlug.cs +++ b/Source/Plugins/BuilderModes/General/BuilderPlug.cs @@ -892,7 +892,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.GetPixelFormat(), form.GetImageFormat()); + 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()); diff --git a/Source/Plugins/BuilderModes/IO/ImageExporter.cs b/Source/Plugins/BuilderModes/IO/ImageExporter.cs index a2c1ecc..9906a1f 100644 --- a/Source/Plugins/BuilderModes/IO/ImageExporter.cs +++ b/Source/Plugins/BuilderModes/IO/ImageExporter.cs @@ -29,6 +29,7 @@ using System.Drawing.Drawing2D; using System.IO; using System.Linq; using System.Text; +using CodeImp.DoomBuilder.Data; using CodeImp.DoomBuilder.Geometry; using CodeImp.DoomBuilder.Map; using System.Diagnostics; @@ -48,8 +49,9 @@ namespace CodeImp.DoomBuilder.BuilderModes.IO public bool Tiles; public PixelFormat PixelFormat; public ImageFormat ImageFormat; + public float Scale; - public ImageExportSettings(string path, string name, string extension, bool floor, bool fullbright, bool brightmap, bool tiles, PixelFormat pformat, ImageFormat iformat) + public ImageExportSettings(string path, string name, string extension, bool floor, bool fullbright, bool brightmap, bool tiles, float scale, PixelFormat pformat, ImageFormat iformat) { Path = path; Name = name; @@ -60,6 +62,7 @@ namespace CodeImp.DoomBuilder.BuilderModes.IO Fullbright = fullbright; PixelFormat = pformat; ImageFormat = iformat; + Scale = scale; } } @@ -97,17 +100,47 @@ namespace CodeImp.DoomBuilder.BuilderModes.IO /// public void Export() { - Bitmap texturebitmap = null; - Bitmap brightmapbitmap = null; - Graphics gbrightmap = null; - Graphics gtexture = null; - Vector2D offset; Vector2D size; + Vector2D offset; GetSizeAndOffset(out size, out offset); - // Normal texture - texturebitmap = new Bitmap((int)size.x, (int)size.y, settings.PixelFormat); + // Use the same image for the normal texture and the brightmap because of memory concerns + using (Bitmap image = new Bitmap((int)(size.x * settings.Scale), (int)(size.y * settings.Scale), settings.PixelFormat)) + { + // Normal texture image + CreateImage(image, offset, settings.Scale, false); + + if (settings.Tiles) + SaveImageAsTiles(image); + else + image.Save(Path.Combine(settings.Path, settings.Name) + settings.Extension, settings.ImageFormat); + + // The brightmap + if (settings.Brightmap) + { + CreateImage(image, offset, settings.Scale, true); + + if (settings.Tiles) + SaveImageAsTiles(image, "_brightmap"); + else + image.Save(Path.Combine(settings.Path, settings.Name) + "_brightmap" + settings.Extension, settings.ImageFormat); + } + } + } + + /// + /// Create the image ready to be exported + /// + /// The image the graphics will be drawn to + /// The offset of the selection in map space + /// True if the image should be a brightmap, false if normally textured + /// 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; @@ -115,21 +148,10 @@ namespace CodeImp.DoomBuilder.BuilderModes.IO gtexture.PixelOffsetMode = PixelOffsetMode.HighQuality; gtexture.SmoothingMode = SmoothingMode.AntiAlias; // Without AA the sector edges will be quite rough - // Brightmap - if (settings.Brightmap) - { - brightmapbitmap = new Bitmap((int)size.x, (int)size.y, settings.PixelFormat); - gbrightmap = Graphics.FromImage(brightmapbitmap); - gbrightmap.Clear(Color.Black); // If we don't clear to black we'll see seams where the sectors touch, due to the AA - gbrightmap.InterpolationMode = InterpolationMode.HighQualityBilinear; - gbrightmap.CompositingQuality = CompositingQuality.HighQuality; - gbrightmap.PixelOffsetMode = PixelOffsetMode.HighQuality; - gbrightmap.SmoothingMode = SmoothingMode.AntiAlias; // Without AA the sector edges will be quite rough - } + GraphicsPath gpath = new GraphicsPath(); foreach (Sector s in sectors) { - GraphicsPath p = new GraphicsPath(); float rotation = (float)s.Fields.GetValue("rotationfloor", 0.0); // If a sector is rotated any offset is on the rotated axes. But we need to offset by @@ -140,66 +162,94 @@ namespace CodeImp.DoomBuilder.BuilderModes.IO 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; v1.y *= -1.0f; - Vector2D v2 = s.Triangles.Vertices[i * 3 + 1] - offset; v2.y *= -1.0f; - Vector2D v3 = s.Triangles.Vertices[i * 3 + 2] - offset; v3.y *= -1.0f; - - p.AddLine((float)v1.x, (float)v1.y, (float)v2.x, (float)v2.y); - p.AddLine((float)v2.x, (float)v2.y, (float)v3.x, (float)v3.y); - p.CloseFigure(); + 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(); } - Bitmap brushtexture; - - if (settings.Floor) - brushtexture = General.Map.Data.GetFlatImage(s.FloorTexture).GetBitmap(); - else - brushtexture = General.Map.Data.GetFlatImage(s.CeilTexture).GetBitmap(); - - if (!settings.Fullbright) - brushtexture = AdjustBrightness(brushtexture, s.Brightness > 0 ? s.Brightness / 255.0f : 0.0f); - - Vector2D textureoffset = new Vector2D(); - textureoffset.x = s.Fields.GetValue("xpanningfloor", 0.0f); - textureoffset.y = s.Fields.GetValue("ypanningfloor", 0.0f); - - // Create the transformation matrix - Matrix matrix = new Matrix(); - matrix.Rotate(rotation); - matrix.Translate((float)(-offset.x * rotationvector.x), (float)(offset.x * rotationvector.y)); // Left/right offset from the map origin - matrix.Translate((float)(offset.y * rotationvector.y), (float)(offset.y * rotationvector.x)); // Up/down offset from the map origin - matrix.Translate(-(float)textureoffset.x, -(float)textureoffset.y); // Texture offset - - // 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, p); - - // Create the brightmap based on the sector brightness - if (settings.Brightmap) + if (asbrightmap) { - SolidBrush sbrush = new SolidBrush(Color.FromArgb(255, s.Brightness, s.Brightness, s.Brightness)); - gbrightmap.FillPath(sbrush, p); + // 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) + { + // 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) + 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(); } - // Finally save the image(s) - if (settings.Tiles) - { - SaveImageAsTiles(texturebitmap); - - if (settings.Brightmap) - SaveImageAsTiles(brightmapbitmap, "_brightmap"); - } - else - { - texturebitmap.Save(Path.Combine(settings.Path, settings.Name) + settings.Extension, settings.ImageFormat); - - if (settings.Brightmap) - brightmapbitmap.Save(Path.Combine(settings.Path, settings.Name) + "_brightmap" + settings.Extension, settings.ImageFormat); - } + // Dispose unneeded objects + gpath.Dispose(); + gtexture.Dispose(); } /// @@ -228,12 +278,14 @@ namespace CodeImp.DoomBuilder.BuilderModes.IO height = image.Size.Height - (y * TILE_SIZE); - Bitmap bitmap = new Bitmap(TILE_SIZE, TILE_SIZE); - Graphics g = Graphics.FromImage(bitmap); - g.Clear(Color.Black); - g.DrawImage(image, new Rectangle(0, 0, width, height), new Rectangle(x*TILE_SIZE, y*TILE_SIZE, width, height), GraphicsUnit.Pixel); + using (Bitmap bitmap = new Bitmap(TILE_SIZE, TILE_SIZE)) + using (Graphics g = Graphics.FromImage(bitmap)) + { + g.Clear(Color.Black); + g.DrawImage(image, new Rectangle(0, 0, width, height), new Rectangle(x * TILE_SIZE, y * TILE_SIZE, width, height), GraphicsUnit.Pixel); - bitmap.Save(string.Format("{0}{1}{2}{3}", Path.Combine(settings.Path, settings.Name), suffix, imagenum, settings.Extension)); + bitmap.Save(string.Format("{0}{1}{2}{3}", Path.Combine(settings.Path, settings.Name), suffix, imagenum, settings.Extension)); + } imagenum++; } @@ -317,10 +369,9 @@ namespace CodeImp.DoomBuilder.BuilderModes.IO /// /// Adjusts the brightness of an image. Code by Rod Stephens http://csharphelper.com/blog/2014/10/use-an-imageattributes-object-to-adjust-an-images-brightness-in-c/ /// - /// Base image + /// The image to adjust /// Brightness between 0.0f and 1.0f - /// The new image with changed brightness - private Bitmap AdjustBrightness(Image image, float brightness) + private void AdjustBrightness(ref Bitmap image, float brightness) { // Make the ColorMatrix. float b = brightness; @@ -349,8 +400,45 @@ namespace CodeImp.DoomBuilder.BuilderModes.IO gr.DrawImage(image, points, rect, GraphicsUnit.Pixel, attributes); } - // Return the result. - return bm; + // Dispose the original... + image.Dispose(); + + // ... and set it as the adjusted image + image = bm; + } + + /// + /// Resize the image to the specified width and height. Taken from https://stackoverflow.com/a/24199315 (with some modifications) + /// + /// The image to resize. + /// The width to resize to. + /// The height to resize to. + /// The resized image. + private void ResizeImage(ref Bitmap image, int width, int height) + { + var destRect = new Rectangle(0, 0, width, height); + var destImage = new Bitmap(width, height); + + destImage.SetResolution(image.HorizontalResolution, image.VerticalResolution); + + using (var graphics = Graphics.FromImage(destImage)) + { + graphics.CompositingMode = CompositingMode.SourceCopy; + graphics.CompositingQuality = CompositingQuality.HighQuality; + graphics.InterpolationMode = InterpolationMode.NearestNeighbor; + graphics.SmoothingMode = SmoothingMode.HighQuality; + graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; + + using (var wrapMode = new ImageAttributes()) + { + wrapMode.SetWrapMode(WrapMode.TileFlipXY); + graphics.DrawImage(image, destRect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, wrapMode); + } + } + + image.Dispose(); + + image = destImage; } #endregion diff --git a/Source/Plugins/BuilderModes/Interface/ImageExportSettingsForm.Designer.cs b/Source/Plugins/BuilderModes/Interface/ImageExportSettingsForm.Designer.cs index 88093ca..b23fde0 100644 --- a/Source/Plugins/BuilderModes/Interface/ImageExportSettingsForm.Designer.cs +++ b/Source/Plugins/BuilderModes/Interface/ImageExportSettingsForm.Designer.cs @@ -43,6 +43,8 @@ this.cbFullbright = new System.Windows.Forms.CheckBox(); this.cbBrightmap = new System.Windows.Forms.CheckBox(); this.cbTiles = new System.Windows.Forms.CheckBox(); + this.cbScale = new System.Windows.Forms.ComboBox(); + this.label4 = new System.Windows.Forms.Label(); this.SuspendLayout(); // // tbExportPath @@ -194,6 +196,29 @@ this.cbTiles.Text = "Create 64x64 tiles"; this.cbTiles.UseVisualStyleBackColor = true; // + // cbScale + // + this.cbScale.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cbScale.FormattingEnabled = true; + this.cbScale.Items.AddRange(new object[] { + "100%", + "200%", + "400%", + "800%"}); + this.cbScale.Location = new System.Drawing.Point(102, 89); + this.cbScale.Name = "cbScale"; + this.cbScale.Size = new System.Drawing.Size(71, 21); + this.cbScale.TabIndex = 17; + // + // label4 + // + this.label4.AutoSize = true; + this.label4.Location = new System.Drawing.Point(12, 92); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(37, 13); + this.label4.TabIndex = 18; + this.label4.Text = "Scale:"; + // // ImageExportSettingsForm // this.AcceptButton = this.export; @@ -201,6 +226,8 @@ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.CancelButton = this.cancel; this.ClientSize = new System.Drawing.Size(447, 188); + this.Controls.Add(this.label4); + this.Controls.Add(this.cbScale); this.Controls.Add(this.cbTiles); this.Controls.Add(this.cbBrightmap); this.Controls.Add(this.cbFullbright); @@ -242,5 +269,7 @@ private System.Windows.Forms.CheckBox cbFullbright; private System.Windows.Forms.CheckBox cbBrightmap; private System.Windows.Forms.CheckBox cbTiles; + private System.Windows.Forms.ComboBox cbScale; + private System.Windows.Forms.Label label4; } } \ No newline at end of file diff --git a/Source/Plugins/BuilderModes/Interface/ImageExportSettingsForm.cs b/Source/Plugins/BuilderModes/Interface/ImageExportSettingsForm.cs index eee7653..1a37d75 100644 --- a/Source/Plugins/BuilderModes/Interface/ImageExportSettingsForm.cs +++ b/Source/Plugins/BuilderModes/Interface/ImageExportSettingsForm.cs @@ -43,6 +43,7 @@ namespace CodeImp.DoomBuilder.BuilderModes.Interface public bool Fullbright { get { return cbFullbright.Checked; } } public bool Brightmap { get { return cbBrightmap.Checked; } } public bool Tiles { get { return cbTiles.Checked; } } + public float ImageScale { get { return (float)Math.Pow(2, cbScale.SelectedIndex); } } #endregion @@ -72,6 +73,7 @@ namespace CodeImp.DoomBuilder.BuilderModes.Interface cbFullbright.Checked = General.Settings.ReadPluginSetting("imageexportfullbright", true); cbBrightmap.Checked = General.Settings.ReadPluginSetting("imageexportbrightmap", false); cbTiles.Checked = General.Settings.ReadPluginSetting("imageexporttiles", false); + cbScale.SelectedIndex = General.Settings.ReadPluginSetting("imageexportscale", 0); } #endregion @@ -132,9 +134,10 @@ 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("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();