/// <summary> /// Gets the Potrace settings from the plug-in settings file. /// </summary> private void GetPotraceSettings() { Potrace.RestoreDefaults(); if (Settings.TryGetInteger("turnpolicy", out var turnpolicy)) { Potrace.turnpolicy = (TurnPolicy)turnpolicy; } if (Settings.TryGetInteger("turdsize", out var turdsize)) { Potrace.turdsize = turdsize; } if (Settings.TryGetDouble("alphamax", out var alphamax)) { Potrace.alphamax = alphamax; } if (Settings.TryGetBool("curveoptimizing", out var curveoptimizing)) { Potrace.curveoptimizing = curveoptimizing; } if (Settings.TryGetDouble("opttolerance", out var opttolerance)) { Potrace.opttolerance = opttolerance; } if (Settings.TryGetDouble("Treshold", out var Treshold)) { Potrace.Treshold = Treshold; } }
/// <summary> /// Creates the content of the dialog /// </summary> private RhinoDialogTableLayout CreateTableLayout() { // Create controls and define behaviors var ns_threshold = new NumericUpDownWithUnitParsing { ValueUpdateMode = NumericUpDownWithUnitParsingUpdateMode.WhenDoneChanging, MinValue = 0.0, MaxValue = 100.0, DecimalPlaces = 0, Increment = 1.0, ToolTip = "Weighted RGB color evaluation threshold.", Value = (int)(Potrace.Treshold * 100.0), Width = 45 }; var sld_threshold = new Slider { MinValue = 0, MaxValue = 100, TickFrequency = 25, Value = (int)(Potrace.Treshold * 100.0), Width = 220 }; ns_threshold.ValueChanged += (sender, args) => { if (m_allow_update_and_redraw) { m_allow_update_and_redraw = false; Potrace.Treshold = ns_threshold.Value / 100.0; sld_threshold.Value = (int)(Potrace.Treshold * 100.0); m_allow_update_and_redraw = true; UpdateAndRedraw(); } }; sld_threshold.ValueChanged += (sender, args) => { if (m_allow_update_and_redraw) { m_allow_update_and_redraw = false; Potrace.Treshold = sld_threshold.Value / 100.0; ns_threshold.Value = (int)(Potrace.Treshold * 100.0); m_allow_update_and_redraw = true; UpdateAndRedraw(); } }; var dd_turnpolicy = new DropDown { ToolTip = "Algorithm used to resolve ambiguities in path decomposition." }; foreach (var str in Enum.GetNames(typeof(TurnPolicy))) { dd_turnpolicy.Items.Add(str); } dd_turnpolicy.SelectedIndex = (int)Potrace.turnpolicy; dd_turnpolicy.SelectedIndexChanged += (sender, args) => { if (dd_turnpolicy.SelectedIndex != 0) { Potrace.turnpolicy = (TurnPolicy)dd_turnpolicy.SelectedIndex; UpdateAndRedraw(); } }; var ns_turdsize = new NumericUpDownWithUnitParsing { ValueUpdateMode = NumericUpDownWithUnitParsingUpdateMode.WhenDoneChanging, MinValue = 1.0, MaxValue = 100.0, DecimalPlaces = 0, Increment = 1.0, ToolTip = "Filter speckles of up to this size in pixels.", Value = Potrace.turdsize }; ns_turdsize.ValueChanged += (sender, args) => { Potrace.turdsize = (int)ns_turdsize.Value; UpdateAndRedraw(); }; var ns_alphamax = new NumericUpDownWithUnitParsing { ValueUpdateMode = NumericUpDownWithUnitParsingUpdateMode.WhenDoneChanging, MinValue = 0.0, MaxValue = 100.0, DecimalPlaces = 0, Increment = 1.0, ToolTip = "Corner rounding threshold.", Value = Potrace.alphamax }; ns_alphamax.ValueChanged += (sender, args) => { Potrace.alphamax = ns_alphamax.Value; UpdateAndRedraw(); }; var chk_curveoptimizing = new CheckBox { ThreeState = false, ToolTip = "Optimize of Bézier segments by a single segment when possible.", Checked = Potrace.curveoptimizing }; var ns_opttolerance = new NumericUpDownWithUnitParsing { ValueUpdateMode = NumericUpDownWithUnitParsingUpdateMode.WhenDoneChanging, MinValue = 0.1, MaxValue = 1.0, DecimalPlaces = 1, Increment = 0.1, Enabled = Potrace.curveoptimizing, ToolTip = "Tolerance used to optimize Bézier segments.", Value = Potrace.opttolerance }; chk_curveoptimizing.CheckedChanged += (sender, args) => { Potrace.curveoptimizing = chk_curveoptimizing.Checked.Value; ns_opttolerance.Enabled = Potrace.curveoptimizing; UpdateAndRedraw(); }; ns_opttolerance.ValueChanged += (sender, args) => { Potrace.opttolerance = ns_opttolerance.Value; UpdateAndRedraw(); }; var btn_reset = new Button { Text = "Restore Defaults" }; btn_reset.Click += (sender, args) => { m_allow_update_and_redraw = false; Potrace.RestoreDefaults(); sld_threshold.Value = (int)(Potrace.Treshold * 100.0); ns_threshold.Value = sld_threshold.Value; dd_turnpolicy.SelectedIndex = (int)Potrace.turnpolicy; ns_turdsize.Value = Potrace.turdsize; ns_alphamax.Value = Potrace.alphamax; chk_curveoptimizing.Checked = Potrace.curveoptimizing; ns_opttolerance.Value = Potrace.opttolerance; m_allow_update_and_redraw = true; UpdateAndRedraw(); }; // Layout the controls var minimum_size = new Eto.Drawing.Size(150, 0); var layout = new RhinoDialogTableLayout(false) { Spacing = new Eto.Drawing.Size(10, 8) }; layout.Rows.Add(new TableRow(new TableCell(new LabelSeparator { Text = "Vectorization options" }, true))); var panel0 = new Panel { MinimumSize = minimum_size, Content = new Label() { Text = "Threshold" } }; var table0 = new TableLayout { Padding = new Eto.Drawing.Padding(8, 0, 0, 0) }; table0.Rows.Add(new TableRow(new TableCell(panel0), new TableCell(sld_threshold, true), new TableCell(ns_threshold))); layout.Rows.Add(table0); var panel1 = new Panel { MinimumSize = minimum_size, Content = new Label() { Text = "Turn policy" } }; var table1 = new TableLayout { Padding = new Eto.Drawing.Padding(8, 0, 0, 0), Spacing = new Size(10, 8) }; table1.Rows.Add(new TableRow(new TableCell(panel1), new TableCell(dd_turnpolicy))); table1.Rows.Add(new TableRow(new TableCell(new Label() { Text = "Filter size" }), new TableCell(ns_turdsize))); table1.Rows.Add(new TableRow(new TableCell(new Label() { Text = "Corner rounding" }), new TableCell(ns_alphamax))); layout.Rows.Add(table1); layout.Rows.Add(new TableRow(new TableCell(new LabelSeparator { Text = "Curve optimization" }, true))); var panel2 = new Panel { MinimumSize = minimum_size, Content = new Label() { Text = "Optimizing" } }; var table2 = new TableLayout { Padding = new Eto.Drawing.Padding(8, 0, 0, 0), Spacing = new Size(10, 8) }; table2.Rows.Add(new TableRow(new TableCell(panel2), new TableCell(chk_curveoptimizing))); table2.Rows.Add(new TableRow(new TableCell(new Label() { Text = "Tolerance" }), new TableCell(ns_opttolerance))); table2.Rows.Add(null); table2.Rows.Add(new TableRow(new TableCell(new Label() { Text = "" }), new TableCell(btn_reset))); layout.Rows.Add(table2); return(layout); }
/// <summary> /// Command.RunCommand override /// </summary> protected override Result RunCommand(RhinoDoc doc, RunMode mode) { Potrace.Clear(); // Prompt the user for the name of the image file to vectorize. string path = GetImageFileName(mode); if (string.IsNullOrEmpty(path)) { return(Result.Cancel); } // Creates a bitmap from the specified file. var bitmap = Image.FromFile(path) as Bitmap; if (null == bitmap) { RhinoApp.WriteLine("The specified file cannot be identifed as a supported type."); return(Result.Failure); } // Verify bitmap size if (0 == bitmap.Width || 0 == bitmap.Height) { RhinoApp.WriteLine("Error reading the specified file."); return(Result.Failure); } // Calculate scale factor so curves of a reasonable size are added to Rhino var unit_scale = (doc.ModelUnitSystem != UnitSystem.Inches) ? RhinoMath.UnitScale(UnitSystem.Inches, doc.ModelUnitSystem) : 1.0; var scale = (double)(1.0 / bitmap.HorizontalResolution * unit_scale); // I'm not convinced this is useful... if (true) { var format = $"F{doc.DistanceDisplayPrecision}"; // Print image size in pixels RhinoApp.WriteLine("Image size in pixels: {0} x {1}", bitmap.Width, bitmap.Height ); // Print image size in inches var width = (double)(bitmap.Width / bitmap.HorizontalResolution); var height = (double)(bitmap.Height / bitmap.VerticalResolution); RhinoApp.WriteLine("Image size in inches: {0} x {1}", width.ToString(format, CultureInfo.InvariantCulture), height.ToString(format, CultureInfo.InvariantCulture) ); // Image size in in model units, if needed if (doc.ModelUnitSystem != UnitSystem.Inches) { width = (double)(bitmap.Width / bitmap.HorizontalResolution * unit_scale); height = (double)(bitmap.Height / bitmap.VerticalResolution * unit_scale); RhinoApp.WriteLine("Image size in {0}: {1} x {2}", doc.ModelUnitSystem.ToString().ToLower(), width.ToString(format, CultureInfo.InvariantCulture), height.ToString(format, CultureInfo.InvariantCulture) ); } } // Convert the bitmap to an Eto bitmap var eto_bitmap = ConvertBitmapToEto(bitmap); if (null == eto_bitmap) { RhinoApp.WriteLine("Unable to convert image to Eto bitmap."); return(Result.Failure); } // 12-Jan-2021 Dale Fugier // This should prevent Eto.Drawing.BitmapData.GetPixels() from throwing an exception if (!IsCompatibleBitmap(eto_bitmap)) { RhinoApp.WriteLine("The image has an incompatible pixel format. Please select an image with 24 or 32 bits per pixel, or 8 bit indexed."); return(Result.Failure); } // This bitmap is not needed anymore, so dispose of it bitmap.Dispose(); // Gets the Potrace settings from the plug-in settings file GetPotraceSettings(); // Create the conduit, which does most of the work var conduit = new VectorizeConduit( eto_bitmap, scale, doc.ModelAbsoluteTolerance, m_select_output ? Rhino.ApplicationSettings.AppearanceSettings.SelectedObjectColor : doc.Layers.CurrentLayer.Color ) { Enabled = true }; if (mode == RunMode.Interactive) { // Show the interactive dialog box var dialog = new VectorizeDialog(doc, conduit); dialog.RestorePosition(); var result = dialog.ShowSemiModal(doc, RhinoEtoApp.MainWindow); dialog.SavePosition(); if (result != Result.Success) { conduit.Enabled = false; Potrace.Clear(); doc.Views.Redraw(); return(Result.Cancel); } } else { // Show the command line options var go = new GetOption(); go.SetCommandPrompt("Vectorization options. Press Enter when done"); go.AcceptNothing(true); while (true) { conduit.TraceBitmap(); doc.Views.Redraw(); go.ClearCommandOptions(); // IgnoreArea var turdsize_opt = new OptionInteger(Potrace.turdsize, 2, 100); var turdsize_idx = go.AddOptionInteger("FilterSize", ref turdsize_opt, "Filter speckles of up to this size in pixels"); // TurnPolicy var turnpolicy_idx = go.AddOptionEnumList("TurnPolicy", Potrace.turnpolicy); // Optimizing var curveoptimizing_opt = new OptionToggle(Potrace.curveoptimizing, "No", "Yes"); var curveoptimizing_idx = go.AddOptionToggle("Optimizing", ref curveoptimizing_opt); // Tolerance var opttolerance_opt = new OptionDouble(Potrace.opttolerance, 0.0, 1.0); var opttolerance_idx = go.AddOptionDouble("Tolerance", ref opttolerance_opt, "Optimizing tolerance"); // CornerThreshold var alphamax_opt = new OptionDouble(Potrace.alphamax, 0.0, 100.0); var alphamax_idx = go.AddOptionDouble("CornerRounding", ref alphamax_opt, "Corner rounding threshold"); // Threshold var threshold_opt = new OptionDouble(Potrace.Treshold, 0.0, 100.0); var threshold_idx = go.AddOptionDouble("Threshold", ref threshold_opt, "Threshold"); // RestoreDefaults var defaults_idx = go.AddOption("RestoreDefaults"); var res = go.Get(); if (res == GetResult.Option) { var option = go.Option(); if (null != option) { if (turdsize_idx == option.Index) { Potrace.turdsize = turdsize_opt.CurrentValue; } if (turnpolicy_idx == option.Index) { var list = Enum.GetValues(typeof(TurnPolicy)).Cast <TurnPolicy>().ToList(); Potrace.turnpolicy = list[option.CurrentListOptionIndex]; } if (curveoptimizing_idx == option.Index) { Potrace.curveoptimizing = curveoptimizing_opt.CurrentValue; } if (opttolerance_idx == option.Index) { Potrace.opttolerance = opttolerance_opt.CurrentValue; } if (alphamax_idx == option.Index) { Potrace.alphamax = alphamax_opt.CurrentValue; } if (threshold_idx == option.Index) { Potrace.Treshold = threshold_opt.CurrentValue; } if (defaults_idx == option.Index) { Potrace.RestoreDefaults(); } } continue; } if (res != GetResult.Nothing) { conduit.Enabled = false; doc.Views.Redraw(); Potrace.Clear(); return(Result.Cancel); } break; } } // Group curves var attributes = doc.CreateDefaultAttributes(); attributes.AddToGroup(doc.Groups.Add()); for (var i = 0; i < conduit.OutlineCurves.Count; i++) { var rhobj_id = doc.Objects.AddCurve(conduit.OutlineCurves[i], attributes); if (m_select_output) { var rhobj = doc.Objects.Find(rhobj_id); if (null != rhobj) { rhobj.Select(true); } } } conduit.Enabled = false; Potrace.Clear(); doc.Views.Redraw(); // Set the Potrace settings to the plug -in settings file. SetPotraceSettings(); return(Result.Success); }