Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- using UnityEngine;
- using System.Collections.Generic;
- namespace B83.UA.ImageTools
- {
- public class DetectImageShapes : MonoBehaviour
- {
- public Texture2D texture;
- List<List<Vector2i>> shapes = null;
- Material m;
- void Start()
- {
- shapes = FindShapes(texture, true); // true--> black on white | false --> white on black
- m = new Material(Shader.Find("Particles/Alpha Blended Premultiply"));
- Debug.Log("shapeCount: " + shapes.Count);
- }
- void OnRenderObject()
- {
- m.SetPass(0);
- GL.modelview = Camera.main.worldToCameraMatrix * transform.localToWorldMatrix;
- GL.Begin(GL.LINES);
- UnityEngine.Random.seed = 0;
- for(int i = 0; i < shapes.Count; i++)
- {
- var shape = shapes[i];
- GL.Color(UnityEngine.Random.ColorHSV(0,1,1,1,1,1,1,1));
- Vector2 last = (Vector2)shape[shape.Count - 1];
- for(int n = 0; n < shape.Count; n++)
- {
- Vector2 p = (Vector2)shape[n];
- GL.Vertex(last);
- GL.Vertex(p);
- last = p;
- }
- }
- GL.End();
- }
- public static List<List<Vector2i>> FindShapes(Texture2D aTex, bool aInvertColors)
- {
- int width = aTex.width;
- byte[] data = CreateBWData(aTex, 0.5f, aInvertColors);
- var res = new List<List<Vector2i>>();
- int maxCount = 20000; // safety switch
- for (int start = FindShapeStart(data, 0); start != -1 && maxCount > 0; start = FindShapeStart(data, start+1), --maxCount)
- {
- var shape = GetShapePoints(data, start, width);
- res.Add(shape);
- RemoveShape(data, width, shape);
- }
- return res;
- }
- // converts the pixel data into a pure data array
- static byte[] CreateBWData(Texture2D aTexture, float aBWThreshold, bool aInvertColors)
- {
- var tmp = aTexture.GetPixels();
- int count = tmp.Length;
- byte[] data = new byte[count];
- for (int i = 0; i < count; i++)
- data[i] = (byte)(((tmp[i].grayscale > aBWThreshold) ^ aInvertColors) ? 1 : 0);
- return data;
- }
- // This simply iterates the image row by row from the bottom to find the next "filled" point
- static int FindShapeStart(byte[] aData, int aStartingIndex)
- {
- int count = aData.Length;
- for (int i = aStartingIndex; i < count; i++)
- if (aData[i] > 0)
- return i;
- return -1;
- }
- // this is just a helper that translates a point into an index and checks if the point is "filled".
- static bool TestPoint(byte[] aData, int aWidth, Vector2i aPoint)
- {
- int index = aPoint.x + aPoint.y * aWidth;
- if (index > 0 && index < aData.Length)
- return aData[index] > 0;
- return false;
- }
- static List<Vector2i> GetShapePoints(byte[] aData, int aIndex, int aWidth)
- {
- List<Vector2i> res = new List<Vector2i>();
- HashSet<int> pointCache = new HashSet<int>();
- Vector2i dir = new Vector2i(1,0);
- Vector2i start = new Vector2i(aIndex % aWidth, aIndex / aWidth);
- Vector2i p = start;
- res.Add(p);
- bool done = false;
- int maxPoints = 40000;
- while (!done && --maxPoints > 0)
- {
- var d = dir.right;
- for(int i = 0; i < 7; i++)
- {
- var current = p + d;
- int index = current.x + current.y * aWidth;
- // If we are back to the start or if we reached a point we already included
- if (current == start || pointCache.Contains(index))
- {
- done = true;
- break;
- }
- if (TestPoint(aData, aWidth, current))
- {
- // valid point, so move on to that point and update dir
- // so we know where we were coming from
- p = current;
- dir = d;
- res.Add(current);
- pointCache.Add(index);
- break;
- }
- // rotate 45° each iteration
- d = d.left45;
- }
- }
- return res;
- }
- // removes the pixels of a shape from the data array
- static void RemoveShape(byte[] aData, int aWidth, List<Vector2i> aPoints)
- {
- int count = aPoints.Count;
- var one = new Vector2i(1, 1);
- // image resolution constraints.
- var min = new Vector2i(0, 0);
- var max = new Vector2i(aWidth - 1, aData.Length / aWidth - 1);
- for (int i = 0; i < count; i++)
- {
- // construct valid bottom-left and top-right coordinates
- Vector2i s = Vector2i.Clamp(aPoints[i] - one, min, max);
- Vector2i e = Vector2i.Clamp(aPoints[i] + one, min, max);
- // remove the whole 3x3 area around each point
- for(int y = s.y; y <= e.y; y++)
- {
- for (int x = s.x; x <= e.x; x++)
- aData[x + y * aWidth] = 0;
- }
- }
- }
- }
- public struct Vector2i
- {
- public int x;
- public int y;
- public Vector2i(int aX, int aY)
- {
- x = aX; y = aY;
- }
- public static Vector2i operator +(Vector2i aV1, Vector2i aV2)
- {
- return new Vector2i(aV1.x + aV2.x, aV1.y + aV2.y);
- }
- public static Vector2i operator -(Vector2i aV1, Vector2i aV2)
- {
- return new Vector2i(aV1.x - aV2.x, aV1.y - aV2.y);
- }
- public static Vector2i operator +(Vector2i aV1, int aScalar)
- {
- return new Vector2i(aV1.x + aScalar, aV1.y + aScalar);
- }
- public static Vector2i operator +(int aScalar, Vector2i aV1)
- {
- return new Vector2i(aV1.x + aScalar, aV1.y + aScalar);
- }
- public static Vector2i operator -(Vector2i aV1, int aScalar)
- {
- return new Vector2i(aV1.x - aScalar, aV1.y - aScalar);
- }
- public static Vector2i operator -(int aScalar, Vector2i aV1)
- {
- return new Vector2i(-aV1.x + aScalar, -aV1.y + aScalar);
- }
- public static Vector2i operator -(Vector2i aV1)
- {
- return new Vector2i(-aV1.x, -aV1.y);
- }
- public static Vector2i operator *(Vector2i aV1, int aScalar)
- {
- return new Vector2i(aV1.x * aScalar, aV1.y * aScalar);
- }
- public static Vector2i operator *(int aScalar, Vector2i aV1)
- {
- return new Vector2i(aV1.x * aScalar, aV1.y * aScalar);
- }
- public static Vector2i operator /(Vector2i aV1, int aScalar)
- {
- return new Vector2i(aV1.x / aScalar, aV1.y / aScalar);
- }
- public static implicit operator Vector2(Vector2i aV1)
- {
- return new Vector2(aV1.x, aV1.y);
- }
- public static explicit operator Vector2i(Vector2 aV1)
- {
- return new Vector2i(Mathf.RoundToInt(aV1.x), Mathf.RoundToInt(aV1.y));
- }
- public static bool operator ==(Vector2i aV1, Vector2i aV2)
- {
- return aV1.x == aV2.x && aV1.y == aV2.y;
- }
- public static bool operator !=(Vector2i aV1, Vector2i aV2)
- {
- return aV1.x != aV2.x || aV1.y != aV2.y;
- }
- public override bool Equals(object obj)
- {
- if (obj is Vector2i)
- return this == (Vector2i)obj;
- return false;
- }
- public override int GetHashCode()
- {
- return (x.GetHashCode()+y).GetHashCode();
- }
- public override string ToString()
- {
- return string.Format("({0},{1})", x, y);
- }
- // This only reduces the vector uniformly by the largest component
- // So (2,2) --> (1,1) but (2,1) --> (1,0)
- public Vector2i normalized
- {
- get
- {
- int len = Mathf.Max(Mathf.Abs(x), Mathf.Abs(y));
- return this / len;
- }
- }
- public Vector2i right
- {
- get { return new Vector2i(y, -x); }
- }
- public Vector2i left
- {
- get { return new Vector2i(-y, x); }
- }
- public Vector2i right45
- {
- // (this + right).normalized
- get { return new Vector2i(x + y, y - x).normalized; }
- }
- public Vector2i left45
- {
- // (this + left).normalized
- get { return new Vector2i(x - y, y + x).normalized; }
- }
- // performs a component-wise min operation
- public static Vector2i Min(Vector2i aV1, Vector2i aV2)
- {
- return new Vector2i(Mathf.Min(aV1.x, aV2.x), Mathf.Min(aV1.y, aV2.y));
- }
- // performs a component-wise max operation
- public static Vector2i Max(Vector2i aV1, Vector2i aV2)
- {
- return new Vector2i(Mathf.Max(aV1.x, aV2.x), Mathf.Max(aV1.y, aV2.y));
- }
- // Keep the vector inside the box defined by min and max
- public static Vector2i Clamp(Vector2i aVec, Vector2i aMin, Vector2i aMax)
- {
- aVec.x = Mathf.Clamp(aVec.x, aMin.x, aMax.x);
- aVec.y = Mathf.Clamp(aVec.y, aMin.y, aMax.y);
- return aVec;
- }
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement