From 69d202170c4d22f73c334636c29127b875f10e27 Mon Sep 17 00:00:00 2001 From: LarsBrubaker Date: Sun, 1 Aug 2021 21:24:33 -0700 Subject: [PATCH] improving image to path controls --- .../DesignTools/Operations/Image/Histogram.cs | 99 ++++++++++++------- .../Operations/Image/ImageToPathObject3D_2.cs | 69 +++++++++++-- .../DesignTools/Primitives/ImageObject3D.cs | 42 ++++++++ .../DesignTools/PublicPropertyEditor.cs | 55 ++++------- .../DesignTools/Sheets/SheetObject3D.cs | 35 +++++-- Submodules/agg-sharp | 2 +- 6 files changed, 215 insertions(+), 87 deletions(-) diff --git a/MatterControlLib/DesignTools/Operations/Image/Histogram.cs b/MatterControlLib/DesignTools/Operations/Image/Histogram.cs index e46d87b9e..515bd132e 100644 --- a/MatterControlLib/DesignTools/Operations/Image/Histogram.cs +++ b/MatterControlLib/DesignTools/Operations/Image/Histogram.cs @@ -33,6 +33,7 @@ using MatterHackers.Agg; using MatterHackers.Agg.Image; using MatterHackers.Agg.Image.ThresholdFunctions; using MatterHackers.Agg.UI; +using MatterHackers.Agg.VertexSource; using MatterHackers.VectorMath; namespace MatterHackers.MatterControl.DesignTools @@ -42,9 +43,9 @@ namespace MatterHackers.MatterControl.DesignTools private ImageBuffer _histogramRawCache = new ImageBuffer(256, 100); private ThemeConfig theme; - public double RangeStart { get; set; } = .1; + public double RangeStart { get; set; } = 0; - public double RangeEnd { get; set; } = 1; + public double RangeEnd { get; set; } = .9; private Color GetRGBA(byte[] buffer, int offset) { @@ -52,6 +53,8 @@ namespace MatterHackers.MatterControl.DesignTools } public event EventHandler RangeChanged; + + public event EventHandler EditComplete; public void RebuildAlphaImage(ImageBuffer sourceImage, ImageBuffer alphaImage) { @@ -97,7 +100,7 @@ namespace MatterHackers.MatterControl.DesignTools byte[] sourceBuffer = sourceImage.GetBuffer(); byte[] destBuffer = alphaImage.GetBuffer(); - for (int y = 0; y < sourceImage.Height; y++) + Parallel.For(0, sourceImage.Height, (y) => { int imageOffset = sourceImage.GetBufferOffsetY(y); @@ -112,7 +115,7 @@ namespace MatterHackers.MatterControl.DesignTools destBuffer[imageBufferOffsetWithX + 2] = b; destBuffer[imageBufferOffsetWithX + 3] = GetAlphaFromIntensity(r, g, b); } - } + }); alphaImage.MarkImageChanged(); } @@ -122,7 +125,8 @@ namespace MatterHackers.MatterControl.DesignTools // build the histogram cache _histogramRawCache = new ImageBuffer(256, 100); var counts = new int[_histogramRawCache.Width]; - var function = new MapOnMaxIntensity(RangeStart, RangeEnd); + IThresholdFunction function = new MapOnMaxIntensity(RangeStart, RangeEnd); + function = new HueThresholdFunction(RangeStart, RangeEnd); byte[] buffer = image.GetBuffer(); for (int y = 0; y < image.Height; y++) @@ -141,7 +145,7 @@ namespace MatterHackers.MatterControl.DesignTools .OrderByDescending(vi => vi.value) .First().value; var graphics2D2 = _histogramRawCache.NewGraphics2D(); - graphics2D2.Clear(Color.White); + graphics2D2.Clear(ApplicationController.Instance.Theme.SlightShade); for (int i = 0; i < 256; i++) { graphics2D2.Line(i, 0, i, Easing.Exponential.Out(counts[i] / max) * _histogramRawCache.Height, Color.Black); @@ -156,7 +160,6 @@ namespace MatterHackers.MatterControl.DesignTools HAnchor = HAnchor.Stretch, Height = 60 * GuiWidget.DeviceScale, Margin = 5, - BackgroundColor = theme.SlightShade }; var handleWidth = 10 * GuiWidget.DeviceScale; @@ -167,15 +170,39 @@ namespace MatterHackers.MatterControl.DesignTools Margin = new BorderDouble(handleWidth, 0) }; - histogramBackground.AfterDraw += HistogramBackground_AfterDraw; + histogramBackground.AfterDraw += (s, e) => + { + var rangeStart = RangeStart; + var rangeEnd = RangeEnd; + var graphics2D = e.Graphics2D; + graphics2D.Render(_histogramRawCache, 0, 0); + var background = _histogramRawCache; + graphics2D.FillRectangle(rangeStart * background.Width, 0, rangeEnd * background.Width, background.Height, theme.PrimaryAccentColor.WithAlpha(60)); + }; + histogramWidget.AddChild(histogramBackground); + void RenderHandle(Graphics2D g, double s, double e) + { + var w = g.Width; + var h = g.Height; + g.Line(w * e, 0, w * e, h, theme.TextColor); + var leftEdge = new VertexStorage(); + leftEdge.MoveTo(w * e, h * .80); + leftEdge.curve3(w * e, h * .70, w * .5, h * .70); + leftEdge.curve3(w * s, h * .60); + leftEdge.LineTo(w * s, h * .40); + leftEdge.curve3(w * s, h * .30, w * .5, h * .30); + leftEdge.curve3(w * e, h * .20); + g.Render(new FlattenCurves(leftEdge), theme.TextColor); + g.Line(w * .35, h * .6, w * .35, h * .4, theme.BackgroundColor); + g.Line(w * .65, h * .6, w * .65, h * .4, theme.BackgroundColor); + } + var leftHandle = new ImageWidget((int)(handleWidth), (int)histogramWidget.Height); leftHandle.Position = new Vector2(RangeStart * _histogramRawCache.Width, 0); var image = leftHandle.Image; - var leftGraphics = image.NewGraphics2D(); - leftGraphics.Line(image.Width, 0, image.Width, image.Height, theme.TextColor); - leftGraphics.FillRectangle(0, image.Height / 4, image.Width, image.Height / 4 * 3, theme.TextColor); + RenderHandle(image.NewGraphics2D(), 0, 1); histogramWidget.AddChild(leftHandle); bool leftDown = false; @@ -193,23 +220,29 @@ namespace MatterHackers.MatterControl.DesignTools if (leftDown) { var offset = e.Position.X - leftX; - RangeStart += offset / _histogramRawCache.Width; - RangeStart = Math.Max(0, Math.Min(RangeStart, RangeEnd)); - leftHandle.Position = new Vector2(RangeStart * _histogramRawCache.Width, 0); - RangeChanged?.Invoke(this, null); + var newStart = RangeStart + offset / _histogramRawCache.Width; + newStart = agg_basics.Clamp(newStart, 0, RangeEnd); + if (RangeStart != newStart) + { + RangeStart = newStart; + leftHandle.Position = new Vector2(RangeStart * _histogramRawCache.Width, 0); + RangeChanged?.Invoke(this, null); + } } }; leftHandle.MouseUp += (s, e) => { - leftDown = false; + if (leftDown) + { + leftDown = false; + EditComplete?.Invoke(this, null); + } }; var rightHandle = new ImageWidget((int)(handleWidth), (int)histogramWidget.Height); rightHandle.Position = new Vector2(RangeEnd * _histogramRawCache.Width + handleWidth, 0); image = rightHandle.Image; - var rightGraphics = image.NewGraphics2D(); - rightGraphics.Line(0, 0, 0, image.Height, theme.TextColor); - rightGraphics.FillRectangle(0, image.Height / 4, image.Width, image.Height / 4 * 3, theme.TextColor); + RenderHandle(image.NewGraphics2D(), 1, 0); histogramWidget.AddChild(rightHandle); bool rightDown = false; @@ -227,28 +260,26 @@ namespace MatterHackers.MatterControl.DesignTools if (rightDown) { var offset = e.Position.X - rightX; - RangeEnd += offset / _histogramRawCache.Width; - RangeEnd = Math.Min(1, Math.Max(RangeStart, RangeEnd)); - rightHandle.Position = new Vector2(RangeEnd * _histogramRawCache.Width + handleWidth, 0); - RangeChanged?.Invoke(this, null); + var newEnd = RangeEnd + offset / _histogramRawCache.Width; + newEnd = agg_basics.Clamp(newEnd, RangeStart, 1); + if (RangeEnd != newEnd) + { + RangeEnd = newEnd; + rightHandle.Position = new Vector2(RangeEnd * _histogramRawCache.Width + handleWidth, 0); + RangeChanged?.Invoke(this, null); + } } }; rightHandle.MouseUp += (s, e) => { - rightDown = false; + if (rightDown) + { + rightDown = false; + EditComplete?.Invoke(this, null); + } }; return histogramWidget; } - - private void HistogramBackground_AfterDraw(object sender, DrawEventArgs e) - { - var rangeStart = RangeStart; - var rangeEnd = RangeEnd; - var graphics2D = e.Graphics2D; - graphics2D.Render(_histogramRawCache, 0, 0); - var background = _histogramRawCache; - graphics2D.FillRectangle(rangeStart * background.Width, 0, rangeEnd * background.Width, background.Height, theme.PrimaryAccentColor.WithAlpha(60)); - } } } \ No newline at end of file diff --git a/MatterControlLib/DesignTools/Operations/Image/ImageToPathObject3D_2.cs b/MatterControlLib/DesignTools/Operations/Image/ImageToPathObject3D_2.cs index 4efcec685..e6d3dace1 100644 --- a/MatterControlLib/DesignTools/Operations/Image/ImageToPathObject3D_2.cs +++ b/MatterControlLib/DesignTools/Operations/Image/ImageToPathObject3D_2.cs @@ -29,6 +29,7 @@ either expressed or implied, of the FreeBSD Project. using System; using System.Collections.Generic; +using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Threading.Tasks; @@ -51,14 +52,14 @@ using Polygons = System.Collections.Generic.List + { + IntensityHistogram.RebuildAlphaImage(SourceImage, _image); + }; + + IntensityHistogram.EditComplete += (s, e) => { this.Invalidate(InvalidateType.Properties); }; + + switch (FeatureDetector) + { + case FeatureDetectors.Intensity: + IntensityHistogram.RebuildAlphaImage(SourceImage, _image); + break; + + case FeatureDetectors.Transparency: + _image.CopyFrom(SourceImage); + break; + } } return _image; @@ -94,8 +112,38 @@ namespace MatterHackers.MatterControl.DesignTools } + private FeatureDetectors _featureDetector = FeatureDetectors.Intensity; [EnumDisplay(Mode = EnumDisplayAttribute.PresentationMode.Tabs)] - public ThresholdFunctions FeatureDetector { get; set; } = ThresholdFunctions.Intensity; + public FeatureDetectors FeatureDetector + { + get + { + return _featureDetector; + } + + set + { + if (_featureDetector != value) + { + _featureDetector = value; + switch (FeatureDetector) + { + case FeatureDetectors.Intensity: + IntensityHistogram.RebuildAlphaImage(SourceImage, Image); + break; + + case FeatureDetectors.Transparency: + Image?.CopyFrom(SourceImage); + break; + } + } + } + } + + [DisplayName("")] + [ReadOnly(true)] + public string TransparencyMessage { get; set; } = "Your image is processed as is with no modifications. Transparent pixels are ignored, only opaque pixels are considered in feature detection."; + [JsonIgnore] private ImageBuffer SourceImage => ((IImageProvider)this.Descendants().Where(i => i is IImageProvider).FirstOrDefault())?.Image; @@ -189,6 +237,7 @@ namespace MatterHackers.MatterControl.DesignTools && !RebuildLocked) { IntensityHistogram.BuildHistogramFromImage(SourceImage); + IntensityHistogram.RebuildAlphaImage(SourceImage, _image); await Rebuild(); } else if ((invalidateArgs.InvalidateType.HasFlag(InvalidateType.Properties) && invalidateArgs.Source == this)) @@ -217,7 +266,7 @@ namespace MatterHackers.MatterControl.DesignTools var progressStatus = new ProgressStatus(); switch (FeatureDetector) { - case ThresholdFunctions.Transparency: + case FeatureDetectors.Transparency: this.GenerateMarchingSquaresAndLines( (progress0to1, status) => { @@ -229,7 +278,7 @@ namespace MatterHackers.MatterControl.DesignTools new AlphaFunction()); break; - case ThresholdFunctions.Intensity: + case FeatureDetectors.Intensity: this.GenerateMarchingSquaresAndLines( (progress0to1, status) => { @@ -251,5 +300,11 @@ namespace MatterHackers.MatterControl.DesignTools return Task.CompletedTask; }); } + + public void UpdateControls(PublicPropertyChange change) + { + change.SetRowVisible(nameof(IntensityHistogram), () => FeatureDetector == FeatureDetectors.Intensity); + change.SetRowVisible(nameof(TransparencyMessage), () => FeatureDetector == FeatureDetectors.Transparency); + } } } \ No newline at end of file diff --git a/MatterControlLib/DesignTools/Primitives/ImageObject3D.cs b/MatterControlLib/DesignTools/Primitives/ImageObject3D.cs index 7aae7163e..c5f8c83fe 100644 --- a/MatterControlLib/DesignTools/Primitives/ImageObject3D.cs +++ b/MatterControlLib/DesignTools/Primitives/ImageObject3D.cs @@ -36,8 +36,10 @@ using MatterHackers.Agg; using MatterHackers.Agg.Image; using MatterHackers.Agg.ImageProcessing; using MatterHackers.Agg.Platform; +using MatterHackers.Agg.UI; using MatterHackers.DataConverters3D; using MatterHackers.Localizations; +using MatterHackers.MatterControl.DataStorage; using MatterHackers.MatterControl.PartPreviewWindow; using MatterHackers.PolygonMesh; using Newtonsoft.Json; @@ -240,5 +242,45 @@ namespace MatterHackers.MatterControl.DesignTools return null; } + + public void AddEditorExtra(GuiWidget imageWidget, ThemeConfig theme, Action updateEditorImage) + { + imageWidget.Click += (s, e) => + { + if (e.Button == MouseButtons.Right) + { + var popupMenu = new PopupMenu(theme); + + var pasteMenu = popupMenu.CreateMenuItem("Paste".Localize()); + pasteMenu.Click += (s2, e2) => + { + var activeImage = Clipboard.Instance.GetImage(); + + // Persist + string filePath = ApplicationDataStorage.Instance.GetNewLibraryFilePath(".png"); + ImageIO.SaveImageData( + filePath, + activeImage); + + this.AssetPath = filePath; + this.Mesh = null; + + updateEditorImage(); + + this.Invalidate(InvalidateType.Image); + }; + + pasteMenu.Enabled = Clipboard.Instance.ContainsImage; + + var copyMenu = popupMenu.CreateMenuItem("Copy".Localize()); + copyMenu.Click += (s2, e2) => + { + Clipboard.Instance.SetImage(this.Image); + }; + + popupMenu.ShowMenu(imageWidget, e); + } + }; + } } } \ No newline at end of file diff --git a/MatterControlLib/DesignTools/PublicPropertyEditor.cs b/MatterControlLib/DesignTools/PublicPropertyEditor.cs index ff3f0e675..3ad938a85 100644 --- a/MatterControlLib/DesignTools/PublicPropertyEditor.cs +++ b/MatterControlLib/DesignTools/PublicPropertyEditor.cs @@ -618,6 +618,24 @@ namespace MatterHackers.MatterControl.DesignTools imageWidget.Margin = new BorderDouble(0, 3); } + imageWidget.BeforeDraw += (s, e) => + { + // render a checkerboard that can show through the alpha mask + var g = e.Graphics2D; + var w = (int)(10 * GuiWidget.DeviceScale); + for (int x = 0; x < g.Width / w; x ++) + { + for (int y = 0; y < g.Height / w; y ++) + { + if (y % 2 == 0 && x % 2 == 1 + || y % 2 == 1 && x % 2 == 0) + { + g.FillRectangle(x * w, y * w, x * w + w, y * w + w, Color.LightGray); + } + } + } + }; + ImageBuffer GetImageCheckingForErrors() { var image = imageBuffer; @@ -665,42 +683,7 @@ namespace MatterHackers.MatterControl.DesignTools if (object3D is ImageObject3D imageObject) { - imageWidget.Click += (s, e) => - { - if (e.Button == MouseButtons.Right) - { - var popupMenu = new PopupMenu(theme); - - var pasteMenu = popupMenu.CreateMenuItem("Paste".Localize()); - pasteMenu.Click += (s2, e2) => - { - var activeImage = Clipboard.Instance.GetImage(); - - // Persist - string filePath = ApplicationDataStorage.Instance.GetNewLibraryFilePath(".png"); - ImageIO.SaveImageData( - filePath, - activeImage); - - imageObject.AssetPath = filePath; - imageObject.Mesh = null; - - UpdateEditorImage(); - - imageObject.Invalidate(InvalidateType.Image); - }; - - pasteMenu.Enabled = Clipboard.Instance.ContainsImage; - - var copyMenu = popupMenu.CreateMenuItem("Copy".Localize()); - copyMenu.Click += (s2, e2) => - { - Clipboard.Instance.SetImage(imageObject.Image); - }; - - popupMenu.ShowMenu(imageWidget, e); - } - }; + imageObject.AddEditorExtra(imageWidget, theme, UpdateEditorImage); } rowContainer.AddChild(imageWidget); diff --git a/MatterControlLib/DesignTools/Sheets/SheetObject3D.cs b/MatterControlLib/DesignTools/Sheets/SheetObject3D.cs index f18f03ce1..baaf59a3a 100644 --- a/MatterControlLib/DesignTools/Sheets/SheetObject3D.cs +++ b/MatterControlLib/DesignTools/Sheets/SheetObject3D.cs @@ -249,29 +249,46 @@ namespace MatterHackers.MatterControl.DesignTools ["index2"] = (owner) => RetrieveArrayIndex(owner, 2), }; - private static ArrayObject3D FindParentArray(IObject3D item, int level) + private static ArrayObject3D FindParentArray(IObject3D item, int wantLevel) { + int foundLevel = 0; // look through all the parents foreach (var parent in item.Parents()) { - // then each child of any give parent - foreach (var sibling in parent.Children) + // if it is a sheet + if (parent is ArrayObject3D arrayObject) { - // if it is a sheet - if (sibling != item - && sibling is SheetObject3D sheet) + if (foundLevel == wantLevel) { - return sheet; + return arrayObject; } + + foundLevel++; } } return null; } - private static double RetrieveArrayIndex(IObject3D owner, int level) + private static int RetrieveArrayIndex(IObject3D owner, int level) { - throw new NotImplementedException(); + var arrayObject = FindParentArray(owner, level); + + if (arrayObject != null) + { + int index = 0; + foreach(var child in arrayObject.Children) + { + if (child == owner) + { + return index; + } + + index++; + } + } + + return 0; } private static string ReplaceConstantsWithValues(IObject3D owner, string stringWithConstants) diff --git a/Submodules/agg-sharp b/Submodules/agg-sharp index 3355908f5..2ec468c98 160000 --- a/Submodules/agg-sharp +++ b/Submodules/agg-sharp @@ -1 +1 @@ -Subproject commit 3355908f5e59e8f0afa64478111071d656ab5d47 +Subproject commit 2ec468c986a2e2a4b65fc092e120760d3f0e1a2d