Advertisement
Bunny83

Image Shape detector

Jan 16th, 2017
894
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 9.95 KB | None | 0 0
  1. using UnityEngine;
  2. using System.Collections.Generic;
  3.  
  4. namespace B83.UA.ImageTools
  5. {
  6.     public class DetectImageShapes : MonoBehaviour
  7.     {
  8.         public Texture2D texture;
  9.         List<List<Vector2i>> shapes = null;
  10.         Material m;
  11.         void Start()
  12.         {
  13.             shapes = FindShapes(texture, true); // true--> black on white | false --> white on black
  14.             m = new Material(Shader.Find("Particles/Alpha Blended Premultiply"));
  15.             Debug.Log("shapeCount: " + shapes.Count);
  16.         }
  17.         void OnRenderObject()
  18.         {
  19.             m.SetPass(0);
  20.             GL.modelview = Camera.main.worldToCameraMatrix * transform.localToWorldMatrix;
  21.             GL.Begin(GL.LINES);
  22.             UnityEngine.Random.seed = 0;
  23.             for(int i = 0; i < shapes.Count; i++)
  24.             {
  25.                 var shape = shapes[i];
  26.                 GL.Color(UnityEngine.Random.ColorHSV(0,1,1,1,1,1,1,1));
  27.                 Vector2 last = (Vector2)shape[shape.Count - 1];
  28.                 for(int n = 0; n < shape.Count; n++)
  29.                 {
  30.                     Vector2 p = (Vector2)shape[n];
  31.                     GL.Vertex(last);
  32.                     GL.Vertex(p);
  33.                     last = p;
  34.                 }
  35.             }
  36.             GL.End();
  37.         }
  38.  
  39.  
  40.         public static List<List<Vector2i>> FindShapes(Texture2D aTex, bool aInvertColors)
  41.         {
  42.             int width = aTex.width;
  43.             byte[] data = CreateBWData(aTex, 0.5f, aInvertColors);
  44.             var res = new List<List<Vector2i>>();
  45.             int maxCount = 20000; // safety switch
  46.             for (int start = FindShapeStart(data, 0); start != -1 && maxCount > 0; start = FindShapeStart(data, start+1), --maxCount)
  47.             {
  48.                 var shape = GetShapePoints(data, start, width);
  49.                 res.Add(shape);
  50.                 RemoveShape(data, width, shape);
  51.             }
  52.             return res;
  53.         }
  54.         // converts the pixel data into a pure data array
  55.         static byte[] CreateBWData(Texture2D aTexture, float aBWThreshold, bool aInvertColors)
  56.         {
  57.             var tmp = aTexture.GetPixels();
  58.             int count = tmp.Length;
  59.             byte[] data = new byte[count];
  60.             for (int i = 0; i < count; i++)
  61.                 data[i] = (byte)(((tmp[i].grayscale > aBWThreshold) ^ aInvertColors) ? 1 : 0);
  62.             return data;
  63.         }
  64.         // This simply iterates the image row by row from the bottom to find the next "filled" point
  65.         static int FindShapeStart(byte[] aData, int aStartingIndex)
  66.         {
  67.             int count = aData.Length;
  68.             for (int i = aStartingIndex; i < count; i++)
  69.                 if (aData[i] > 0)
  70.                     return i;
  71.             return -1;
  72.         }
  73.         // this is just a helper that translates a point into an index and checks if the point is "filled".
  74.         static bool TestPoint(byte[] aData, int aWidth, Vector2i aPoint)
  75.         {
  76.             int index = aPoint.x + aPoint.y * aWidth;
  77.             if (index > 0 && index < aData.Length)
  78.                 return aData[index] > 0;
  79.             return false;
  80.         }
  81.        
  82.         static List<Vector2i> GetShapePoints(byte[] aData, int aIndex, int aWidth)
  83.         {
  84.             List<Vector2i> res = new List<Vector2i>();
  85.             HashSet<int> pointCache = new HashSet<int>();
  86.             Vector2i dir = new Vector2i(1,0);
  87.             Vector2i start = new Vector2i(aIndex % aWidth, aIndex / aWidth);
  88.             Vector2i p = start;
  89.             res.Add(p);
  90.             bool done = false;
  91.             int maxPoints = 40000;
  92.             while (!done && --maxPoints > 0)
  93.             {
  94.                 var d = dir.right;
  95.                 for(int i = 0; i < 7; i++)
  96.                 {
  97.                     var current = p + d;
  98.                     int index = current.x + current.y * aWidth;
  99.                     // If we are back to the start or if we reached a point we already included
  100.                     if (current == start || pointCache.Contains(index))
  101.                     {
  102.                         done = true;
  103.                         break;
  104.                     }
  105.                     if (TestPoint(aData, aWidth, current))
  106.                     {
  107.                         // valid point, so move on to that point and update dir
  108.                         // so we know where we were coming from
  109.                         p = current;
  110.                         dir = d;
  111.                         res.Add(current);
  112.                         pointCache.Add(index);
  113.                         break;
  114.                     }
  115.                     // rotate 45° each iteration
  116.                     d = d.left45;
  117.                 }
  118.             }
  119.             return res;
  120.         }
  121.         // removes the pixels of a shape from the data array
  122.         static void RemoveShape(byte[] aData, int aWidth, List<Vector2i> aPoints)
  123.         {
  124.             int count = aPoints.Count;
  125.             var one = new Vector2i(1, 1);
  126.             // image resolution constraints.
  127.             var min = new Vector2i(0, 0);
  128.             var max = new Vector2i(aWidth - 1, aData.Length / aWidth - 1);
  129.             for (int i = 0; i < count; i++)
  130.             {
  131.                 // construct valid bottom-left and top-right coordinates
  132.                 Vector2i s = Vector2i.Clamp(aPoints[i] - one, min, max);
  133.                 Vector2i e = Vector2i.Clamp(aPoints[i] + one, min, max);
  134.                 // remove the whole 3x3 area around each point
  135.                 for(int y = s.y; y <= e.y; y++)
  136.                 {
  137.                     for (int x = s.x; x <= e.x; x++)
  138.                         aData[x + y * aWidth] = 0;
  139.                 }
  140.             }
  141.         }
  142.     }
  143.  
  144.  
  145.     public struct Vector2i
  146.     {
  147.         public int x;
  148.         public int y;
  149.         public Vector2i(int aX, int aY)
  150.         {
  151.             x = aX; y = aY;
  152.         }
  153.  
  154.         public static Vector2i operator +(Vector2i aV1, Vector2i aV2)
  155.         {
  156.             return new Vector2i(aV1.x + aV2.x, aV1.y + aV2.y);
  157.         }
  158.         public static Vector2i operator -(Vector2i aV1, Vector2i aV2)
  159.         {
  160.             return new Vector2i(aV1.x - aV2.x, aV1.y - aV2.y);
  161.         }
  162.         public static Vector2i operator +(Vector2i aV1, int aScalar)
  163.         {
  164.             return new Vector2i(aV1.x + aScalar, aV1.y + aScalar);
  165.         }
  166.         public static Vector2i operator +(int aScalar, Vector2i aV1)
  167.         {
  168.             return new Vector2i(aV1.x + aScalar, aV1.y + aScalar);
  169.         }
  170.         public static Vector2i operator -(Vector2i aV1, int aScalar)
  171.         {
  172.             return new Vector2i(aV1.x - aScalar, aV1.y - aScalar);
  173.         }
  174.         public static Vector2i operator -(int aScalar, Vector2i aV1)
  175.         {
  176.             return new Vector2i(-aV1.x + aScalar, -aV1.y + aScalar);
  177.         }
  178.         public static Vector2i operator -(Vector2i aV1)
  179.         {
  180.             return new Vector2i(-aV1.x, -aV1.y);
  181.         }
  182.         public static Vector2i operator *(Vector2i aV1, int aScalar)
  183.         {
  184.             return new Vector2i(aV1.x * aScalar, aV1.y * aScalar);
  185.         }
  186.         public static Vector2i operator *(int aScalar, Vector2i aV1)
  187.         {
  188.             return new Vector2i(aV1.x * aScalar, aV1.y * aScalar);
  189.         }
  190.         public static Vector2i operator /(Vector2i aV1, int aScalar)
  191.         {
  192.             return new Vector2i(aV1.x / aScalar, aV1.y / aScalar);
  193.         }
  194.         public static implicit operator Vector2(Vector2i aV1)
  195.         {
  196.             return new Vector2(aV1.x, aV1.y);
  197.         }
  198.         public static explicit operator Vector2i(Vector2 aV1)
  199.         {
  200.             return new Vector2i(Mathf.RoundToInt(aV1.x), Mathf.RoundToInt(aV1.y));
  201.         }
  202.         public static bool operator ==(Vector2i aV1, Vector2i aV2)
  203.         {
  204.             return aV1.x == aV2.x && aV1.y == aV2.y;
  205.         }
  206.         public static bool operator !=(Vector2i aV1, Vector2i aV2)
  207.         {
  208.             return aV1.x != aV2.x || aV1.y != aV2.y;
  209.         }
  210.         public override bool Equals(object obj)
  211.         {
  212.             if (obj is Vector2i)
  213.                 return this == (Vector2i)obj;
  214.             return false;
  215.         }
  216.         public override int GetHashCode()
  217.         {
  218.             return (x.GetHashCode()+y).GetHashCode();
  219.         }
  220.         public override string ToString()
  221.         {
  222.             return string.Format("({0},{1})", x, y);
  223.         }
  224.  
  225.         // This only reduces the vector uniformly by the largest component
  226.         // So (2,2) --> (1,1) but (2,1) --> (1,0)
  227.         public Vector2i normalized
  228.         {
  229.             get
  230.             {
  231.                 int len = Mathf.Max(Mathf.Abs(x), Mathf.Abs(y));
  232.                 return this / len;
  233.             }
  234.         }
  235.  
  236.         public Vector2i right
  237.         {
  238.             get { return new Vector2i(y, -x); }
  239.         }
  240.         public Vector2i left
  241.         {
  242.             get { return new Vector2i(-y, x); }
  243.         }
  244.         public Vector2i right45
  245.         {
  246.             // (this + right).normalized
  247.             get { return new Vector2i(x + y, y - x).normalized; }
  248.         }
  249.         public Vector2i left45
  250.         {
  251.             // (this + left).normalized
  252.             get { return new Vector2i(x - y, y + x).normalized; }
  253.         }
  254.         // performs a component-wise min operation
  255.         public static Vector2i Min(Vector2i aV1, Vector2i aV2)
  256.         {
  257.             return new Vector2i(Mathf.Min(aV1.x, aV2.x), Mathf.Min(aV1.y, aV2.y));
  258.         }
  259.         // performs a component-wise max operation
  260.         public static Vector2i Max(Vector2i aV1, Vector2i aV2)
  261.         {
  262.             return new Vector2i(Mathf.Max(aV1.x, aV2.x), Mathf.Max(aV1.y, aV2.y));
  263.         }
  264.         // Keep the vector inside the box defined by min and max
  265.         public static Vector2i Clamp(Vector2i aVec, Vector2i aMin, Vector2i aMax)
  266.         {
  267.             aVec.x = Mathf.Clamp(aVec.x, aMin.x, aMax.x);
  268.             aVec.y = Mathf.Clamp(aVec.y, aMin.y, aMax.y);
  269.             return aVec;
  270.         }
  271.     }
  272. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement