<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0">
 
 <title>jackadamblog</title>
 
 <link href="http://blog.jackadam.net/" />
 <updated>2012-02-28T17:50:45-08:00</updated>
 <id>http://blog.jackadam.net/</id>
 <author>
   <name>jackadamblog</name>
   <email>info@jackad.am</email>
 </author>

 
 <atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/atom+xml" href="http://feeds.feedburner.com/jackadamblog" /><feedburner:info uri="jackadamblog" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><feedburner:feedFlare href="http://add.my.yahoo.com/rss?url=http%3A%2F%2Ffeeds.feedburner.com%2Fjackadamblog" src="http://us.i1.yimg.com/us.yimg.com/i/us/my/addtomyyahoo4.gif">Subscribe with My Yahoo!</feedburner:feedFlare><feedburner:feedFlare href="http://www.newsgator.com/ngs/subscriber/subext.aspx?url=http%3A%2F%2Ffeeds.feedburner.com%2Fjackadamblog" src="http://www.newsgator.com/images/ngsub1.gif">Subscribe with NewsGator</feedburner:feedFlare><feedburner:feedFlare href="http://feeds.my.aol.com/add.jsp?url=http%3A%2F%2Ffeeds.feedburner.com%2Fjackadamblog" src="http://o.aolcdn.com/favorites.my.aol.com/webmaster/ffclient/webroot/locale/en-US/images/myAOLButtonSmall.gif">Subscribe with My AOL</feedburner:feedFlare><feedburner:feedFlare href="http://www.bloglines.com/sub/http://feeds.feedburner.com/jackadamblog" src="http://www.bloglines.com/images/sub_modern11.gif">Subscribe with Bloglines</feedburner:feedFlare><feedburner:feedFlare href="http://www.netvibes.com/subscribe.php?url=http%3A%2F%2Ffeeds.feedburner.com%2Fjackadamblog" src="http://www.netvibes.com/img/add2netvibes.gif">Subscribe with Netvibes</feedburner:feedFlare><feedburner:feedFlare href="http://fusion.google.com/add?feedurl=http%3A%2F%2Ffeeds.feedburner.com%2Fjackadamblog" src="http://buttons.googlesyndication.com/fusion/add.gif">Subscribe with Google</feedburner:feedFlare><feedburner:feedFlare href="http://www.pageflakes.com/subscribe.aspx?url=http%3A%2F%2Ffeeds.feedburner.com%2Fjackadamblog" src="http://www.pageflakes.com/ImageFile.ashx?instanceId=Static_4&amp;fileName=ATP_blu_91x17.gif">Subscribe with Pageflakes</feedburner:feedFlare><feedburner:feedFlare href="http://www.plusmo.com/add?url=http%3A%2F%2Ffeeds.feedburner.com%2Fjackadamblog" src="http://plusmo.com/res/graphics/fbplusmo.gif">Subscribe with Plusmo</feedburner:feedFlare><feedburner:feedFlare href="http://www.thefreedictionary.com/_/hp/AddRSS.aspx?http%3A%2F%2Ffeeds.feedburner.com%2Fjackadamblog" src="http://img.tfd.com/hp/addToTheFreeDictionary.gif">Subscribe with The Free Dictionary</feedburner:feedFlare><feedburner:feedFlare href="http://www.bitty.com/manual/?contenttype=rssfeed&amp;contentvalue=http%3A%2F%2Ffeeds.feedburner.com%2Fjackadamblog" src="http://www.bitty.com/img/bittychicklet_91x17.gif">Subscribe with Bitty Browser</feedburner:feedFlare><feedburner:feedFlare href="http://www.live.com/?add=http%3A%2F%2Ffeeds.feedburner.com%2Fjackadamblog" src="http://tkfiles.storage.msn.com/x1piYkpqHC_35nIp1gLE68-wvzLZO8iXl_JMledmJQXP-XTBOLfmQv4zhj4MhcWEJh_GtoBIiAl1Mjh-ndp9k47If7hTaFno0mxW9_i3p_5qQw">Subscribe with Live.com</feedburner:feedFlare><feedburner:feedFlare href="http://mix.excite.eu/add?feedurl=http%3A%2F%2Ffeeds.feedburner.com%2Fjackadamblog" src="http://image.excite.co.uk/mix/addtomix.gif">Subscribe with Excite MIX</feedburner:feedFlare><feedburner:feedFlare href="http://www.webwag.com/wwgthis.php?url=http%3A%2F%2Ffeeds.feedburner.com%2Fjackadamblog" src="http://www.webwag.com/images/wwgthis.gif">Subscribe with Webwag</feedburner:feedFlare><feedburner:feedFlare href="http://www.podcastready.com/oneclick_bookmark.php?url=http%3A%2F%2Ffeeds.feedburner.com%2Fjackadamblog" src="http://www.podcastready.com/images/podcastready_button.gif">Subscribe with Podcast Ready</feedburner:feedFlare><feedburner:feedFlare href="http://www.wikio.com/subscribe?url=http%3A%2F%2Ffeeds.feedburner.com%2Fjackadamblog" src="http://www.wikio.com/shared/img/add2wikio.gif">Subscribe with Wikio</feedburner:feedFlare><feedburner:feedFlare href="http://www.dailyrotation.com/index.php?feed=http%3A%2F%2Ffeeds.feedburner.com%2Fjackadamblog" src="http://www.dailyrotation.com/rss-dr2.gif">Subscribe with Daily Rotation</feedburner:feedFlare><entry>
   <title>How Dark Sky Works</title>
   <link href="http://feedproxy.google.com/~r/jackadamblog/~3/s7qe98fIbYQ/" />
   <updated>2011-11-07T00:00:00-08:00</updated>
   <id>http://blog.jackadam.net/2011/how-dark-sky-works</id>
   <content type="html">&lt;p&gt;
&lt;em&gt;Dark Sky is a weather prediction and visualization app for the iPhone, iPad and iPod touch. For an overview, and to learn how you can help us, check out &lt;a href="http://www.kickstarter.com/projects/jackadam/dark-sky-hyperlocal-weather-prediction-and-visuali"&gt;our Kickstarter page.&lt;/a&gt; &lt;/em&gt;
&lt;/p&gt;

&lt;p&gt;
A lot of people have been wondering how &lt;a href="http://www.kickstarter.com/projects/jackadam/dark-sky-hyperlocal-weather-prediction-and-visuali"&gt;Dark Sky&lt;/a&gt; works. In this post I’ll try to shed some light on what goes on under the hood.
&lt;/p&gt;

&lt;h2&gt;Getting the data&lt;/h2&gt;

&lt;p&gt;
All our radar data comes from the National Oceanic and Atmospheric Administration. NOAA &lt;a href="http://radar.weather.gov"&gt;operates a network&lt;/a&gt; of over 140 radar stations spread across the United States and its territories, and one of the advantages of being a US citizen is that you get access to this data for free. They’re mandated by law to make their information public, but they go above and beyond by providing real-time access via the web and FTP.
&lt;/p&gt;
&lt;p&gt;
We &lt;a href="http://www.nws.noaa.gov/tg/radfiles.html"&gt;download&lt;/a&gt; the raw radar data in binary format. But if you’re looking to get started playing around with weather radar, a better place to start is perhaps &lt;a href="http://www.srh.noaa.gov/jetstream/doppler/ridge_download.htm"&gt;RIDGE&lt;/a&gt;, which boils the raw data into color-coded images that are easier to work with.
&lt;/p&gt;

&lt;h2&gt;Cleaning it up&lt;/h2&gt;

&lt;p&gt;
Weather radar is noisy. There’s lots of ground clutter, bug and bird migrations (!), and other artifacts that can be confused for precipitation. Here’s an example:
&lt;/p&gt;
&lt;p&gt;
&lt;img class="aligncenter" src="/images/how-dark-sky-works/1.jpg" /&gt;
&lt;/p&gt;
&lt;p&gt;
So we’ll need to clean this up. How do we do it? Well, the noise mostly consists of low-intensity data -- the light blue areas. One technique (which &lt;a href="http://egb13.net/2009/09/noise-removal-from-noaa-weather-radar/"&gt;this gentleman&lt;/a&gt; writes more about), is to simply remove all low-intensity data. It clears away the noise, and leaves &lt;em&gt;most&lt;/em&gt; of the actual precipitation data.
&lt;/p&gt;
&lt;p&gt;
However, for our purposes, “most” isn’t good enough. By removing low-intensity data indiscriminately, we’re removing valuable data from the leading and trailing edges of the storms. This data is crucially important if we’re going to predict when it will start and stop raining. So we’ll have to be a little more clever.
&lt;/p&gt;
&lt;p&gt;
After staring at enough radar images, it becomes pretty clear which signals are real precipitation data, and which are noise. Not only does noise tend to be low-intensity, but it has a recognizable “texture” which is distinct from actual storms. It’s easy to see with the eyes, but hard to explicitly quantify or define. So how do we write a program to remove it? This type of I-know-it-when-I-see-it, but ill-defined situation is ripe for some machine learnin’.
&lt;/p&gt;
&lt;p&gt;
In this case, we opted for neural nets. Using the &lt;a href="http://leenissen.dk/fann/wp/"&gt;Fast Artificial Neural Network&lt;/a&gt; C-library, we trained a neural net with thousands of “blobs” of data hand-separated into two categories: noise, and not-noise.
&lt;/p&gt;
&lt;p&gt;
&lt;img class="aligncenter" src="/images/how-dark-sky-works/2.jpg" /&gt;
&lt;/p&gt;
&lt;p&gt;
The training process takes a while to run but the end result is a small, blazing fast program that accurately identifies somewhere between 90% and 95% of the noise, with very few false positives.
&lt;/p&gt;
&lt;p&gt;
(To take care of the remainder, we spent an embarrassingly large amount of time and effort writing heuristic clean-up code by a tedious process of trial and error. But we’re keeping that part secret ;-)
&lt;/p&gt;
&lt;p&gt;
The end result looks something like this:
&lt;/p&gt;
&lt;p&gt;
&lt;img class="aligncenter" src="/images/how-dark-sky-works/3.jpg" /&gt;
&lt;/p&gt;

&lt;h2&gt;Extracting storm velocity&lt;/h2&gt;

&lt;p&gt;
Now that we have the radar data and we’ve cleaned it up, we come to the real meat of the app: velocity extraction.
&lt;/p&gt;
&lt;p&gt;
But first, let me talk a little about the philosophy of our approach...
&lt;/p&gt;
&lt;p&gt;
Weather is chaotic. It’s the quintessential non-linear dynamical system. Complicated fluid dynamics combined with a spinning globe, uneven terrain, and energy pumped into the system from the sun makes the weather extremely hard to predict with any accuracy. Meteorologists have spent a century coming up with increasingly sophisticated models and they still can’t reliably talk about the weather -- even generally -- much more than a week in advance.
&lt;/p&gt;
&lt;p&gt;
So how can we justify our claim of being able to predict precipitation down to the very minute?
&lt;/p&gt;
&lt;p&gt;
Well, here’s the thing: while the weather becomes chaotic and unpredictable at large timescales (hours to days), its behavior becomes increasingly linear at smaller and smaller timescales. To see this in action, take a look at cumulus clouds drifting across the sky. They tend to move in relatively straight lines:
&lt;/p&gt;
&lt;p&gt;
&lt;iframe width="640" height="360" class="aligncenter" src="http://www.youtube.com/embed/p6qPJAmmQ8A?rel=0&amp;amp;hd=1" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;
&lt;/p&gt;
&lt;p&gt;
We’ve found that precipitation bands are even more coherent and behave approximately linearly over the course of minutes, and in many cases up to an hour or more. So how do we go about quantifying this linear motion? Math, that’s how!
&lt;/p&gt;
&lt;p&gt;
We use various computer vision algorithms to compare multiple radar image frames and create a map of velocity. Specifically, we use &lt;a href="http://opencv.willowgarage.com/wiki/"&gt;OpenCV&lt;/a&gt;, an open-source computer vision library, which comes with a number of optical flow and object tracking algorithms (for a great introduction to the topic and to see some sample code, check out &lt;a href="http://robots.stanford.edu/cs223b05/notes/CS%20223-B%20T1%20stavens_opencv_optical_flow.pdf"&gt;this tutorial&lt;/a&gt; by David Stavens at the Stanford AI Labs).
&lt;/p&gt;
&lt;p&gt;
The end result looks something like this:
&lt;/p&gt;
&lt;p&gt;
&lt;img class="aligncenter" src="/images/how-dark-sky-works/4.jpg" /&gt;
&lt;/p&gt;
&lt;p&gt;
Actually, we don’t draw little arrows. Instead, we encode the velocity in a more useful form: colors. We create a 3-channel image where the red channel represents velocity in the x-direction, the blue channel represents velocity in the y-direction, and the green channel represents change in storm intensity:
&lt;/p&gt;
&lt;p&gt;
&lt;img class="aligncenter" src="/images/how-dark-sky-works/5.png" /&gt;
&lt;/p&gt;
&lt;p&gt;
(If you’re so inclined, open the image in Photoshop and check out the color channels individually)
&lt;/p&gt;

&lt;h2&gt;Prediction and Interpolation&lt;/h2&gt;

&lt;p&gt;
So, now that we’ve extracted storm velocity, it’s time to use it to predict the future!
&lt;/p&gt;
&lt;p&gt;
The reason we encode velocity data as an image is so we can pass it off to the GPU on the iPhone and iPad. Both the storm prediction and the smooth animations are calculated on the device itself, rather than the server, and all the magic happens directly on the GPU.
&lt;/p&gt;
&lt;p&gt;
I’m going to be a little vague here, since this part constitutes our “secret sauce”, and is the component of our system that is in most active development. So instead, here’s a pretty video of the app in action:
&lt;/p&gt;
&lt;p&gt;
&lt;iframe src="http://player.vimeo.com/video/31751453?title=0&amp;amp;byline=0&amp;amp;portrait=0" width="680" height="510" class="aligncenter" frameborder="0" webkitAllowFullScreen allowFullScreen&gt;&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;Monitoring error&lt;/h2&gt;

&lt;p&gt;
A prediction is worthless unless it is not only accurate, but &lt;em&gt;reliably&lt;/em&gt; accurate. A large amount of our effort is focused on measuring the error rate of Dark Sky predictions. Some storms are more coherent and stable than others, so how far into the future we can project varies over time and at different geographical locations.
&lt;/p&gt;
&lt;p&gt;
Whenever we process a new radar image, we go back to previous images and project them forward, creating a map of what we &lt;em&gt;think&lt;/em&gt; the storm will look like in the present. We then compare this with the latest radar image to see how close we got.
&lt;/p&gt;
&lt;p&gt;
&lt;img class="aligncenter" src="/images/how-dark-sky-works/6.gif" /&gt;
&lt;/p&gt;
&lt;p&gt;
We are constantly doing this check in real time, for every radar station. This lets us monitor our accuracy, and helps us quantify how effective future improvements are.
&lt;/p&gt;

&lt;h2&gt;In conclusion...&lt;/h2&gt;

&lt;p&gt;
So Dark Sky consists of a number of different moving parts that all need to fit together to create accurate predictions and pretty animations. This post glosses over many of the details, but I hope it helps get across the essential process and our approach to short-term weather forecasting.
&lt;/p&gt;
&lt;p&gt;
It’s very much a numerical &amp; statistical approach, rather than a meteorological one. Since we’ve launched the Kickstarter project, we’ve had several people (many of them meteorologists) criticize it because it eschews physical modeling of atmospheric fluid dynamics. And it’s true that our system will never be able to predict six hours in the future, let alone a day from now. But by focusing on the immediate future, and leaving everything beyond to the meteorologists, we can create a highly accurate app that is useful in a surprisingly wide range of applications.
&lt;/p&gt;
&lt;p&gt;
So if you are excited by this as much as we are, please help us out by backing our Kickstarter project. Every dollar counts!
&lt;/p&gt;
&lt;p&gt;
&lt;a href="http://www.kickstarter.com/projects/jackadam/dark-sky-hyperlocal-weather-prediction-and-visuali"&gt;http://www.kickstarter.com/projects/jackadam/dark-sky-hyperlocal-weather-prediction-and-visuali&lt;/a&gt;
&lt;/p&gt;
&lt;img src="http://feeds.feedburner.com/~r/jackadamblog/~4/s7qe98fIbYQ" height="1" width="1"/&gt;</content>
 <feedburner:origLink>http://blog.jackadam.net/2011/how-dark-sky-works/</feedburner:origLink></entry>
 
 <entry>
   <title>How Small Can You Make Your Stupid Face?</title>
   <link href="http://feedproxy.google.com/~r/jackadamblog/~3/daX7f1nEpIw/" />
   <updated>2011-09-10T00:00:00-07:00</updated>
   <id>http://blog.jackadam.net/2011/how-small-can-you-make-your-stupid-face</id>
   <content type="html">&lt;h2&gt;Using High-Falutin’ Computer Vision Algorithms for Quantitative Face Reduction Analysis&lt;/h2&gt;
&lt;p&gt;When we saw this clip from the first episode of Workaholics on Comedy Central, it made us mad:&lt;/p&gt;
&lt;p&gt;&lt;iframe width="640" height="390" src="http://www.youtube.com/embed/tV4M7Bz79E8?rel=0&amp;amp;hd=1" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;&lt;/p&gt;
&lt;p&gt;Here you have an individual who is asked to judge which of his friends can make the smaller face, but instead of taking this task seriously he hastily declares a winner based on scant evidence and with no discernible justification!&lt;/p&gt;
&lt;p&gt;What does it say about the human race as a tool-building species that we still — after centuries of technological progress — have no means of &lt;em&gt;objectively&lt;/em&gt; quantifying a person’s face reduction aptitude?&lt;/p&gt;
&lt;p&gt;Videos such as this simply perpetuate (dare we say, celebrate?) a lack of scientific rigor.&lt;/p&gt;
&lt;p&gt;This will not stand. Jack and I knew we had to do something. So we built &lt;a href="http://tinyfaceapp.com"&gt;Tiny Face&lt;/a&gt;, the world’s first mobile app for scientifically determining one’s face reduction percentage.&lt;/p&gt;
&lt;p&gt;How it works is simple: The user is prompted to make their normal face followed by their tiny face, and the app then computes how much smaller the latter is compared to the former. It’s not about &lt;em&gt;absolute&lt;/em&gt; face size, but rather how much smaller you can compress your face relative to your baseline "normal" face. Here’s an example output:&lt;/p&gt;
&lt;p&gt;&lt;img src="http://my.tinyfac.es/1r.jpg" style="width:480px" class="aligncenter" /&gt;&lt;/p&gt;
&lt;p&gt;How does it work?&lt;/p&gt;
&lt;h2&gt;Face Detection&lt;/h2&gt;
&lt;p&gt;Face detection algorithms have been around for a while. We use &lt;a href="http://opencv.willowgarage.com/wiki/"&gt;OpenCV&lt;/a&gt;, an open-source library of computer vision algorithms (written in C, and easily &lt;a href="http://www.eosgarden.com/en/opensource/opencv-ios/documentation/tutorial/"&gt;compiled for the iPhone&lt;/a&gt;), to detect faces in the input image.&lt;/p&gt;
&lt;p&gt;&lt;img src="/images/tiny-face/1.jpg" class="aligncenter" /&gt;&lt;/p&gt;
&lt;p&gt;It’s pretty easy to use, since other people have done the hard work of training these magical algorithmic gizmos called &lt;a href="http://en.wikipedia.org/wiki/Haar-like_features"&gt;Haar Classifiers&lt;/a&gt; using collections of thousands of sample faces. We simply took this classifier and fed it into OpenCV along with the input image, and out comes a bounding box around any faces! Like magic!&lt;/p&gt;
&lt;p&gt;By doing this for both the normal face and the scrunched face, we get two bounding boxes whose areas we can compare to figure out how much smaller the face has become:&lt;/p&gt;
&lt;p&gt;&lt;img src="/images/tiny-face/3.jpg" class="aligncenter" /&gt;&lt;/p&gt;
&lt;h2&gt;Not So Fast!&lt;/h2&gt;
&lt;p&gt;This method only works in tightly controlled situations where the user and camera are stationary, and the only thing that changes is the face. But if we were to turn this into an iPhone app, we’d have to deal with the real-world where we can’t expect people to hold themselves and the phone still.&lt;/p&gt;
&lt;p&gt;The problem with simply comparing the bounding boxes of the normal and tiny faces is that moving the camera closer or father away results in unacceptable errors due to perspective scaling. For example:&lt;/p&gt;
&lt;p&gt;&lt;img src="/images/tiny-face/4.jpg" class="aligncenter" /&gt;&lt;/p&gt;
&lt;p&gt;We &lt;em&gt;could&lt;/em&gt; just ask users to keep their camera and face steady. But, as any software developer can tell you, most users are terrible awful cheating sons-of-bitches (haha! We love our users!).&lt;/p&gt;
&lt;p&gt;So how do we correct for this?&lt;/p&gt;
&lt;h2&gt;Feature Tracking&lt;/h2&gt;
&lt;p&gt;If we knew the distance from the camera to the face in both images, we could compensate for perspective. Unfortunately, there’s no way to get the &lt;em&gt;absolute&lt;/em&gt; distance from each image alone (until the iPhone comes equipped with 3D camera or laser range-finder).&lt;/p&gt;
&lt;p&gt;But we can &lt;em&gt;compare&lt;/em&gt; the two images, and employ a nifty trick:&lt;/p&gt;
&lt;p&gt;Take a look at the photos above. In the before and after images she’s changed the size of her face, but other aspects of her head / body are unaltered. These include her hairline, the shape of her head, shoulders, clothing, freckles, jewelry, etc.&lt;/p&gt;
&lt;p&gt;If we can identify these features in both images, we can compare how their sizes and positions differ and compute the &lt;em&gt;relative&lt;/em&gt; change in scale between them.&lt;/p&gt;
&lt;p&gt;Fortunately, OpenCV comes with &lt;a href="http://robots.stanford.edu/cs223b05/notes/CS%20223-B%20T1%20stavens_opencv_optical_flow.pdf"&gt;neat ways&lt;/a&gt; to identify and track features from one image to the next. Using the position of the face, we can draw a box around the likely location of the entire head and body, and then run these algorithms to find features in common:&lt;/p&gt;
&lt;p&gt;&lt;img src="/images/tiny-face/6.jpg" class="aligncenter" /&gt;&lt;/p&gt;
&lt;p&gt;By then comparing the distance between these points in both images, we can calculate by how much the camera has moved closer or farther away, and compensate accordingly. Presto!&lt;/p&gt;
&lt;p&gt;Actually, while this works in theory, it doesn’t always work in practice. In our tests we found that users move around quite a bit between images, leading to inconsistent lighting, blurring, and all sorts of similar nastiness.&lt;/p&gt;
&lt;p&gt;In fact, we weren’t able to find a single feature tracking algorithm that could handle all these situations. So we resorted to using multiple ones, including the &lt;a href="http://en.wikipedia.org/wiki/Lucas%E2%80%93Kanade_method"&gt;Lucas-Kanade method&lt;/a&gt; and &lt;a href="http://www.aishack.in/2010/05/sift-scale-invariant-feature-transform/"&gt;SIFT - Scale Invariant Feature Transform&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We run them all (after passing the images through various cleaning stages), and then "sanity check" the resulting numbers to determine which results are likely to be most accurate.&lt;/p&gt;
&lt;p&gt;So there you go. After countless hours of tweaking numbers and nearly spraining our faces generating hundreds of test images, we’d got ourselves our very first iOS app!&lt;/p&gt;
&lt;p&gt;You can check out user-submitted faces at &lt;a href="http://tinyfaceapp.com"&gt;Tiny Face website&lt;/a&gt; and buy the app — &lt;em&gt; only 99¢!&lt;/em&gt; — from the &lt;a href="http://itunes.apple.com/us/app/tiny-face/id461812078?ls=1&amp;mt=8"&gt;App Store&lt;/a&gt; for the iPhone 4, iPod touch and iPad 2.&lt;/p&gt;
&lt;p&gt;&lt;em style="color:#AA0000"&gt;WARNING FROM YOUR MOM: Prolonged use of this app can cause your face to freeze like that, young lady.&lt;/em&gt;&lt;/p&gt;
&lt;img src="http://feeds.feedburner.com/~r/jackadamblog/~4/daX7f1nEpIw" height="1" width="1"/&gt;</content>
 <feedburner:origLink>http://blog.jackadam.net/2011/how-small-can-you-make-your-stupid-face/</feedburner:origLink></entry>
 
 <entry>
   <title>The Tiny Humanity Bubble</title>
   <link href="http://feedproxy.google.com/~r/jackadamblog/~3/RvNkB4v_dX4/" />
   <updated>2011-02-04T00:00:00-08:00</updated>
   <id>http://blog.jackadam.net/2011/the-tiny-humanity-bubble</id>
   <content type="html">&lt;p&gt;Mankind has been broadcasting radio waves into deep space for about a hundred years now — since the days of Marconi.&lt;/p&gt;
&lt;p&gt;That, of course, means there is an ever-expanding bubble announcing Humanity’s presence to anyone listening in the Milky Way. This bubble is astronomically large (literally), and currently spans approximately 200 light years across.&lt;/p&gt;
&lt;p&gt;But how big is this, really, compared to the size of the Galaxy in which we live (which is, itself, just one of countless billions of galaxies in the observable universe)?&lt;/p&gt;
&lt;p&gt;To answer that question, I put together the following diagram of our galaxy with the "Humanity Bubble" embedded within it. You’ll need to click on it to get the &lt;a href="http://jackadam.net/misc/radio_broadcasts/radio_broadcasts.jpg"&gt;full resolution image&lt;/a&gt; and zoom in on the highlighted region.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://jackadam.net/misc/radio_broadcasts/radio_broadcasts.jpg"&gt;&lt;br /&gt;&lt;img src="http://jackadam.net/misc/radio_broadcasts/radio_broadcasts_thumb.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style="text-decoration:line-through"&gt;This makes me feel small, sad, and alone.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style="text-decoration:line-through"&gt;Hold me.&lt;/span&gt;&lt;/p&gt;
&lt;h3&gt;Update:&lt;/h3&gt;
&lt;p&gt;In response to my depression over the smallness of our presence, &lt;em&gt;johnohara&lt;/em&gt; on the &lt;a href="http://news.ycombinator.com/item?id=2185773"&gt;Hacker News thread&lt;/a&gt; says:&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;[That is] One man’s opinion.&lt;br /&gt;
  For me, I am grateful I live in a time when I can use a human invention to view images, taken by another human invention, of galaxies 13.5 billion light years away that probably no longer exist and be educated enough to sit down and calculate in terms of miles just how far those specks of light have traveled.
  Aristotle, Caesar, DaVinci, Newton, Kepler, Napoleon, Faraday and Einstein never saw what I have seen from my desktop.
  Sad? No. Privileged.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;em&gt;thasc&lt;/em&gt;, in the comments below, concurs:&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;For a tiny, young little species, that’s one heck of a lot of ground covered! We actually register as visible on a relatively low-res image of the Galaxy! Go us!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Indeed.&lt;/p&gt;
&lt;p&gt;(p.s. Since we’re inside the Milky Way, obviously we cannot take photographs of it. This is an artistic rendering of what it might look like, which I &lt;a href="http://commons.wikimedia.org/wiki/File:Milky_Way_Galaxy.jpg"&gt;stole from Wikipedia&lt;/a&gt;. The artist is Nick Risinger.)&lt;/p&gt;
&lt;img src="http://feeds.feedburner.com/~r/jackadamblog/~4/RvNkB4v_dX4" height="1" width="1"/&gt;</content>
 <feedburner:origLink>http://blog.jackadam.net/2011/the-tiny-humanity-bubble/</feedburner:origLink></entry>
 
 <entry>
   <title>3D Rotating Molecules on the iPhone/iPad</title>
   <link href="http://feedproxy.google.com/~r/jackadamblog/~3/8L9IOJRuDcU/" />
   <updated>2010-10-29T00:00:00-07:00</updated>
   <id>http://blog.jackadam.net/2010/3d-rotating-molecules-on-the-iphoneipad</id>
   <content type="html">&lt;p&gt;&lt;a href="http://demos.jackadam.net/webkit_molecule/"&gt;&lt;img src="/images/3d-molecules/molecule.png" alt="" title="molecule" width="650" class="alignnone size-full wp-image-81" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Turns out, &lt;a href="http://blog.jackadam.net/2010/the-unfortunate-state-of-canvas-animations-on-the-iphone-ipad/"&gt;canvas kinda sucks&lt;/a&gt; for developing full-screen animations on the iPhone and iPad. Without hardware acceleration and a more efficient clearing mechanism, it just doesn’t cut the mustard.&lt;/p&gt;
&lt;p&gt;But all hope is not lost... CSS 3D transforms to the rescue!&lt;/p&gt;
&lt;p&gt;Check out this &lt;a href="http://demos.jackadam.net/webkit_molecule/"&gt;3D molecule simulation&lt;/a&gt; we put together:&lt;/p&gt;
&lt;p&gt;&lt;iframe src="http://player.vimeo.com/video/16280629" width="640" height="360" frameborder="0"&gt;&lt;/iframe&gt;&lt;/p&gt;
&lt;p&gt;You can rotate the molecule around with your fingers, and pinch to zoom in and out. At blisteringly high frame rates. &lt;a href="http://demos.jackadam.net/webkit_molecule/"&gt;Try it out&lt;/a&gt; yourself.&lt;/p&gt;
&lt;p&gt;(It currently works on iOS devices, Safari, and the latest dev version of Chrome... basically, any browser that supports true CSS 3D transformations / animations.)&lt;/p&gt;
&lt;p&gt;So how does it work?&lt;/p&gt;
&lt;p&gt;Take a gander at this &lt;a href="http://demos.jackadam.net/rotation/1.html"&gt;simplified example&lt;/a&gt;, consisting of four rotating circles:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://demos.jackadam.net/rotation/1.html"&gt;&lt;img src="/images/3d-molecules/rotation1.jpg" alt="" title="rotation1" width="650" class="alignnone size-full wp-image-73" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;We apply the following CSS animation to the container holding the circles...&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;code class="css"&gt;&lt;span class="nf"&gt;#container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;webkit&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;preserve&lt;/span&gt;&lt;span class="m"&gt;-3&lt;/span&gt;&lt;span class="err"&gt;d&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;webkit&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;animation&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;spin&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;webkit&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;animation&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;duration&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;webkit&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;animation&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;iteration&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;infinite&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;webkit&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;animation&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;timing&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;linear&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    
&lt;span class="k"&gt;@-webkit-keyframes&lt;/span&gt; &lt;span class="nt"&gt;spin&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nt"&gt;0&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;webkit&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rotateY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="n"&gt;deg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nt"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;webkit&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rotateY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;360&lt;/span&gt;&lt;span class="n"&gt;deg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Which tells the container to spin around the y-axis perpetually. Notice, however, that the circles don’t look like three-dimensional spheres... instead, they appear as flat circles painting onto the plane of the container. In order to simulate the atoms of a molecule, we need honest-to-goodness 3D balls.&lt;/p&gt;
&lt;p&gt;To accomplish this in CSS, we take advantage of a technique called "billboarding". Billboarding involves rotating an object (in our case, each of the colored balls), in such as way that it remains facing directly at the camera at all times. This is a fairly common technique in the world of 3D gaming, used to render things such as trees and grass — and in our case, it gives the illusion that the circles are really spheres.&lt;/p&gt;
&lt;p&gt;Now, there isn’t any built-in method for billboarding an object using CSS 3D transforms, but its easy to fake: We simply need to rotate each circle individually in the &lt;em&gt;opposite direction&lt;/em&gt; from the rotation of the parent container. We do this by defining an equal-but-opposite CSS animation, and apply it to the balls:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;code class="css"&gt;&lt;span class="nc"&gt;.ball&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;webkit&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;preserve&lt;/span&gt;&lt;span class="m"&gt;-3&lt;/span&gt;&lt;span class="err"&gt;d&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;webkit&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;animation&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;spin&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;webkit&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;animation&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;duration&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;webkit&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;animation&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;iteration&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;infinite&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;webkit&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;animation&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;timing&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;linear&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    
&lt;span class="k"&gt;@-webkit-keyframes&lt;/span&gt; &lt;span class="nt"&gt;reverse-spin&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nt"&gt;0&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;webkit&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rotateY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;360&lt;/span&gt;&lt;span class="n"&gt;deg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nt"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;webkit&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rotateY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="n"&gt;deg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Notice that the reverse-spin animation starts at 360 degrees and ends at 0 degrees, whereas the original spin animation was the opposite. Ta da! The results can be &lt;a href="http://demos.jackadam.net/rotation/2.html"&gt;seen here&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://demos.jackadam.net/rotation/2.html"&gt;&lt;img src="/images/3d-molecules/rotation2.jpg" alt="" title="rotation2" width="650" class="alignnone size-full wp-image-74" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Things become a little more complicated when it comes to arbitrary rotations about multiple axes in response to touch gestures (as seen in the molecule demo), but you get the idea! Pretty neat, huh?&lt;/p&gt;
&lt;img src="http://feeds.feedburner.com/~r/jackadamblog/~4/8L9IOJRuDcU" height="1" width="1"/&gt;</content>
 <feedburner:origLink>http://blog.jackadam.net/2010/3d-rotating-molecules-on-the-iphoneipad/</feedburner:origLink></entry>
 
 <entry>
   <title>The Unfortunate State of Canvas Animations on the iPhone / iPad</title>
   <link href="http://feedproxy.google.com/~r/jackadamblog/~3/ZJixzY4Q_WY/" />
   <updated>2010-10-28T00:00:00-07:00</updated>
   <id>http://blog.jackadam.net/2010/the-unfortunate-state-of-canvas-animations-on-the-iphone-ipad</id>
   <content type="html">&lt;p&gt;Over the next several months, Jack and I will be building a series of fun little interactive online games for a client. In years past, these type of (animation-heavy) games would have undoubtedly been built in Flash. Our client, however, is forward-thinking enough to let us try ditching Flash and implementing them all with HTML / CSS / JS (a.k.a. “HTML5”).&lt;/p&gt;
&lt;p&gt;Part of the rationale of moving away from Flash is that we want (at least some of) these games to be playable on the iPad and iPhone. The target demographic is younger school-age kids, and the iPad is poised to &lt;a href="http://speirs.org/blog/2010/10/1/ipad-apps-for-primary.html"&gt;take off&lt;/a&gt; in that space.&lt;/p&gt;
&lt;p&gt;Now, when it comes to building rich, graphical, animated games in HTML5, the obvious first step is to investigate Canvas. We’ve done so, and we’ve come to a couple conclusions:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Canvas is entirely unsuited to generating full-screen animations on any iOS device. The frame-rates are unacceptably low.&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Clearing the canvas between frames is surprisingly expensive.&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here’s the test I used, so you can try it out yourself:&lt;br /&gt;
&lt;a href="http://demos.jackadam.net/canvas_test/"&gt;http://demos.jackadam.net/canvas_test&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It’s pretty much the simplest animation you can perform with canvas... drawing a single circle moving across the screen.&lt;/p&gt;
&lt;p&gt;If you don’t have an iPhone / iPad, here’s a video of the test in action:&lt;/p&gt;
&lt;p&gt;&lt;iframe src="http://player.vimeo.com/video/16280618" width="640" height="360" frameborder="0"&gt;&lt;/iframe&gt;&lt;/p&gt;
&lt;p&gt;As you can see, frame-rates on the iPad vary from "unacceptably bad" at fullscreen to "okay, I guess" at the relatively tiny resolution of 250x250.&lt;/p&gt;
&lt;p&gt;And notice how much snappier the animation becomes when we stop clearing the canvas between frames! Canvas was originally designed to generate static images, not animations, and the standard canvas clearing method (calling &lt;code&gt;ctx.clearRect(0, 0, width, height)&lt;/code&gt;) appears to be quite expensive.&lt;/p&gt;
&lt;p&gt;I did experiment with alternative clearing methods, including:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Creating an empty canvas on page load, grabbing its contents with &lt;code&gt;getImageData&lt;/code&gt; and sending it to the canvas on the page with &lt;code&gt;putImageData&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Replacing the canvas element with a new one each frame.&lt;/li&gt;
  &lt;li&gt;Forcing a refresh of the canvas element by resetting it’s width every frame (Using &lt;code&gt;canvas.width = canvas.width+0&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of which you can experiment with in the above demo. None of these, however, improved clearing performance significantly.&lt;/p&gt;
&lt;p&gt;As a result of these tests, we’ve decided to ditch canvas as a viable option for fullscreen games on iOS.&lt;/p&gt;
&lt;p&gt;(And straight-up DOM manipulation via Javascript doesn’t fare much better when you’re doing anything more complicated than moving a handful of elements around.)&lt;/p&gt;
&lt;p&gt;That only leaves one option for us: CSS transitions.&lt;/p&gt;
&lt;p&gt;Stay tuned...&lt;/p&gt;
&lt;img src="http://feeds.feedburner.com/~r/jackadamblog/~4/ZJixzY4Q_WY" height="1" width="1"/&gt;</content>
 <feedburner:origLink>http://blog.jackadam.net/2010/the-unfortunate-state-of-canvas-animations-on-the-iphone-ipad/</feedburner:origLink></entry>
 
 <entry>
   <title>JPEGs with Alpha Channels?!?</title>
   <link href="http://feedproxy.google.com/~r/jackadamblog/~3/T7odrotc898/" />
   <updated>2010-10-04T00:00:00-07:00</updated>
   <id>http://blog.jackadam.net/2010/alpha-jpegs</id>
   <content type="html">&lt;p&gt;&lt;em&gt;I wanted a reasonably sized photographic image with a 24-bit alpha channel. So I used a JPEG for what JPEGs are good for and a PNG for what PNGs are good for...&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I combined them using an HTML5 &lt;code&gt;canvas&lt;/code&gt; element and then inserted into the DOM. The results look the same as using a normal 24-bit PNG but are one-half to one-sixth the size. In one case we got a 573KB 24-bit PNG down to a 49KB JPEG with a 4KB PNG alpha-mask!&lt;/p&gt;
&lt;p&gt;On a recent project I did this using jQuery and scanning the CSS for &lt;code&gt;background-images&lt;/code&gt;, but this could easily be achieved without any special CSS using valid HTML5 syntax by referring to a normal JPEG throug the &lt;code&gt;src&lt;/code&gt; of an &lt;code&gt;img&lt;/code&gt; element and adding a new &lt;code&gt;data-alpha-src&lt;/code&gt; attribute with the URL of a 24-bit PNG that is just a mask.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;code class="html"&gt;&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;image.jpg&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;data-alpha-src=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;alpha.png&amp;quot;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;&lt;a href="http://demos.jackadam.net/alpha_jpeg/"&gt;This little demo&lt;/a&gt;&lt;sup&gt;&lt;a href="#footnote_0_7" id="identifier_0_7" class="footnote-link footnote-identifier-link" title="Plastics trucker hats are available from Americorp."&gt;1&lt;/a&gt;&lt;/sup&gt; works in the newest versions of Firefox, Chrome, and Safari (including Safari for iOS). For Internet Explorer support, I used the wonderful &lt;a href="http://flashcanvas.net"&gt;FlashCanvas&lt;/a&gt;&lt;sup&gt;&lt;a href="#footnote_1_7" id="identifier_1_7" class="footnote-link footnote-identifier-link" title="This technique uses the globalCompositeOperation operation, which requires FlashCanvas Pro. Free for non-profit use or just $31 for a commercial license."&gt;2&lt;/a&gt;&lt;/sup&gt;. It’s fine in IE8 but it’s inconsistent in IE7. This might be solved by waiting for DOM to be ready. At the time of this writing the IE9 beta &lt;a href="http://ie.microsoft.com/testdrive/info/ReleaseNotes/Default.html#WhatsNew"&gt;does not support &lt;code&gt;globalCompositeOperation&lt;/code&gt;&lt;/a&gt;, so we’ll have to wait and see.&lt;/p&gt;
&lt;p&gt;Recently we’ve taken on two different projects that involve lots of large sprites being animated around landscapes. Normally sites like this (we typically see them done in Flash) necessitate painful loading screens. By using this JPEG-alpha trick we should be able to keep load times to a minimum.&lt;/p&gt;
&lt;p&gt;Here is the JavaScript code from the demo:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;code class="javascript"&gt;&lt;span class="p"&gt;;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;create_alpha_jpeg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;alpha_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;data-alpha-src&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;alpha_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="c1"&gt;// Hide the original un-alpha&amp;#39;d&lt;/span&gt;
    &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;visiblity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;hidden&amp;#39;&lt;/span&gt;

    &lt;span class="c1"&gt;// Preload the un-alpha&amp;#39;d image&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;img&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;
    &lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

      &lt;span class="c1"&gt;// Then preload alpha mask&lt;/span&gt;
      &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;alpha&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;img&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nx"&gt;alpha&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;alpha_path&lt;/span&gt;
      &lt;span class="nx"&gt;alpha&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;canvas&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;
        &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;
        &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parentNode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replaceChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;// For IE7/8&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;FlashCanvas&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;undefined&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;FlashCanvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;initElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;// Canvas compositing code&lt;/span&gt;
        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2d&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clearRect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;drawImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;globalCompositeOperation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;xor&amp;#39;&lt;/span&gt;
        &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;drawImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;alpha&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Apply this technique to every image on the page once DOM is ready&lt;/span&gt;
  &lt;span class="c1"&gt;// (I just placed it at the bottom of the page for brevity)&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;imgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementsByTagName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;img&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;lt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;imgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;create_alpha_jpeg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;imgs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="p"&gt;})();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;In the &lt;code&gt;head&lt;/code&gt; element I linked to FlashCanvas:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;code class="html"&gt;&lt;span class="c"&gt;&amp;lt;!--[if lte IE 8]&amp;gt;&amp;lt;script src=&amp;quot;flashcanvas.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&amp;lt;![endif]--&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;... and I threw in this to avoid a flicker of the un-alpha’d JPEG:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;code class="html"&gt;&lt;span class="nt"&gt;&amp;lt;style&amp;gt;img&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;data-alpha-src&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="k"&gt;visibility&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;ol class="footnotes"&gt;
  &lt;li id="footnote_0_7" class="footnote"&gt;&lt;em&gt;Plastics&lt;/em&gt; trucker hats are available from &lt;a href="http://www.cafepress.com/americorp"&gt;Americorp&lt;/a&gt;. [&lt;a href="#identifier_0_7" class="footnote-link footnote-back-link"&gt;&amp;#8617;&lt;/a&gt;]&lt;/li&gt;
  &lt;li id="footnote_1_7" class="footnote"&gt;This technique uses the &lt;code&gt;globalCompositeOperation&lt;/code&gt; operation, which requires FlashCanvas Pro. Free for non-profit use or just $31 for a commercial license. [&lt;a href="#identifier_1_7" class="footnote-link footnote-back-link"&gt;&amp;#8617;&lt;/a&gt;]&lt;/li&gt;
&lt;/ol&gt;&lt;img src="http://feeds.feedburner.com/~r/jackadamblog/~4/T7odrotc898" height="1" width="1"/&gt;</content>
 <feedburner:origLink>http://blog.jackadam.net/2010/alpha-jpegs/</feedburner:origLink></entry>
 
 
</feed>
