<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wikidot="http://www.wikidot.com/rss-namespace">

	<channel>
		<title>Unity C# version</title>
		<link>http://contourtextures.wikidot.com/forum/t-442136/unity-c-version</link>
		<description>Posts in the discussion thread &quot;Unity C# version&quot; - I made a version for Unity in C#.</description>
				<copyright></copyright>
		<lastBuildDate>Sun, 16 Aug 2026 22:33:56 +0000</lastBuildDate>
		
					<item>
				<guid>http://contourtextures.wikidot.com/forum/t-442136#post-1374505</guid>
				<title>Re: Unity C# version</title>
				<link>http://contourtextures.wikidot.com/forum/t-442136/unity-c-version#post-1374505</link>
				<description></description>
				<pubDate>Wed, 15 Feb 2012 12:56:44 +0000</pubDate>
				<wikidot:authorName>Jasper Flick</wikidot:authorName>				<wikidot:authorUserId>1304472</wikidot:authorUserId>				<content:encoded>
					<![CDATA[
						 <p>Glad you appreciate it!</p> <p>I have made a unity package for it. It contains the above code, an editor class that makes an editor window for it, and the GNU GPL. You can find it, along with documentation, over at catlikecoding.com/unity/products/distance-map-generator/</p> 
				 	]]>
				</content:encoded>							</item>
					<item>
				<guid>http://contourtextures.wikidot.com/forum/t-442136#post-1372887</guid>
				<title>Re: Unity C# version</title>
				<link>http://contourtextures.wikidot.com/forum/t-442136/unity-c-version#post-1372887</link>
				<description></description>
				<pubDate>Mon, 13 Feb 2012 08:11:43 +0000</pubDate>
				<wikidot:authorName>stegu</wikidot:authorName>				<wikidot:authorUserId>30391</wikidot:authorUserId>				<content:encoded>
					<![CDATA[
						 <p>Thank you very much! It's nice to see my code being used<br /> and tweaked, and to see contour rendering catching on!</p> 
				 	]]>
				</content:encoded>							</item>
					<item>
				<guid>http://contourtextures.wikidot.com/forum/t-442136#post-1371186</guid>
				<title>Unity C# version</title>
				<link>http://contourtextures.wikidot.com/forum/t-442136/unity-c-version#post-1371186</link>
				<description></description>
				<pubDate>Fri, 10 Feb 2012 17:15:54 +0000</pubDate>
				<wikidot:authorName>Jasper Flick</wikidot:authorName>				<wikidot:authorUserId>1304472</wikidot:authorUserId>				<content:encoded>
					<![CDATA[
						 <p>Hi!</p> <p>I'm working on a text tool for the Unity3D game engine and I included a C# version of the EDTAA algorithm to create nice distance maps from font atlases. The Generate method takes the alpha channel of a source texture and generates the distances from that, either outside, inside, or both. Beyond that, it's conceptually the same code as edtaa3 and the post process part, although I approached a few things differently.</p> <p>Though it's Unity3D specific, it shouldn't be too hard to integrate the C# code into a non-Unity project.</p> <div class="code"> <pre><code>using UnityEngine; /// &lt;summary&gt; /// Utility class for generating distance maps from anti-aliased alpha maps. /// &lt;/summary&gt; public static class CCDistanceMapGenerator { /// &lt;summary&gt; /// How to fill the RGB channels of the generated distance map /// &lt;/summary&gt; public enum RGBMode { /// &lt;summary&gt; /// Set the RGB channels to 1. /// &lt;/summary&gt; White, /// &lt;summary&gt; /// Set the RGB channels to 0. /// &lt;/summary&gt; Black, /// &lt;summary&gt; /// Set the RGB channels to the computed distance. /// &lt;/summary&gt; Distance, /// &lt;summary&gt; /// Copy the source texture's RGB channels. /// &lt;/summary&gt; Source } private class Pixel { public float alpha, distance; public Vector2 gradient; public int dX, dY; } private static int width, height; private static Pixel[,] pixels; /// &lt;summary&gt; /// Generates a distance texture from the alpha channel of a source texture. /// &lt;/summary&gt; /// &lt;param name=&quot;source&quot;&gt; /// The source texture. Alpha values of 1 are considered inside, values of 0 are considered outside, and any other values are considered /// to be on the edge. Make sure the texture is readable and not compressed. /// &lt;/param&gt; /// &lt;param name=&quot;destination&quot;&gt; /// The destination texture. Must be the same size as the source texture. /// &lt;/param&gt; /// &lt;param name=&quot;maxInside&quot;&gt; /// The maximum pixel distance measured inside the boundary, resulting in an alpha value of 1. /// If set to zero, everything inside will have an alpha value of 1. /// &lt;/param&gt; /// &lt;param name=&quot;maxOutside&quot;&gt; /// The maximum pixel distance measured outside the boundary, resulting in an alpha value of 0. /// If set to zero, everything outside will have an alpha value of 0. /// &lt;/param&gt; /// &lt;param name=&quot;postProcessDistance&quot;&gt; /// Pixel distance from the boundary which will be post-processed using the boundary gradient. /// &lt;/param&gt; /// &lt;param name=&quot;rgbMode&quot;&gt; /// How to fill the destination texture's RGB channels. /// &lt;/param&gt; public static void Generate (Texture2D source, Texture2D destination, float maxInside, float maxOutside, float postProcessDistance, RGBMode rgbMode) { if(source.height != destination.height || source.width != destination.width){ Debug.LogError(&quot;Source and destination textures must be the same size.&quot;); return; } try{ source.GetPixel(0, 0); } catch{ Debug.LogError(&quot;Source texture is not read/write enabled.&quot;); return; } width = source.width; height = source.height; pixels = new Pixel[width, height]; int x, y; float scale; Color c = rgbMode == RGBMode.White ? Color.white : Color.black; for(y = 0; y &lt; height; y++){ for(x = 0; x &lt; width; x++){ pixels[x, y] = new Pixel(); } } if(maxInside &gt; 0f){ for(y = 0; y &lt; height; y++){ for(x = 0; x &lt; width; x++){ pixels[x, y].alpha = 1f - source.GetPixel(x, y).a; } } ComputeEdgeGradients(); GenerateDistanceTransform(); if(postProcessDistance &gt; 0f){ PostProcess(postProcessDistance); } scale = 1f / maxInside; for(y = 0; y &lt; height; y++){ for(x = 0; x &lt; width; x++){ c.a = Mathf.Clamp01(pixels[x, y].distance * scale); destination.SetPixel(x, y, c); } } } if(maxOutside &gt; 0f){ for(y = 0; y &lt; height; y++){ for(x = 0; x &lt; width; x++){ pixels[x, y].alpha = source.GetPixel(x, y).a; } } ComputeEdgeGradients(); GenerateDistanceTransform(); if(postProcessDistance &gt; 0f){ PostProcess(postProcessDistance); } scale = 1f / maxOutside; if(maxInside &gt; 0f){ for(y = 0; y &lt; height; y++){ for(x = 0; x &lt; width; x++){ c.a = 0.5f + (destination.GetPixel(x, y).a - Mathf.Clamp01(pixels[x, y].distance * scale)) * 0.5f; destination.SetPixel(x, y, c); } } } else{ for(y = 0; y &lt; height; y++){ for(x = 0; x &lt; width; x++){ c.a = Mathf.Clamp01(1f - pixels[x, y].distance * scale); destination.SetPixel(x, y, c); } } } } if(rgbMode == RGBMode.Distance){ for(y = 0; y &lt; height; y++){ for(x = 0; x &lt; width; x++){ c = destination.GetPixel(x, y); c.r = c.a; c.g = c.a; c.b = c.a; destination.SetPixel(x, y, c); } } } else if(rgbMode == RGBMode.Source){ for(y = 0; y &lt; height; y++){ for(x = 0; x &lt; width; x++){ c = source.GetPixel(x, y); c.a = destination.GetPixel(x, y).a; destination.SetPixel(x, y, c); } } } pixels = null; } private static void ComputeEdgeGradients () { float sqrt2 = Mathf.Sqrt(2f); for(int y = 1; y &lt; height - 1; y++){ for(int x = 1; x &lt; width - 1; x++){ Pixel p = pixels[x, y]; if(p.alpha &gt; 0f &amp;&amp; p.alpha &lt; 1f){ // estimate gradient of edge pixel using surrounding pixels float g = - pixels[x - 1, y - 1].alpha - pixels[x - 1, y + 1].alpha + pixels[x + 1, y - 1].alpha + pixels[x + 1, y + 1].alpha; p.gradient.x = g + (pixels[x + 1, y].alpha - pixels[x - 1, y].alpha) * sqrt2; p.gradient.y = g + (pixels[x, y + 1].alpha - pixels[x, y - 1].alpha) * sqrt2; p.gradient.Normalize(); } } } } private static float ApproximateEdgeDelta (float gx, float gy, float a) { // (gx, gy) can be either the local pixel gradient or the direction to the pixel if(gx == 0f || gy == 0f){ // linear function is correct if both gx and gy are zero // and still fair if only one of them is zero return 0.5f - a; } // normalize (gx, gy) float length = Mathf.Sqrt(gx * gx + gy * gy); gx = gx / length; gy = gy / length; // reduce symmetrical equation to first octant only // gx &gt;= 0, gy &gt;= 0, gx &gt;= gy gx = Mathf.Abs(gx); gy = Mathf.Abs(gy); if(gx &lt; gy){ float temp = gx; gx = gy; gy = temp; } // compute delta float a1 = 0.5f * gy / gx; if(a &lt; a1){ // 0 &lt;= a &lt; a1 return 0.5f * (gx + gy) - Mathf.Sqrt(2f * gx * gy * a); } if(a &lt; (1f - a1)){ // a1 &lt;= a &lt;= 1 - a1 return (0.5f - a) * gx; } // 1-a1 &lt; a &lt;= 1 return -0.5f * (gx + gy) + Mathf.Sqrt(2f * gx * gy * (1f - a)); } private static void UpdateDistance (Pixel p, int x, int y, int oX, int oY) { Pixel neighbor = pixels[x + oX, y + oY]; Pixel closest = pixels[x + oX - neighbor.dX, y + oY - neighbor.dY]; if(closest.alpha == 0f || closest == p){ // neighbor has no closest yet // or neighbor's closest is p itself return; } int dX = neighbor.dX - oX; int dY = neighbor.dY - oY; float distance = Mathf.Sqrt(dX * dX + dY * dY) + ApproximateEdgeDelta(dX, dY, closest.alpha); if(distance &lt; p.distance){ p.distance = distance; p.dX = dX; p.dY = dY; } } private static void GenerateDistanceTransform () { // perform anti-aliased Euclidean distance transform int x, y; Pixel p; // initialize distances for(y = 0; y &lt; height; y++){ for(x = 0; x &lt; width; x++){ p = pixels[x, y]; p.dX = 0; p.dY = 0; if(p.alpha &lt;= 0f){ // outside p.distance = 1000000f; } else if (p.alpha &lt; 1f){ // on the edge p.distance = ApproximateEdgeDelta(p.gradient.x, p.gradient.y, p.alpha); } else{ // inside p.distance = 0f; } } } // perform 8SSED (eight-points signed sequential Euclidean distance transform) // scan up for(y = 1; y &lt; height; y++){ // |P. // |XX p = pixels[0, y]; if(p.distance &gt; 0f){ UpdateDistance(p, 0, y, 0, -1); UpdateDistance(p, 0, y, 1, -1); } // --&gt; // XP. // XXX for(x = 1; x &lt; width - 1; x++){ p = pixels[x, y]; if(p.distance &gt; 0f){ UpdateDistance(p, x, y, -1, 0); UpdateDistance(p, x, y, -1, -1); UpdateDistance(p, x, y, 0, -1); UpdateDistance(p, x, y, 1, -1); } } // XP| // XX| p = pixels[width - 1, y]; if(p.distance &gt; 0f){ UpdateDistance(p, width - 1, y, -1, 0); UpdateDistance(p, width - 1, y, -1, -1); UpdateDistance(p, width - 1, y, 0, -1); } // &lt;-- // .PX for(x = width - 2; x &gt;= 0; x--){ p = pixels[x, y]; if(p.distance &gt; 0f){ UpdateDistance(p, x, y, 1, 0); } } } // scan down for(y = height - 2; y &gt;= 0; y--){ // XX| // .P| p = pixels[width - 1, y]; if(p.distance &gt; 0f){ UpdateDistance(p, width - 1, y, 0, 1); UpdateDistance(p, width - 1, y, -1, 1); } // &lt;-- // XXX // .PX for(x = width - 2; x &gt; 0; x--){ p = pixels[x, y]; if(p.distance &gt; 0f){ UpdateDistance(p, x, y, 1, 0); UpdateDistance(p, x, y, 1, 1); UpdateDistance(p, x, y, 0, 1); UpdateDistance(p, x, y, -1, 1); } } // |XX // |PX p = pixels[0, y]; if(p.distance &gt; 0f){ UpdateDistance(p, 0, y, 1, 0); UpdateDistance(p, 0, y, 1, 1); UpdateDistance(p, 0, y, 0, 1); } // --&gt; // XP. for(x = 1; x &lt; width; x++){ p = pixels[x, y]; if(p.distance &gt; 0f){ UpdateDistance(p, x, y, -1, 0); } } } } private static void PostProcess (float maxDistance) { // adjust distances near edges based on the local edge gradient for(int y = 0; y &lt; height; y++){ for(int x = 0; x &lt; width; x++){ Pixel p = pixels[x, y]; if((p.dX == 0 &amp;&amp; p.dY == 0) || p.distance &gt;= maxDistance){ // ignore edge, inside, and beyond max distance continue; } float dX = p.dX, dY = p.dY; Pixel closest = pixels[x - p.dX, y - p.dY]; Vector2 g = closest.gradient; if(g.x == 0f &amp;&amp; g.y == 0f){ // ignore unknown gradients (inside) continue; } // compute hit point offset on gradient inside pixel float df = ApproximateEdgeDelta(g.x, g.y, closest.alpha); float t = dY * g.x - dX * g.y; float u = -df * g.x + t * g.y; float v = -df * g.y - t * g.x; // use hit point to compute distance if(Mathf.Abs(u) &lt;= 0.5f &amp;&amp; Mathf.Abs(v) &lt;= 0.5f){ p.distance = Mathf.Sqrt((dX + u) * (dX + u) + (dY + v) * (dY + v)); } } } } }</code></pre></div> 
				 	]]>
				</content:encoded>							</item>
				</channel>
</rss>