internal static new double ApproxFit(ImageSection ImgSec) { //double Fit = 0d; //return Fit; //Just return circle from top to bottom, left to right return ImgSec.PixelsUsed().Length / (ImgSec.Width / 2 * ImgSec.Height / 2 * Math.PI); }
/// <summary> /// /// </summary> /// <param name="ImgSec"></param> /// <param name="Vertices"></param> /// <returns></returns> internal Point[] HardFit(ImageSection ImgSec) { List<Point> Corners = GenerateCorners(ImgSec); //Find the Centroid //Func<Point, Point, Point, double> GetAngle = (a, b, c) => { }; //http://en.wikipedia.org/wiki/Centroid#Centroid_of_polygon //Point Centroid; //{ // int ASigma = 0, XSigma = 0, YSigma = 0, ASigAdd; // for (int i = 0; i < Outsides.Count - 1; i++) // { // ASigAdd = Outsides[i].X * Outsides[i + 1].Y - Outsides[i + 1].X * Outsides[i].Y; // ASigma += ASigAdd; // XSigma += (Outsides[i].X + Outsides[i + 1].X) * ASigAdd; // YSigma += (Outsides[i].Y + Outsides[i + 1].Y) * ASigAdd; // } // double A = 1d / 2d * ASigma; // double // X = 1d / (6d * A) * XSigma, // Y = 1d / (6d * A) * YSigma; // Centroid = new Point((int)X, (int)Y); //} //Ensure that the whole thing is convex //{ // Func<Point, Point, Point, bool> IsConcave = (a, b, c) => GetAngle(a, b, Centroid) + GetAngle(Centroid, b, c) > 180d; // for (int k = 0; k < Corners.Count; k++) // if (IsConcave(Corners[k], Corners[(k + 1) % Corners.Count], Corners[(k + 2) % Corners.Count])) // { Corners.RemoveAt((k + 1) % Corners.Count); k--; } //} while (Corners.Count < mVerticeCount && mVerticeCount != -1) Corners.Insert(0, Corners[0]); if (Corners.Count > mVerticeCount && mVerticeCount != -1) { //Insert points as new Point[]{Base1, Base2, Extender2, Extender1} List<Point[]> TrianglePossibilities; List<double> TriangleScores; //Figure out, based on ASA, the area of a triangle; note that the angles are in radians //in<Angle1, Side, Angle2, output> Func<double, double, double, double> AreaOfTriangle = (a1, s, a2) => s * s * Math.Sin(a1) * Math.Sin(a2) / Math.Sin(Math.PI - (a1 + a2)); //a = s*sin(A)/sin(180-(A+B)) //area = a*s*sin(B) //Func<Point, Point, double> Hyp = (p1,p2) => Math.Sqrt(Math.Pow(p1.X - p2.X,2) + Math.Pow(p1.Y - p2.Y,2)); //Func<Point, Point, Point, double> GetAngle = // (r1, v, r2) => // Math.Acos((Math.Pow(Hyp(v, r1), 2) + Math.Pow(Hyp(v, r2), 2) + Math.Pow(Hyp(r1, r2), 2)) / (2 * Hyp(v, r1) * Hyp(v, r2))); double TempArea, TempBase; //while (Corners.Count > Vertices) { TrianglePossibilities = new List<Point[]>(); TriangleScores = new List<double>(); //int b1, b2, e1, e2; for (int i = 0; i < Corners.Count; i++) { //Compare using 1&2, repetition ensures that all possibilities get considered TrianglePossibilities.Add(new Point[] { Corners[(i + 3) % Corners.Count], Corners[i], Corners[(i + 1) % Corners.Count], Corners[(i + 2) % Corners.Count] }); //You have to use this formula for area of the triangle, because the peak of the triangle is an unknown point beyond the polygon //Save the efficiency of doing each of these by taking the area * altitude of the full triangle and subtracting the area of the quadrilateral TriangleScores.Add( (TempArea = AreaOfTriangle( GetAngle(Corners[i], Corners[(i + 3) % Corners.Count], Corners[(i + 2) % Corners.Count]), TempBase = Hyp(Corners[(i + 3) % Corners.Count], Corners[i]), GetAngle(Corners[(i + 3) % Corners.Count], Corners[i], Corners[(i + 1) % Corners.Count]))) * GetAltitude(TempBase, TempArea) - Area(Corners[i], Corners[(i + 1) % Corners.Count], Corners[(i + 2) % Corners.Count], Corners[(i + 3) % Corners.Count])); //Apologies for the complexity. Note the repetition of Corners[(i + k) % Corners.Count] //k=1=e1, k=2=e2, k=3=b1, k=0=b2 //This has to be this way to prevent impossible triangles //I've already checked that the shape is convex, so \_/ is only guaranteed to work as \/ } //Based off of y-y2=m(x-x2) //a=b1=p[3], b=b2=p[0], c=e1=p[2], d=e2=p[1], e=a.y, f=b.y, g=c.y, h=d.y //using wolframalpha to solve //x = -(-(c*(e-g))/(a-c)+(d*(f-h))/(b-d)+g-h)/((e-g)/(a-c)-(f-h)/(b-d)) //y = -(-a*f*g+a*g*h+b*e*h-b*g*h+c*e*f-c*e*h-d*e*f+d*f*g)/(a*f-a*h-b*e+b*g-c*f+c*h+d*e-d*g) Func<Point[], Point> CalcThirdPoint = p => new Point( -(-(p[2].X * (p[3].Y - p[2].Y)) / (p[3].X - p[2].X) + (p[1].X * (p[0].Y - p[1].Y)) / (p[0].X - p[1].X) + p[2].Y - p[1].Y) / ((p[3].Y - p[2].Y) / (p[3].X - p[2].X) - (p[0].Y - p[1].Y) / (p[0].X - p[1].X)), -(-p[3].X * p[0].Y * p[2].Y + p[3].X * p[2].Y * p[1].Y + p[0].X * p[3].Y * p[1].Y - p[0].X * p[2].Y * p[1].Y + p[2].X * p[3].Y * p[0].Y - p[2].X * p[3].Y * p[1].Y - p[1].X * p[3].Y * p[0].Y + p[1].X * p[0].Y * p[2].Y) / (p[3].X * p[0].Y - p[3].X * p[1].Y - p[0].X * p[3].Y + p[0].X * p[2].Y - p[2].X * p[0].Y + p[2].X * p[1].Y + p[1].X * p[3].Y - p[1].X * p[2].Y)); List<int> indices = new List<int>(); int index; while (Corners.Count > mVerticeCount) { index = TriangleScores.IndexOf(TriangleScores.Min()); Corners.Insert(Corners.IndexOf(TrianglePossibilities[index][2]), CalcThirdPoint(TrianglePossibilities[index])); Corners.Remove(TrianglePossibilities[index][2]); Corners.Remove(TrianglePossibilities[index][3]); TrianglePossibilities.RemoveAt(index); TriangleScores.RemoveAt(index); //Also remove 2 surrounding possibilities, since their estimates and required points no longer exist if (index - 1 >= 0) { TrianglePossibilities.RemoveAt(index - 1); TriangleScores.RemoveAt(index - 1); } if (index + 1 < TrianglePossibilities.Count) { TrianglePossibilities.RemoveAt(index + 1); TriangleScores.RemoveAt(index + 1); } } } } return Corners.ToArray(); throw new NotImplementedException(); }
/// <summary> /// /// </summary> /// <param name="ImgSec"></param> /// <returns></returns> internal static new double ApproxFit(ImageSection ImgSec) { return ImgSec.PixelsUsed().Length / Area(GenerateCorners(ImgSec).ToArray()); }
protected Polygon(Child Parent, ImageSection ImgSec, int VertexCount) : base(Parent, ImgSec) { if (VertexCount <= 2) throw new InvalidOperationException("Vertices count must be greater than 2"); mVerticeCount = VertexCount; }
public Rectangle(Child Parent, ImageSection ImgSec) : base(Parent, ImgSec, 4) { }
/// <summary> /// /// </summary> /// <param name="ImgSec"></param> /// <returns></returns> private static List<Point> GenerateCorners(ImageSection ImgSec) { //Find all points that are clear on at least two sides of any other selected pixels List<Point> Outsides; { Point[] Pts = ImgSec.PixelsUsed(); Outsides = new List<Point>(); foreach (Point p in Pts) { int Bordering = 0; foreach (Point q in Pts) { //It only counts as bordering if they are adjacent, not diagonal if (Math.Abs(p.X - q.X) == 1 ^ Math.Abs(p.Y - q.Y) == 1) Bordering++; if (Bordering > 3) break; } if (Bordering <= 3) Outsides.Add(p); } } List<Point> Corners = new List<Point>(); //Order points around the shape in a roughly circular shape //This solution also ensure that the resulting shape is convex //I'll take a scan approach //Pick a point that you know is on the outside, so any one with min or max x or y //I'm going to find min y, then min x for that //Then do a scan using that and (-1,-1) to find the point with the SMALLEST angle //Guaranteed, those two points are part of the convex containing polygon //Using those two points, scan around //Scan every other point and its angle with the current two //The point with the greatest angle is the next convex vertex //Continue until you reach the original point { Point a, b = new Point(-1, -1), c = new Point(-1, -1); double angle = Math.PI, temp; //int MinY = Outsides.Min(p => p.Y); //MinY = 0; a = new Point(Outsides.Min(p => (p.Y == 0 ? p.X : ImgSec.Width)), 0); foreach (Point p in Outsides) { if (!p.Equals(a)) if ((temp = GetAngle(new Point(-1, -1), a, p)) < angle) { b = p; angle = temp; } } Corners.Add(a); Corners.Add(b); do { angle = 0d; foreach (Point q in Outsides) if (!Corners.Contains(q)) if ((temp = GetAngle(Corners[Corners.Count - 2], Corners[Corners.Count - 1], q)) > angle) { c = q; angle = temp; } if (Corners.Contains(c)) break; else Corners.Add(c); } while (Corners.Count < Outsides.Count); } return Corners; }
/// <summary> /// /// </summary> /// <param name="Graphic"></param> /// <param name="x"></param> /// <param name="y"></param> /// <returns></returns> internal ImageSection Recognize(ref ImageSection Graphic, int x, int y) { if (mVisualReport != null && mVisDisplay != null) { Master.sInvokable.Invoke(new Action(mVisDisplay.Show)); mVisualReport.Start(); } //We've got something, so now scan to find adjacent pixels. //Step 1: diagonal, down-right to get a general square shape //Step 2: right from each of diag's to fill right side //Step 3: down from each of diag's to fill bottom //Step 4: around in circles around that, to pick up stragglers Func<Color, Point, bool> CheckAdd = (c, p) => { if (this.ValidPixel(c)) { this.AddPixel(p, c); return true; } return false; }; int DiagMove = 0; while (CheckAdd(Graphic.GetPixel(x + DiagMove, y + DiagMove), new Point(x + DiagMove, y + DiagMove))) DiagMove++; //This should work on the first pixel, but change to do{}while if it doesn't int SubY, SubX; for (SubY = 0; SubY <= DiagMove; SubY++) //See the comment on the next line for (SubX = SubY + 1; SubX <= DiagMove - SubY && CheckAdd(Graphic.GetPixel(x + SubX, y + SubY), new Point(x + SubX, y + SubY)); SubX++) ; //Note the semicolon: the logical check does all the needed activity //This is just a perpendicular copy of above for (SubX = 0; SubX <= DiagMove; SubX++) for (SubY = SubX + 1; SubY <= DiagMove - SubX && CheckAdd(Graphic.GetPixel(x + SubX, y + SubY), new Point(x + SubX, y + SubY)); SubY++) ; //Note the semicolon: the logical check does all the needed activity //Now the tricky part, the round about //I'm going to ignore diagonals //For each pixel that's been added so far, check all adjacent pixels not checked yet and checkadd them //Add the successes to another list and foreach through that one as well. Repeat. { //These parenthesis are just to keep these large array variables from lingering too long List<Point> LatestAdds = new List<Point>(), AllTested = new List<Point>(), Failed = new List<Point>(); ; LatestAdds.AddRange(this.SelectedPixels()); Point[] CurrentTesting; do { AllTested.AddRange(LatestAdds); CurrentTesting = LatestAdds.ToArray(); LatestAdds = new List<Point>(); Point Temp; foreach (Point p in CurrentTesting) { Temp = new Point(p.X - 1, p.Y); if (Temp.X >= 0 && !LatestAdds.Contains(Temp) && !Failed.Contains(Temp) && !AllTested.Contains(Temp)) { if (CheckAdd(Graphic.GetPixel(Temp.X, Temp.Y), Temp)) LatestAdds.Add(Temp); else Failed.Add(Temp); } Temp = new Point(p.X, p.Y - 1); if (Temp.Y >= 0 && !LatestAdds.Contains(Temp) && !Failed.Contains(Temp) && !AllTested.Contains(Temp)) { if (CheckAdd(Graphic.GetPixel(Temp.X, Temp.Y), Temp)) LatestAdds.Add(Temp); else Failed.Add(Temp); } Temp = new Point(p.X + 1, p.Y); if (Temp.X < Graphic.Size.Width && !LatestAdds.Contains(Temp) && !Failed.Contains(Temp) && !AllTested.Contains(Temp)) { if (CheckAdd(Graphic.GetPixel(Temp.X, Temp.Y), Temp)) LatestAdds.Add(Temp); else Failed.Add(Temp); } Temp = new Point(p.X, p.Y + 1); if (Temp.Y < Graphic.Size.Height && !LatestAdds.Contains(Temp) && !Failed.Contains(Temp) && !AllTested.Contains(Temp)) { if (CheckAdd(Graphic.GetPixel(Temp.X, Temp.Y), Temp)) LatestAdds.Add(Temp); else Failed.Add(Temp); } } } while (LatestAdds.Count > 0); } // //Almost done // if (mVisualReport != null && mVisDisplay != null) { mVisualReport.Stop(); Master.sInvokable.Invoke(new Action(mVisDisplay.CloseSafe)); } if (this.mAddedPixels.Count <= Master.sMaster.MinMargin) return null; this.SubtractFrom(ref Graphic); #if OUTPUTEACH System.IO.FileStream fs = Master.sMaster.GenerateFile("ImgSec " + this.mSize.X + " " + this.mSize.Y + " " + DateTime.Now.ToFileTime() + ".bmp"); Master.sMaster.OriginalPixels(mAddedPixels.ToArray()).Save(fs, System.Drawing.Imaging.ImageFormat.Bmp); fs.Flush(); fs.Close(); fs.Dispose(); #endif return this.Generate(); // // ALL DONE! One ImageSection down // }
/// <summary> /// /// </summary> /// <param name="Source"></param> internal void SubtractFrom(ref ImageSection Source) { System.Drawing.Rectangle SourceFill = new System.Drawing.Rectangle(Source.Location, Source.Size); foreach (Point p in mAddedPixels) if (SourceFill.Contains(p)) Source.Alpha.SetPixel(p.X - Source.Location.X, p.Y - Source.Location.Y, Constants.ALPHA_EMPTY); }
/// <summary> /// /// </summary> /// <param name="Base"></param> /// <returns></returns> internal ImageSection OriginalPixels(ImageSection Base) { Bitmap Clip = new Bitmap(Base.Size.Width, Base.Size.Height); Graphics.FromImage(Clip).DrawImageUnscaledAndClipped(mBaseImage, new System.Drawing.Rectangle(Base.Location, Base.Size)); return new ImageSection(Base.Location, Clip, Base.Alpha, Color.Empty); }
/// <summary> /// /// </summary> /// <param name="Count"></param> public void GenerateChildren() { //int Count = 0; #if GUIOUTPUT double Prog = 0d; ProgressDisplay Disp = new ProgressDisplay(); System.Threading.Tasks.Task t = new System.Threading.Tasks.Task(new Action(Disp.Show)); t.Start(); #endif //Reduce the image into simplicity mLeftover = (Bitmap)mBaseImage.Clone(); ImgManip Man = new ImgManip(ref mLeftover); Man.ReduceColors(mColorDetail); mScale = Man.ReduceGrid(mResDetail); mLeftover = Man.GetImage; Bitmap Clear = new Bitmap(mLeftover.Width, mLeftover.Height); Graphics.FromImage(Clear).Clear(Constants.ALPHA_FULL); ImageSection Img = new ImageSection(new Point(0, 0), mLeftover, Clear, Color.Empty); mChildren = Child.ScanImageSection(Img, mColorForgive, mColorDetail, mMinMargin); #if GUIOUTPUT Prog = 0.5d; #endif //Count += mChildren.Length; double Scale; foreach (Child c in mChildren) { Scale = Constants.GetScale(((ImageSection)c).Size, this.Size); c.GenerateSubChildren((int)(Scale * mColorForgive), (int)(Scale * mColorDetail), mMinMargin); #if GUIOUTPUT Prog += 1d / (double)mChildren.Count() * 0.5d; #endif } #if GUIOUTPUT Disp.Close(); Disp.Dispose(); t.Dispose(); #endif }
public Triangle(Child Parent, ImageSection ImgSec) : base(Parent, ImgSec, 3) { }
public Ellipse(Child Parent, ImageSection ImgSec) : base(Parent, ImgSec) { this.HardFit(); }
{ return 0d; } //NEEDS OVERRIDE using new keyword internal Child(Child Parent, ImageSection Img) { mImgSec = Img; mParent = Parent; }
/// <summary> /// 0 means absolutely no selected pixels can fit into this shape, 1 means all and only selected pixels can fit into this shape. Selection is calculated by Constants.CalcFit(Selected, Unselected) /// </summary> /// <param name="Img"></param> /// <returns></returns> protected static double ApproxFit(ImageSection Img) { return 0d; } //NEEDS OVERRIDE using new keyword
/// <summary> /// /// </summary> /// <param name="ImgSec"></param> /// <param name="ColorForgive"></param> /// <param name="ColorDetail"></param> /// <param name="MinMargin"></param> /// <returns></returns> internal static Child[] ScanImageSection(ImageSection ImgSec, int ColorForgive, int ColorDetail, int MinMargin, Child Parent = null) { #if GUIOUTPUT mScannedPx = 0; int MaxSize = ImgSec.Width * ImgSec.Height; frmImgSecDisplay DisplayOut = null; //DisplayOut.Show(); //Master.sInvokable.Invoke(Master.sAddable, null => {return new frmImgSecDisplay();}, DisplayOut); MakeForm NewDisp = delegate() { return new frmImgSecDisplay(); }; AssignForm AssDisp = delegate(System.Windows.Forms.Form ToAssign) { DisplayOut = (frmImgSecDisplay)ToAssign; }; Master.sInvokable.Invoke(Master.sAddable, new object[] { NewDisp, AssDisp }); #endif //Break the image into ImageSections List<ImageSection> Sections = new List<ImageSection>(); ImageSectionFactory Factory; for (int y = 0; y < ImgSec.Height; y++) { for (int x = 0; x < ImgSec.Width; x++) { if (ImgSec.GetPixel(x, y).A == byte.MaxValue) { Factory = new ImageSectionFactory(new Point(x, y), ColorForgive, ColorDetail #if GUIOUTPUT ,DisplayOut #endif ); Sections.Add(Factory.Recognize(ref ImgSec, x, y)); #if GUIOUTPUT UpdateDisplay((mScannedPx += Factory.SelectedPixels().Count()), MaxSize, 0); #endif } } } Sections.RemoveAll(imsec => imsec == null); //Figure out if it just selected the whole thing. If so, cancel the scan if (Sections.Count == 1 && Sections[0].Size == ImgSec.Size) return new Child[0]; //Figure out which ones of those are too small and should be discounted for (int remove = 0; remove < Sections.Count; remove++) { if (Sections[remove].PixelsUsed().Length <= MinMargin) Sections.RemoveAt(remove); } Sections.TrimExcess(); // Once that's done, turn the remaining Image Sections into Children Child[] Children = new Child[Sections.Count]; #if MULTITHREAD Task<Child>[] Tasks = new Task<Child>[Sections.Count]; #endif for (int i = 0; i < Sections.Count; i++) { #if MULTITHREAD Tasks[i] = new Task<Child>(s => Child.FromSection((Child)((object[])s)[0], (ImageSection)((object[])s)[1]), new object[]{Parent, Sections[i]}); Tasks[i].Start(); #else Children[i] = Child.FromSection(Parent, Sections[i]); #endif #if GUIOUTPUT UpdateDisplay(MaxSize, MaxSize, 1); #endif } #if MULTITHREAD try { while (Tasks.Any(t => !t.IsCompleted)) Tasks.First(t => !t.IsCompleted).Wait(); } catch (NullReferenceException) { } for (int i = 0; i < Tasks.Length; i++) { Children[i] = Tasks[i].Result; } #endif return Children; }
/// <summary> /// /// </summary> /// <param name="Parent"></param> /// <param name="ImgSec"></param> /// <returns></returns> internal static Child FromSection(Child Parent, ImageSection ImgSec) { Func<ImageSection, double> BestFitter = null; double BestFit = 0d; double temp; sRegisteredChecks.ForEach(f => { if (BestFit < (temp = f(ImgSec))) { BestFitter = f; BestFit = temp; } }); if (BestFit == 0d) throw new InvalidOperationException("No shapes are registered, or they are not matching any pixels."); return sRegisteredConstructs[sRegisteredChecks.IndexOf(BestFitter)](Parent, ImgSec); }