Envelope Distortion using C# and GraphicsPath Class

I asked this question on StackOverflow nearly two years ago.  That is, looking for an algorithm to distort an arbitrary set of polygons (text for example) into a predetermined shape.  If you are familiar with Adobe Illustrator this feature is called Envelope Distortion.   Surprisingly my SO post got little response and I ultimately followed up with a limited workaround, although not a solution. I really wanted to incorporate this feature into my upcoming api, so I set out to figure it out myself.  Since there is not a lot of information about this on the net (that I can find) I decided to share a portion of my work. Step one was printing out some grid paper and figuring out exactly how this process should work. In this case writing code wasn’t going get anywhere until the underlying process was solved.  Essentially the algorithm should accept any X, Y inside of the bounds of the source polygon and return a transformed X, Y that fits inside of the resulting distorted polygon.

Implementing Bulge Distortion

Bulge distortion creates a bulging effect on a path by stretching it up and down.  This is useful since several other types of distortions can be easily achieved by making slight modifications to the generated distortion path.

bulge-distortion-1

Shown above is some arbitrary text loaded into a GDI+ GraphicsPath object.  Also drawn around the text is a distortion path that we will ultimately warp the text into.  The code accepts an float called Intensity which can be positive or negative.  This is a positive intensity of 1, should you use a negative intensity the distortion path would look like below.

bulge-distortion-2

In order to debug the process of writing the bulge distortion my first task was to view a grid of each point remapped into the correct location.  Upon this process it should be relatively easy to feed in a flattened Polygon (no bezier curves, just points) and see the resulting transformation.

bulge-distortion-3

How it works

For example, if we wanted to know where would X=170 and Y=10 translate to given the above Bulge distortion with Intensity = 1?  Refer the image below, first we calculate the relative position of Y in the bounding box.  We then scale that position linear to the relative height of the distortion path.  The difficult part is that should we ask for an X that is not in the flattened distortion path we have to calculate the location of that point on any given polygon.  This seems quite easy but in fact was not.  My solution was to utilize the outstanding polygon clipping implementation called Clipper.

bulge-distortion-4

In order to get the upper and lower bound of the Distortion Path given an source X, you need to essentially subtract the shaded polygon from the distortion path. At that point the resulting polygon read in the upper left and lower left points to calculate the height range. These two methods implement the above

        public PointF Distort(GraphicsPath source, PointF point)
        {
            if (_distortionPath == null)
            {
                BuildDistortion(source);
            }
 
            var ScaledPoint = point;
 
            PointF UpperBoundPoint;
            PointF LowerBoundPoint;
 
            GetBoundedPoints(out UpperBoundPoint, out LowerBoundPoint, point);
            var Y = UpperBoundPoint.Y + (((ScaledPoint.Y - _sourceBounds.Top) / _sourceBounds.Height) * Math.Abs(UpperBoundPoint.Y - LowerBoundPoint.Y));
 
            return new PointF(ScaledPoint.X, Y);
        }
 
        private void GetBoundedPoints(out PointF upperBoundPoint, out PointF lowerBoundPoint, PointF source)
        {
 
            if (_boundCache.ContainsKey(source.X))
            {
                upperBoundPoint = _boundCache[source.X][0];
                lowerBoundPoint = _boundCache[source.X][1];
                return;
            }
 
            var Path = new GraphicsPath();
            var UpperX = source.X * (_sourceBounds.Width / (_upperRight.X -_upperLeft.X));
            var LowerX = source.X * (_sourceBounds.Width / (_lowerRight.X -_lowerLeft.X));
            Path.AddPolygon(new PointF[]{
                new PointF(_distortionBounds.Left,_distortionBounds.Bottom),
                new PointF(_distortionBounds.Left, _distortionBounds.Top),
                new PointF(UpperX,  _distortionBounds.Top),
                new PointF(LowerX, _distortionBounds.Bottom), 
            });
            Path.CloseFigure();
            var ClippingPath = ClipperUtility.ConvertToClipperPolygons(Path);
            Path.Dispose();
 
            var ClippedPath = ClipperUtility.Clip(ClippingPath, _distortionPoints);
            if (Math.Abs(source.X - _sourceBounds.Left) < .1 || Math.Abs(source.X - _sourceBounds.Right) < .1)             {                 upperBoundPoint = new PointF(_sourceBounds.Left, _sourceBounds.Top);                 lowerBoundPoint = new PointF(_sourceBounds.Left, _sourceBounds.Bottom );             }             else             {                 var Points = ClippedPath.PathPoints;                 var QuickBounded = Points.Where(p => Math.Abs(p.X - LowerX) < .01);                 if (QuickBounded.Any())                 {                     upperBoundPoint = Points.Where(p => Math.Abs(p.X - LowerX) < .01).OrderBy(p => p.Y).First();
                    lowerBoundPoint = Points.Where(p => Math.Abs(p.X - LowerX) < .01).OrderByDescending(p => p.Y).First();
                    _boundCache.Add(source.X, new PointF[] { upperBoundPoint, lowerBoundPoint });
                }
                else
                {
                    var RightMostPoints = Points.OrderByDescending(p => p.X).Take(2).ToList();
                    upperBoundPoint = RightMostPoints.OrderBy(p => p.Y).First();
                    lowerBoundPoint = RightMostPoints.OrderByDescending(p => p.Y).First();
                }
                ClippedPath.Dispose();
            }
 
        }

The GetBoundedPoints method generates the shaded polygon and calls  ClipperUtility.Clip(ClippingPath, _distortionPoints) which actually clips and generates the resulting polygon.  The last if statement handles the edge case of the X=0 or X= Width else it looks through the points for the closest to the edge.     You’ll probably the notice the _boundCache, that saves some work since once the upper and lower points are calculated for bulge distortion they are the same no matter what Y.  For that reason we cache them.  My particular complaint with this code in the state it is in is the floating point precision comparison.  You’ll notice a particularly sloppy Math.Abs(source.X – _sourceBounds.Left) < .1, Yikes!   While this code does perform perfectly with the test data I’ve tried so far, I believe that will be a point of failure first.  My thought is that there is a precision loss/gain during the polygon clipping procedure I need to investigate more.   At any rate, the resulting transformed text will look like this.

 bulge-distortion-5

Not bad for a first attempt, in fact everything looks great except the L and the right e.  Why is that?  Actually the algorithm is working perfectly, the issue is based on the process of flattening the path.  The L only has two lower points, the point on the bottom left, and the point on the bottom right.  Therefore, the algorithm did exactly what is was supposed to, move those two points into their respective distorted positions.  What we really need to is to inject points in between to increase the precision of the operation.

bulge-distortion-6

This code checks each path both vertical and horizontal runs, if the span is less than the flatness it injects points to increase the precision.

        private void InjectPrecisionPoints(GraphicsPath gp)
        {
            var InsertDictionary = new Dictionary&lt;int, PointF[]&gt;();
            //inject points on vertical and horizontal runs to increase precision
            for (var j = 0; j &lt; gp.PointCount; j++)
            {
                PointF CurrentPoint;
                PointF NextPoint;
                if (j != gp.PointCount - 1)
                {
                    CurrentPoint = gp.PathPoints[j];
                    NextPoint = gp.PathPoints[j + 1];
                }
                else
                {
                    CurrentPoint = gp.PathPoints[j];
                    NextPoint = gp.PathPoints[0];
                }
                if (Math.Abs(CurrentPoint.X - NextPoint.X) &lt; .001 &amp;&amp; Math.Abs(CurrentPoint.Y - NextPoint.Y) &gt; _flatness)
                {
                    var Distance = CurrentPoint.Y - NextPoint.Y;
                    var Items = Enumerable.Range(1, Convert.ToInt32(Math.Floor(Math.Abs(Distance)/_flatness)))
                                           .Select(p =&gt; new PointF(CurrentPoint.X, Distance &lt; 0 ? (CurrentPoint.Y + (_flatness * p)) : (CurrentPoint.Y - (_flatness * p))))
                                           .ToArray();
                    InsertDictionary.Add(j + 1, Items);
                }
                if (Math.Abs(CurrentPoint.Y - NextPoint.Y) &lt; .001 &amp;&amp; Math.Abs(CurrentPoint.X - NextPoint.X) &gt; _flatness)
                {
                    var Distance = CurrentPoint.X - NextPoint.X;
                    var Items =  Enumerable.Range(1, Convert.ToInt32(Math.Floor(Math.Abs(Distance)/_flatness)))
                                           .Select(p =&gt; new PointF(Distance &lt; 0 ? (CurrentPoint.X + (_flatness * p)) : (CurrentPoint.X - (_flatness * p)), CurrentPoint.Y))                                            .ToArray();                     InsertDictionary.Add(j + 1, Items);                 }             }             if (InsertDictionary.Count &gt; 0)
            {
                var PointArray = gp.PathPoints.ToList();
                InsertDictionary.OrderByDescending(p =&gt; p.Key).ToList().ForEach(p =&gt; PointArray.InsertRange(p.Key, p.Value));
 
                gp.Reset();
                gp.AddPolygon(PointArray.ToArray());
 
                InsertDictionary.Clear();
            }
        }

The resulting warp with the precision points injected

lone-techie-envelope-distort

The same Bulge Distortion with an Intensity of -.2

bulge-distortion-7

Implementing other distortions

distort-shapes

 

With relatively little change to the code you can also implement any of the above distortions just by modifying the BuildDistortion method.  The project is setup in such a way you could create a new class that implements IDistortion and create your own distortions.

Enjoy!

Here is the source code available on GitHub

Tagged: , , , , ,

Using Forms Authentication with ASP.net Web Api

I found myself recently needing to implement a very simple web api private web service into one of my sites to sync some data between servers. I’ve implemented a full blown API key authentication before with web api in past projects. It really isn’t that hard, but why add all of that extra code when my site already fully implements role based forms authentication? It is so easy to just use the authorize attribute with forms authentication.

        // GET api/sync
        [Authorize (Roles = "ApiUser")]
        public IEnumerable<int> Get(int? days)
        {
             //do some work return some data
        }

The problem isn’t really the code above, that will work fine and happily return an “unauthorized” message when you call it. In order to use the HttpClient properly you first need to login posting to the form that contains your login. This could vary depending on how your form is setup, but this works with the default asp.net template.

        private HttpClient CreateAuthenticatedSession()
        {
            var Client = new HttpClient {BaseAddress = new Uri("https://www.yourdomain.com")};
            var Result = Client.PostAsync("/Account/Logon/",
                             new FormUrlEncodedContent(
                                 new Dictionary<string, string>{ {"UserName", "Your Username"}, 
                                 {
                                     "Password","Your Password"
                                 }})).Result;
            Result.EnsureSuccessStatusCode();
            return Client;
        }

The code above once called will hopefully login to your site through forms authentication and return an HttpClient holding the correct cookie that will be passed with subsequent requests. You can keep talking to your Forms Authenticated web service as long as you hold onto the HttpClient returned after you authenticated. Below is a simple code block that calls CreateAuthenticatedSession() and calls the web api service.

        public void GenerateByDay(int days, string path)
        {
            var HttpClientSession = CreateAuthenticatedSession();
            HttpClientSession.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            var Result = HttpClientSession.GetAsync("/api/sync/?days=" + days.ToString(CultureInfo.InvariantCulture)).Result;
            Result.EnsureSuccessStatusCode();
            var ResponseBody = Result.Content.ReadAsStringAsync().Result;
            //deserialize result and continue on 
        }
Tagged: , , ,

Fixing Multiple actions were found that match the request Asp.net Web Api Error

WebApiTest Project Here

I’m currently developing a new project utilizing asp.net Web Api 4.5. I hit a frustrating issue today with regards to routing actions. The error is “Multiple actions were found that match the request” when I added a new method to my code bypassing the built in GET,POST,PUT convention that web API supports out of the box.

The default scaffold controller web api gives you has the following pattern out of the box.

    public class TestController : ApiController
    {
        // GET api/test
        public IEnumerable<string> Get()
        {
            return new string[] { "value1", "value2" };
        }
 
        // GET api/test/5
        public string Get(int id)
        {
            return "value";
        }
 
        // POST api/test
        public void Post([FromBody]string value)
        {
        }
 
        // PUT api/test/5
        public void Put(int id, [FromBody]string value)
        {
        }
 
        // DELETE api/test/5
        public void Delete(int id)
        {
        }
    }

Problems start to appear when you want to go beyond this convention such as another “GET” method that goes by a different name.

    public class TestController : ApiController
    {
        // GET api/test
        public IEnumerable<string> Get()
        {
            return new string[] { "value1", "value2" };
        }
 
        // GET api/test/GetAllWithFilter
        [HttpGet]
        public IEnumerable<string> GetAllWithFilter()
        {
            return new string[] { "value1"};
        }
 
        // GET api/test/5
        public string Get(int id)
        {
            return "value";
        }
 
        // POST api/test
        public void Post([FromBody]string value)
        {
        }
 
        // PUT api/test/5
        public void Put(int id, [FromBody]string value)
        {
        }
 
        // DELETE api/test/5
        public void Delete(int id)
        {
        }
    }

This shouldn’t be that hard to fix on the surface but the built in route that enables this convention actually makes this much more difficult that it should be. I searched around for a while to figure out how to solve this then finally ran across a great Stackoverflow post here that nails it. Sadly this post isn’t even marked as the answer, it should be.

In short replace your built in route in WebApiConfig with this (updated for latest release). Be sure to modify your api path to suit your application.

            config.Routes.MapHttpRoute("DefaultApiWithId", "api/v1/{controller}/{id}", new { id = RouteParameter.Optional }, new { id = @"\d+" });
            config.Routes.MapHttpRoute("DefaultApiWithAction", "api/v1/{controller}/{action}");
            config.Routes.MapHttpRoute("DefaultApiGet", "api/v1/{controller}", new { action = "Get" }, new { httpMethod = new HttpMethodConstraint(HttpMethod.Get) });
            config.Routes.MapHttpRoute("DefaultApiPost", "api/v1/{controller}", new { action = "Post" }, new { httpMethod = new HttpMethodConstraint(HttpMethod.Post) });
            config.Routes.MapHttpRoute("DefaultApiPut", "api/v1/{controller}", new { action = "Put" }, new { httpMethod = new HttpMethodConstraint(HttpMethod.Put) });
            config.Routes.MapHttpRoute("DefaultApiDelete", "api/v1/{controller}", new { action = "Delete" }, new { httpMethod = new HttpMethodConstraint(HttpMethod.Delete) });

This honors the GET,POST,PUT,DELETE convention while allowing you to create differently named actions in the same controller!

Update – Sample Project

As requested here is a sample project that I have verified works.

WebApiTest Project Here

Tagged: , , , ,