diff --git a/Bonsai.Editor/EditorForm.cs b/Bonsai.Editor/EditorForm.cs index b3d5e4ad7..0a5b6b8d7 100644 --- a/Bonsai.Editor/EditorForm.cs +++ b/Bonsai.Editor/EditorForm.cs @@ -1019,49 +1019,27 @@ void ExportImage(WorkflowGraphView model) void ExportImage(WorkflowGraphView model, string fileName) { + var workflow = model.Workflow; if (model.GraphView.SelectedNodes.Count() > 0) { var selectedElements = model.GraphView.SelectedNodes.ToWorkflowBuilder(); - var selectedLayout = selectedElements.Workflow.ConnectedComponentLayering().ToList(); - using var graphView = new GraphViewControl - { - IconRenderer = model.GraphView.IconRenderer, - Font = model.GraphView.Font, - Nodes = selectedLayout - }; - ExportImage(graphView, fileName); + workflow = selectedElements.Workflow; } - else ExportImage(model.GraphView, fileName); - } - void ExportImage(GraphViewControl graphView, string fileName) - { - var bounds = graphView.GetLayoutSize(); var extension = Path.GetExtension(fileName); if (extension == ".svg") { - var graphics = new SvgNet.SvgGdi.SvgGraphics(); - graphView.DrawGraphics(graphics, true); - var svg = graphics.WriteSVGString(); - var attributes = string.Format( - " Nodes { get { return nodes; } @@ -310,6 +311,11 @@ public IEnumerable SelectedNodes } } + Graphics CreateVectorGraphics() + { + return GraphicsProvider != null ? Graphics.FromImage(GraphicsProvider) : CreateGraphics(); + } + void UpdateSelection(Action update) { InvalidateSelection(); @@ -342,12 +348,6 @@ void DisposeDrawResources() DashPen.Dispose(); SolidPen = DashPen = null; } - - if (ExportFont != null) - { - ExportFont.Dispose(); - ExportFont = null; - } } void UpdateCursorPen() @@ -372,9 +372,13 @@ void UpdateCursorPen() protected override void ScaleControl(SizeF factor, BoundsSpecified specified) { DisposeDrawResources(); - using (var graphics = CreateGraphics()) + using (var graphics = CreateVectorGraphics()) { - drawScale = graphics.DpiY / DefaultDpi * Font.SizeInPoints / Control.DefaultFont.SizeInPoints; + drawScale = graphics.DpiY / DefaultDpi; + if (GraphicsProvider == null) + { + drawScale *= Font.SizeInPoints / DefaultFont.SizeInPoints; + } } iconRendererState.Scale = drawScale; @@ -407,7 +411,7 @@ Rectangle GetBoundingRectangle(GraphNode node) var labelRectangle = nodeLayout.LabelRectangle; if (nodeText != nodeLayout.Text) { - using (var graphics = CreateGraphics()) + using (var graphics = CreateVectorGraphics()) { nodeLayout.SetNodeLabel(nodeText, Font, graphics); labelRectangle = RectangleF.Union(labelRectangle, nodeLayout.LabelRectangle); @@ -809,7 +813,7 @@ private void UpdateModelLayout() var size = SizeF.Empty; if (model != null) { - using (var graphics = CreateGraphics()) + using (var graphics = CreateVectorGraphics()) { var layerCount = model.Count(); foreach (var layer in model) @@ -1122,21 +1126,14 @@ private void DrawEdges(IGraphics graphics, Size offset) } } - public void DrawGraphics(IGraphics graphics, bool scaleFont) + public void DrawGraphics(IGraphics graphics) { graphics.SmoothingMode = SmoothingMode.AntiAlias; graphics.Clear(Color.White); - using (var measureGraphics = CreateGraphics()) + using (var measureGraphics = CreateVectorGraphics()) { var font = Font; - var fontScale = measureGraphics.DpiY / DefaultDpi; - if (scaleFont && fontScale != 1.0) - { - ExportFont = ExportFont ?? new Font(Font.FontFamily, Font.SizeInPoints * fontScale); - font = ExportFont; - } - using (var fill = new SolidBrush(Color.White)) using (var stroke = new Pen(NodeEdgeColor, PenWidth)) { @@ -1152,7 +1149,7 @@ public void DrawGraphics(IGraphics graphics, bool scaleFont) { int charactersFitted, linesFilled; var size = measureGraphics.MeasureString( - line, Font, + line, font, labelRect.Size, VectorTextFormat, out charactersFitted, out linesFilled); diff --git a/Bonsai.Editor/WorkflowExporter.cs b/Bonsai.Editor/WorkflowExporter.cs new file mode 100644 index 000000000..b495eaa4c --- /dev/null +++ b/Bonsai.Editor/WorkflowExporter.cs @@ -0,0 +1,51 @@ +using System; +using System.IO; +using System.Windows.Forms; +using System.Xml; +using System.Xml.Serialization; +using Bonsai.Editor.GraphView; + +namespace Bonsai.Editor +{ + public static class WorkflowExporter + { + public static void ExportImage(string fileName, string imageFileName) + { + if (string.IsNullOrEmpty(fileName)) + { + throw new ArgumentNullException(nameof(fileName)); + } + + if (!File.Exists(fileName)) + { + throw new ArgumentException("Specified workflow file does not exist.", nameof(fileName)); + } + + if (string.IsNullOrEmpty(imageFileName)) + { + throw new ArgumentException("No output image file is specified.", nameof(imageFileName)); + } + + WorkflowBuilder workflowBuilder; + using (var reader = XmlReader.Create(fileName)) + { + reader.MoveToContent(); + var serializer = new XmlSerializer(typeof(WorkflowBuilder), reader.NamespaceURI); + workflowBuilder = (WorkflowBuilder)serializer.Deserialize(reader); + } + + var iconRenderer = new SvgRendererFactory(); + var extension = Path.GetExtension(imageFileName); + if (extension == ".svg") + { + var svg = ExportHelper.ExportSvg(workflowBuilder.Workflow, iconRenderer); + File.WriteAllText(imageFileName, svg); + } + else + { + using var bitmap = ExportHelper.ExportBitmap(workflowBuilder.Workflow, Control.DefaultFont, iconRenderer); + bitmap.Save(imageFileName); + } + } + } +} diff --git a/Bonsai/Launcher.cs b/Bonsai/Launcher.cs index 81526e26b..1408d2b37 100644 --- a/Bonsai/Launcher.cs +++ b/Bonsai/Launcher.cs @@ -141,6 +141,27 @@ internal static int LaunchWorkflowPlayer( return Program.NormalExitCode; } + internal static int LaunchExportImage( + string fileName, + string imageFileName, + PackageConfiguration packageConfiguration) + { + if (string.IsNullOrEmpty(fileName)) + { + Console.WriteLine("No workflow file was specified."); + return Program.NormalExitCode; + } + + if (string.IsNullOrEmpty(imageFileName)) + { + Console.WriteLine("No image file was specified."); + return Program.NormalExitCode; + } + + WorkflowExporter.ExportImage(fileName, imageFileName); + return Program.NormalExitCode; + } + static int ShowManifestReadError(string path, string message) { MessageBox.Show( diff --git a/Bonsai/Program.cs b/Bonsai/Program.cs index ca8a04879..1d6351765 100644 --- a/Bonsai/Program.cs +++ b/Bonsai/Program.cs @@ -24,6 +24,7 @@ static class Program const string PackageManagerCommand = "--package-manager"; const string PackageManagerUpdates = "updates"; const string ExportPackageCommand = "--export-package"; + const string ExportImageCommand = "--export-image"; const string ReloadEditorCommand = "--reload-editor"; const string GalleryCommand = "--gallery"; const string EditorDomainName = "EditorDomain"; @@ -45,9 +46,11 @@ internal static int Main(string[] args) var launchEditor = true; var debugScripts = false; var editorScale = 1.0f; + var exportImage = false; var updatePackages = false; var launchResult = default(EditorResult); var initialFileName = default(string); + var imageFileName = default(string); var layoutPath = default(string); var libFolders = new List(); var propertyAssignments = new Dictionary(); @@ -59,6 +62,7 @@ internal static int Main(string[] args) parser.RegisterCommand(DebugScriptCommand, () => debugScripts = true); parser.RegisterCommand(SuppressBootstrapCommand, () => bootstrap = false); parser.RegisterCommand(SuppressEditorCommand, () => launchEditor = false); + parser.RegisterCommand(ExportImageCommand, fileName => { imageFileName = fileName; exportImage = true; }); parser.RegisterCommand(ExportPackageCommand, () => { launchResult = EditorResult.ExportPackage; bootstrap = false; }); parser.RegisterCommand(ReloadEditorCommand, () => { launchResult = EditorResult.ReloadEditor; bootstrap = false; }); parser.RegisterCommand(GalleryCommand, () => { launchResult = EditorResult.OpenGallery; bootstrap = false; }); @@ -143,6 +147,7 @@ internal static int Main(string[] args) libFolders.ForEach(path => ConfigurationHelper.RegisterPath(packageConfiguration, path)); using var scriptExtensions = ScriptExtensionsProvider.CompileAssembly(Launcher.ProjectFramework, packageConfiguration, editorRepositoryPath, debugScripts); ConfigurationHelper.SetAssemblyResolve(packageConfiguration); + if (exportImage) return Launcher.LaunchExportImage(initialFileName, imageFileName, packageConfiguration); if (!launchEditor) return Launcher.LaunchWorkflowPlayer(initialFileName, layoutPath, packageConfiguration, propertyAssignments); else return Launcher.LaunchWorkflowEditor( packageConfiguration,