Tuesday, October 9, 2012

Using HTML5 Canvas to Create Cracked Glass Effect

I decided to spend some time working with the HTML Canvas object and see what kind of fun I could have with it.  I stumbled upon an article about creating a cracked glass effect in the context of gaming and how you might approach the problem in an optimal way.  There's no code accompanying this description so I thought I give it a try and see what I could devise.  As I began working out the details, it became apparent there was more to this problem than figuring out how to construct all the cracks in the glass.  I found myself spending a lot more time figuring out how to draw just one cracked line.  As a result, I broke this algorithm into two pieces:

  1. Find all the points to draw a crack from/to.  This follows the concept of the algorithm described in the article above.

  2. Draw a crack between two points defined in step 1.  This function attempts to replicate the look an individual crack segment.

Before diving into both of these pieces, I decided to start by looking at some real-life examples.  Here's one particular image I thought I would use as a reference:

I was attempting to find more examples of broken glass with more detailed items in the background to see how it looked.  The closest I could find were broken screens of tablets, computers, and other mobile devices.  So decided to just take a image of cracked glass and overlay it on the image I was testing with just to see what it might look like:

[caption id="attachment_422" align="alignnone" width="400"> Base sample image[/caption]

After some fussing around in an image editor, I achieved the following results.  I figured this was a good target so set out to write some code that could make something that looked close to this:

[caption id="attachment_424" align="alignnone" width="400"> Overlay real broken glass onto the image using photo editing software.[/caption]

The end result after writing the algorithm looks like this:

[caption id="attachment_423" align="alignnone" width="397"> Use the algorithm to create a cracked glass effect on a HTML5 Canvas and overlay it on the image.[/caption]

Its pretty close and completely generated by drawing lines on an HTML5 Canvas.  The demo is in my sandbox and each time you click the "Add Cracks" button, a center point is randomly chosen on the image and the cracks are drawn.  Each run is completely different since everything is randomized.

Now, let's dive into the code and see how it all works.  The function that is called by clicking on "Add Cracks" implements creating the network of paths that define where cracks will be drawn.  It starts by constructing an array that represents a table of concentric circles growing outward from the center point and lines running outward like spokes on a wheel.  Each row of the table is considered to be one of these circles and each column is one of the lines.  Each cell of the table then represents the point where the line interests each circle.  The radius of each circle and the angle of each line is randomly generated so each time the function is called, it creates a different set of points.

Our table of points in the array is now setup in a convenient way to iterate through them and create lines between adjacent points:

level / line | line 0 | line 1 | line 2 | line 3 | line 4 |
circle r0 | (x0,y0) | (x1,y1) | (x2,y2) | (x3,y3) | (x4,y4) |
circle r1 | (x5,y5) | (x6,y6) | (x7,y7) | (x8,y8) | (x9,y9) |
circle r2 | (x10,y10) | (x11,y11) | (x12,y12) | (x13,y13) | (x14,y14) |
circle r3 | (x15,y15) | (x16,y16) | (x17,y17) | (x18,y18) | (x19,y19) |

Once that base table of points is generated, we simply need to draw individual cracking lines between each point in the table.  There are three types of lines we want to draw:

[caption id="attachment_428" align="alignnone" width="489"> Three types of lines are drawn between each circle in the cracking pattern[/caption]

  1. Line segment A is always drawn to connect each point along the path from the center out to the last circle (or edge of the image).  In the table, this is the points in the same column but in two consecutive rows.

  2. Line segment B also connects points between two rows of the table but instead of points in the same column, two adjacent columns are connected.

  3. Line segment C connects two points in the same row but in two adjacent columns.

Lines B and C are not always drawn but randomly drawn using a cutoff that causes the lines to be drawn more frequently near the center.

So far, we haven't drawn anything on the canvas.  The next step is to figure out how to actually render a crack in our fictitious glass.  The method I chose does the following steps:

  1. Randomly define a slight curve to the line so the crack isn't perfectly straight.

  2. Create a clipping region along the crack line and copy a portion of the image into the canvas offsetting it slightly to create the refraction you would see in cracked glass.

  3. Step along the path drawing three types of lines along that line at random intervals:

    1. A rectangular shape filled using a radial gradient that fades out to blur the line.  This emulates the opaqueness created in the glass when its cracked.

    2. A white solid line along the path to create a highlight.  This better defines the actual crack.

    3. Short lines perpendicular to the crack path which simulate the fracturing in the glass along the cracking line segment.

  4. Add some random noise around in the rectangle defined by the bounds of the crack.

By controlling the variables used to define the color, length, width, and frequency of those drawn objects, you can create a whole array of visual cracking effects that can mimic various lighting conditions, types of glass, etc.  I tinkered for quite awhile adjusting different values.  As a separate project, I might add all those options to the function arguments so I can externally control the styling of the cracks.  For now, I stuck with mimicking my reference image.

The final result is not perfect and there is definitely some room for improvement.  One problem occurs if you try to copy larger parts of the image into the canvas to create a more pronounced refraction effect.  Each call to the drawing function draws on top of the previous calls already completed drawing.  What happens is the lines drawn in step 3 above get drawn over by the copied sections and they become less visible thus losing part of the effect (this is really bad near the center).  Really, the base image needs to be copied first and then all the lines drawn on top of that.  Additionally, the reference image has a lot of extra "noise" and fogginess between the actual cracking lines.  I did add some noise to the image but its not creating the effect as well as I'd like. Overall, this was a fun first pass at the algorithm and gave me a chance to learn a little more about using the HTML5 Canvas object. I'll probably revisit it after spending more time delving into more specific areas of the Canvas API.

EDIT:  Here's my updated post on an improved version of the effect.  An updated demo is in my sandbox.