using System.Collections.Generic;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Xml.Serialization;
using Color = zenthion.Color;
using Point = zenthion.Point;
public static void Main()
string url = "https://pastebin.com/raw/29MQJ7LT",
dataUrl = "https://pastebin.com/raw/XC3RrV1V";
using (var webClient = new WebClient()) {
imageStr = webClient.DownloadString(url);
pointsStr = webClient.DownloadString(dataUrl);
pointsStr = pointsStr.Base64Decode().Replace(@" encoding=""utf-16""", "");
Point[] points = pointsStr.Deserialize<HashSet<Point>>().ToArray();
int width = imageStr.IndexOf(Environment.NewLine);
imageStr = imageStr.Replace(Environment.NewLine, "");
int height = imageStr.Length / width;
Stopwatch sw = Stopwatch.StartNew();
string source = (string)imageStr.Clone(),
target = (string)imageStr.Clone();
IterateBuild(points, source, ref target, width);
Print(target.ToCharArray(), (c) => c.ToString(), width);
Console.WriteLine("Ellapsed: {0} s", (sw.ElapsedMilliseconds / 1000f).ToString("F2"));
public static void IterateBuild(Point[] points, string source, ref string target, int width, bool drawEdges = false)
HashSet<Point> edges = new HashSet<Point>();
foreach(Point p in points)
target = target.ReplaceAt(F.P(p.x, p.y, width, 0), '2');
Point[] corners = new Point[] { Point.upperLeft, Point.upperRight, Point.downLeft, Point.downRight },
sides = new Point[] { Point.left, Point.up, Point.right, Point.down };
Point actualCorner = points[points.Length - 1],
lastPoint = points[points.Length - 1];
Dictionary<Point, float> slopes = new Dictionary<Point, float>();
for (int i = 0; i < points.Length - 2; ++i)
target = target.ReplaceAt(F.P(p.x, p.y, width, 0), '□');
if(sides.Contains(p - lastPoint))
float actSlope = p.GetSlope(actualCorner);
mean = mean.AddToMean(actSlope, ref meanElements);
target = target.ReplaceAt(F.P(p.x, p.y, width, 0), '■');
Console.WriteLine("I: {0}", i);
Console.WriteLine("Actual Slope: {0}", actSlope);
Console.WriteLine("Mean: {0}", mean);
target = target.ReplaceAt(F.P(p.x, p.y, width, 0), '✖');
private static void Print<T>(T[] objs, Func<T, string> result, int splitEvery = 10)
Console.Write(result == null ? (object)item : (object)result(item));
private static string ColorString(Color color)
if (color == Color.black)
else if (color == Color.white)
else if (color == Color.blue)
else if (color == Color.green)
public enum InnerDirection
private const int dirLength = 4;
public static InnerDirection GetPerpendicular(InnerDirection dir, bool inverse = false)
return GetDir(dir, inverse ? -1 : 1);
public static InnerDirection GetInverse(InnerDirection dir, bool inverse = false)
return GetDir(dir, inverse ? -2 : 2);
private static InnerDirection GetDir(InnerDirection dir, int n)
return (InnerDirection)v;
private static int BmpStride = 0;
public static IEnumerable<Color> ToColor(this Bitmap bmp)
Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
BitmapData bmpData = bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite, bmp.PixelFormat);
IntPtr ptr = bmpData.Scan0;
int bytes = bmpData.Stride * bmp.Height;
byte[] rgbValues = new byte[bytes];
Marshal.Copy(ptr, rgbValues, 0, bytes);
BmpStride = bmpData.Stride;
for (int column = 0; column < bmpData.Height; column++)
for (int row = 0; row < bmpData.Width; row++)
byte b = (byte)(rgbValues[(column * BmpStride) + (row * 4)]);
byte g = (byte)(rgbValues[(column * BmpStride) + (row * 4) + 1]);
byte r = (byte)(rgbValues[(column * BmpStride) + (row * 4) + 2]);
yield return new Color(r, g, b, 255);
public static void SaveBitmap(this Color[] bmp, int width, int height, string path)
byte[] rgbValues = new byte[BmpStride * height];
for (int column = 0; column < height; column++)
for (int row = 0; row < width; row++)
int i = Pn(row, column, width);
rgbValues[(column * BmpStride) + (row * 4)] = bmp[i].b;
rgbValues[(column * BmpStride) + (row * 4) + 1] = bmp[i].g;
rgbValues[(column * BmpStride) + (row * 4) + 2] = bmp[i].r;
rgbValues[(column * BmpStride) + (row * 4) + 3] = bmp[i].a;
using (Bitmap image = new Bitmap(width, height, width * 4, PixelFormat.Format32bppArgb, Marshal.UnsafeAddrOfPinnedArrayElement(rgbValues, 0)))
public static int Pn(int x, int y, int w)
public static int P(int x, int y, int w, int h)
public static string ReplaceAt(this string value, int index, char newchar)
if (value.Length <= index)
return string.Concat(value.Select((c, i) => i == index ? newchar : c));
public static float GetSlope(this Point p1, Point p2)
return (float)(p2.y - p1.y) / (p2.x - p1.x);
public static string Base64Encode(this string plainText)
var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText);
return System.Convert.ToBase64String(plainTextBytes);
public static string Base64Decode(this string base64EncodedData)
var base64EncodedBytes = System.Convert.FromBase64String(base64EncodedData);
return System.Text.Encoding.UTF8.GetString(base64EncodedBytes);
public static float AddToMean(this float mean, float newEl, ref int curElements)
return (mean + newEl) / (++curElements);
public static class Mathf
public static T Clamp<T>(this T val, T min, T max)where T : IComparable<T>
if (val.CompareTo(min) < 0)
else if (val.CompareTo(max) > 0)
public static float Lerp(float a, float b, float t)
return a + (b - a) * Clamp01(t);
public static float Clamp01(float value)
public struct Color : ICloneable
return MemberwiseClone();
public static Color white
return new Color(255, 255, 255);
return new Color(255, 0, 0);
public static Color green
return new Color(0, 255, 0);
return new Color(0, 0, 255);
public static Color yellow
return new Color(255, 255, 0);
return new Color(128, 128, 128);
public static Color black
return new Color(0, 0, 0);
public static Color transparent
return new Color(0, 0, 0, 0);
public Color(byte r, byte g, byte b)
public Color(byte r, byte g, byte b, byte a)
public static bool operator ==(Color c1, Color c2)
return c1.r == c2.r && c1.g == c2.g && c1.b == c2.b && c1.a == c2.a;
public static bool operator !=(Color c1, Color c2)
return !(c1.r == c2.r && c1.g == c2.g && c1.b == c2.b && c1.a == c2.a);
public override int GetHashCode()
return base.GetHashCode();
public override bool Equals(object obj)
return r == c.r && g == c.g && b == c.b;
public static Color operator -(Color c1, Color c2)
return new Color((byte)Mathf.Clamp(c1.r - c2.r, 0, 255), (byte)Mathf.Clamp(c2.g - c2.g, 0, 255), (byte)Mathf.Clamp(c2.b - c2.b, 0, 255));
public static Color operator +(Color c1, Color c2)
return new Color((byte)Mathf.Clamp(c1.r + c2.r, 0, 255), (byte)Mathf.Clamp(c2.g + c2.g, 0, 255), (byte)Mathf.Clamp(c2.b + c2.b, 0, 255));
public Color Lerp(Color c2, float t)
return new Color((byte)Mathf.Lerp(r, c2.r, t), (byte)Mathf.Lerp(g, c2.g, t), (byte)Mathf.Lerp(b, c2.b, t));
return new Color((byte)Mathf.Clamp(byte.MaxValue - r, 0, 255), (byte)Mathf.Clamp(byte.MaxValue - g, 0, 255), (byte)Mathf.Clamp(byte.MaxValue - b, 0, 255));
public override string ToString()
else if (this == transparent)
return string.Format("({0}, {1}, {2}, {3})", r, g, b, a);
public static IEnumerable<Color> Fill(int x, int y)
for (int i = 0; i < x * y; ++i)
public struct Point : ICloneable
public static Point right
public static Point upperLeft
public static Point upperRight
public static Point downLeft
return new Point(-1, -1);
public static Point downRight
public float sqrMagnitude
return (float)Math.Sqrt(sqrMagnitude);
return new Point((int)Mathf.Clamp01(x), (int)Mathf.Clamp01(y));
public Point(int x, int y)
public static Point operator +(Point a, Point b)
return new Point(a.x + b.x, a.y + b.y);
public static Point operator -(Point a, Point b)
return new Point(a.x - b.x, a.y - b.y);
public static Point operator *(Point a, Point b)
return new Point(a.x * b.x, a.y * b.y);
public static Point operator /(Point a, Point b)
return new Point(a.x / b.x, a.y / b.y);
public static Point operator *(Point a, int b)
return new Point(a.x * b, a.y * b);
public static Point operator /(Point a, int b)
return new Point(a.x / b, a.y / b);
public override string ToString()
return string.Format("({0}, {1})", x, y);
public static bool operator ==(Point p1, Point p2)
return p1.x == p2.x && p1.y == p2.y;
public static bool operator !=(Point p1, Point p2)
return p1.x != p2.x || p1.y != p2.y;
public override bool Equals(object obj)
return x == p.x && y == p.y;
public override int GetHashCode()
return base.GetHashCode();
else if (this == upperRight)
else if (this == downLeft)
else if (this == downRight)
public Point GetInnerPoint(InnerDirection dir)
case InnerDirection.right:
case InnerDirection.down:
case InnerDirection.left:
public Point NewInnerPos(InnerDirection dir)
return this + GetInnerPoint(dir);
public Point NextDirection(ref InnerDirection lastDirection)
lastDirection = InnerDirs.GetPerpendicular(lastDirection);
return GetInnerPoint(lastDirection);
return MemberwiseClone();
public static class SerializerHelper
public static string Serialize<T>(this T data, SerializeFormat format = SerializeFormat.JSON)
case SerializeFormat.XML:
return SerializeToXml(data);
case SerializeFormat.JSON:
return SerializeToJson(data);
public static T Deserialize<T>(this string data, SerializeFormat format = SerializeFormat.JSON)
case SerializeFormat.XML:
return DeserializeToXml<T>(data);
case SerializeFormat.JSON:
return DeserializeFromJson<T>(data);
private static string SerializeToXml<T>(T data)
var xmlSerializer = new XmlSerializer(typeof(T));
using (var stringWriter = new StringWriter())
xmlSerializer.Serialize(stringWriter, data);
return stringWriter.ToString();
private static T DeserializeToXml<T>(string data)
XmlRootAttribute xRoot = new XmlRootAttribute();
xRoot.ElementName = "ArrayOfPoint";
var xmlSerializer = new XmlSerializer(data.GetType(), xRoot);
using (var stream = GenerateStreamFromString(data))
var result = xmlSerializer.Deserialize(stream);
private static Stream GenerateStreamFromString(string s)
var stream = new MemoryStream();
var writer = new StreamWriter(stream, System.Text.Encoding.UTF8);
private static string SerializeToJson<T>(T data)
return JsonConvert.SerializeObject(data);
private static T DeserializeFromJson<T>(string data)
return JsonConvert.DeserializeObject<T>(data);
public enum SerializeFormat