using OSCADSharp.Spatial; using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using OSCADSharp.DataBinding; namespace OSCADSharp.Solids.Imported.Images { /// /// Processes a bitmap image by treating contiguous same-color regions as cubes /// internal class PolygonalImageProcessor : IImageProcessor { #region Private Classes private class Polygon : OSCADObject { private List points; public Polygon(List points) { this.points = points; } public override string ToString() { StringBuilder sb = new StringBuilder(); sb.Append("polygon(points=["); Point pt; for (int i = 0; i < this.points.Count; i++) { pt = this.points[i]; if(i == 0) sb.Append(String.Format("[{0}, {1}]", pt.X, pt.Y)); else sb.Append(String.Format(", [{0}, {1}]", pt.X, pt.Y)); } sb.Append("]);"); return sb.ToString(); } public override void Bind(string property, Variable variable) { throw new NotImplementedException(); } public override Bounds Bounds() { var bottomLeft = new Vector3(int.MaxValue, int.MaxValue, 0); var topRight = new Vector3(int.MinValue, int.MinValue, 1); foreach (var point in this.points) { if (point.X < bottomLeft.X) bottomLeft.X = point.X; if (point.Y < bottomLeft.Y) bottomLeft.Y = point.Y; if (point.X > topRight.X) topRight.X = point.X; if (point.Y > topRight.Y) topRight.Y = point.Y; } return new Spatial.Bounds(bottomLeft, topRight); } public override OSCADObject Clone() { return new Polygon(this.points); } public override Vector3 Position() { var bounds = this.Bounds(); return Vector3.Average(bounds.TopRight, bounds.BottomLeft); } } #endregion #region Private Fields private string imagePath; #endregion #region Public Fields public Bounds ImageBounds { get; set; } #endregion #region Constructors internal PolygonalImageProcessor(string imagePath) { this.imagePath = imagePath; } #endregion public OSCADObject ProcessImage() { var polygons = this.processImage(); OSCADObject obj = new OSCADObject.MultiStatementObject("union()", polygons); obj = obj.Scale(1, -1, 1).Translate(0, ImageBounds.Width, 0); return obj; } private List processImage() { Bitmap img = new Bitmap(Image.FromFile(this.imagePath)); if (img.Width > 200 || img.Height > 200) { throw new InvalidOperationException("Cannot process images larger greater than 200x200 pixels"); } this.ImageBounds = new Bounds(new Vector3(), new Vector3(img.Width, img.Height, 1)); var separatedColors = this.separateColors(img); IEnumerable>> contiguousSections = new List>>(); //Parallel.ForEach(separatedColors.Values, (colorGroup) => foreach (var colorGroup in separatedColors.Values) { var sections = this.getContiguousSections(colorGroup); contiguousSections = contiguousSections.Concat(sections); }//); return this.convertToPolygons(contiguousSections); } private List convertToPolygons(IEnumerable>> contiguousSections) { List objects = new List(); StringBuilder sb = new StringBuilder(); var orderedSections = contiguousSections.OrderBy(sec => sec.Count); var largest = orderedSections.Last().Count; foreach (var section in orderedSections) { var color = section[0].Value; var orderedSection = this.getOrderedPoints(section); OSCADObject pgon = new Polygon(orderedSection); pgon = pgon.Color(String.Format("[{0}, {1}, {2}]", color.R == 0 ? 0 : color.R / 255, color.G == 0 ? 0 : color.G / 255, color.B == 0 ? 0 : color.B / 255), color.A); //pgon = pgon.Scale(1, 1, (1.0 / section.Count/ largest) * 100); objects.Add(pgon); //foreach (var pair in section) //{ // var position = pair.Key; // var color = pair.Value; // var cube = new Cube().Color(String.Format("[{0}, {1}, {2}]", color.R == 0 ? 0 : color.R / 255, color.G == 0 ? 0 : color.G / 255, color.B == 0 ? 0 : color.B / 255), color.A); // cube = cube.Translate(position.X, position.Y, 0); // objects.Add(cube); //} } return objects; } private List getOrderedPoints(List> section) { List orderedPoints = new List(section.Count); var grid = new AdjacentPixelMatrix(section); var traversed = new HashSet(); var neighborFinder = new NeighboringPointFinder(true); Stack toTraverse = new Stack(); Point? origin = section[0].Key; while(origin != null) { var orig = (Point)origin; traversed.Add(orig); orderedPoints.Add(orig); var below = neighborFinder.Below(orig); var right = neighborFinder.Right(orig); var above = neighborFinder.Above(orig); var left = neighborFinder.Left(orig); if (grid.IsInBoundsAndNotNull(above, true) && !traversed.Contains(above)) { origin = above; } else if (grid.IsInBoundsAndNotNull(right, true) && !traversed.Contains(right)) { origin = right; } else if (grid.IsInBoundsAndNotNull(below, true) && !traversed.Contains(below)) { origin = below; } else if (grid.IsInBoundsAndNotNull(left, true) && !traversed.Contains(left)) { origin = left; } else { //TODO: Handle additional paths origin = null; } } return orderedPoints; } private List>> getContiguousSections(List> colorGrouping) { var grid = new AdjacentPixelMatrix(colorGrouping); var sections = new List>>(); while (colorGrouping.Count > 0) { var origin = colorGrouping[0]; colorGrouping.RemoveAt(0); sections.Add(this.getConnectedPixelsOfSameColor(origin, grid, colorGrouping)); } foreach (var section in sections) { this.removeCenterPixels(section, grid); } return sections; } private void removeCenterPixels(List> section, AdjacentPixelMatrix grid) { var neighborFinder = new NeighboringPointFinder(); for (int i = section.Count - 1; i >= 0; i--) { var origin = section[i].Key; var color = section[i].Value; // We only care about cardinal directions for the purpose of removing pixels // that lie in the center of a grouping (to avoid redundant vertexes List neighboringPoints = neighborFinder.GetNeighbors(origin); bool isOnanEdge = false; foreach (var pt in neighboringPoints) { //If out of bounds, we found an edge if (grid.IsOutOfBounds(pt)) { isOnanEdge = true; break; } //int x = pt.X - grid.TopLeft.X; //int y = pt.Y - grid.TopLeft.Y; //if(grid.At(x, y) == null) //{ // isOnanEdge = true; // break; //} //if (grid.At(x, y) != null) //{ // var nbr = (KeyValuePair)grid.At(x, y); // if(!nbr.Value.Equals(color)) // { // isOnanEdge = true; // break; // } //} } if (!isOnanEdge) { section.RemoveAt(i); } } } private List> getConnectedPixelsOfSameColor(KeyValuePair origin, AdjacentPixelMatrix grid, List> colorGrouping) { var neighborFinder = new NeighboringPointFinder(); List> neighbors = new List>(); HashSet> traversed = new HashSet>(); Queue> nextOrigins = new Queue>(); nextOrigins.Enqueue(origin); traversed.Add(origin); neighbors.Add(origin); while (nextOrigins.Count > 0) { origin = nextOrigins.Dequeue(); List neighboringPoints = neighborFinder.GetNeighbors(origin.Key); foreach (var pt in neighboringPoints) { //Ignore if out of bounds if(grid.IsOutOfBounds(pt)) { continue; } int x = pt.X - grid.TopLeft.X; int y = pt.Y - grid.TopLeft.Y; if(grid.At(x, y) != null) { var nbr = (KeyValuePair)grid.At(x, y); if (!traversed.Contains(nbr) && nbr.Value.Equals(origin.Value)) { nextOrigins.Enqueue(nbr); neighbors.Add(nbr); traversed.Add(nbr); } } } } colorGrouping.RemoveAll(elem => traversed.Contains(elem)); return neighbors; } private Dictionary>> separateColors(Bitmap img) { var colorGroupings = new Dictionary>>(); for (int column = 0; column < img.Width; column++) { for (int row = 0; row < img.Height; row++) { var color = img.GetPixel(column, row); string key = String.Format("{0}-{1}-{2}", color.R, color.G, color.B); if (!colorGroupings.ContainsKey(key)) { colorGroupings[key] = new List>(); } colorGroupings[key].Add(new KeyValuePair(new Point(column, row), color)); } } return colorGroupings; } } }