improving image to path controls

This commit is contained in:
LarsBrubaker 2021-08-01 21:24:33 -07:00
parent 7e22f26770
commit 69d202170c
6 changed files with 215 additions and 87 deletions

View file

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

View file

@ -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<System.Collections.Generic.List
namespace MatterHackers.MatterControl.DesignTools
{
[HideMeterialAndColor]
public class ImageToPathObject3D_2 : Object3D, IImageProvider, IPathObject, ISelectedEditorDraw, IObject3DControlsProvider
public class ImageToPathObject3D_2 : Object3D, IImageProvider, IPathObject, ISelectedEditorDraw, IObject3DControlsProvider, IPropertyGridModifier
{
public ImageToPathObject3D_2()
{
Name = "Image to Path".Localize();
}
public enum ThresholdFunctions
public enum FeatureDetectors
{
Transparency,
Colors,
@ -75,14 +76,31 @@ namespace MatterHackers.MatterControl.DesignTools
{
get
{
if (_image == null)
if (_image == null
&& SourceImage != null)
{
_image = new ImageBuffer(SourceImage);
IntensityHistogram.RebuildAlphaImage(SourceImage, _image);
IntensityHistogram.BuildHistogramFromImage(SourceImage);
IntensityHistogram.RangeChanged += (s, e) =>
{
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);
}
}
}

View file

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

View file

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

View file

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

@ -1 +1 @@
Subproject commit 3355908f5e59e8f0afa64478111071d656ab5d47
Subproject commit 2ec468c986a2e2a4b65fc092e120760d3f0e1a2d