Export selection to image: added option to scale the output image to a higher resolution

This commit is contained in:
biwa 2020-11-21 17:44:10 +01:00 committed by spherallic
parent fdbb6c35c5
commit a4bc90c754
4 changed files with 208 additions and 88 deletions

View file

@ -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());

View file

@ -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
/// </summary>
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);
}
}
}
/// <summary>
/// Create the image ready to be exported
/// </summary>
/// <param name="texturebitmap">The image the graphics will be drawn to</param>
/// <param name="offset">The offset of the selection in map space</param>
/// <param name="asbrightmap">True if the image should be a brightmap, false if normally textured</param>
/// <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;
@ -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();
}
/// <summary>
@ -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
/// <summary>
/// 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/
/// </summary>
/// <param name="image">Base image</param>
/// <param name="image">The image to adjust</param>
/// <param name="brightness">Brightness between 0.0f and 1.0f</param>
/// <returns>The new image with changed brightness</returns>
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;
}
/// <summary>
/// Resize the image to the specified width and height. Taken from https://stackoverflow.com/a/24199315 (with some modifications)
/// </summary>
/// <param name="image">The image to resize.</param>
/// <param name="width">The width to resize to.</param>
/// <param name="height">The height to resize to.</param>
/// <returns>The resized image.</returns>
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

View file

@ -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;
}
}

View file

@ -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();