The year was 2011. I was a young, aspiring software developer working in my first position in the industry.
In our app we had a web view, which we could use to display dynamic content. As mobile connections were at 2011 speeds, we had a hard limit on the size of each web view page - including all assets and code. That was usually not a problem, until one day I had to implement a design where the background was a just-slightly-grainy grey.
Grainy = white noise.
That was a problem. To understand why, let's talk about image compression.
Image Compression in a Nutshell
The images you see on the web are usually compressed; they can use many different methods in order to save space, but all of them can be categorised into two groups:
- Lose some of the data (This is called lossy compression, and includes JPEG and GIF, among others)
- Represent the data in a way that takes less space (this is called lossless compression, and you might best know it through the PNG format)
Lossy compression of images is commonly visible. JPEG artifacts are a known side effect of JPEG compression, and in GIFs you could recognise "rounding up" of colors, as it's limited to 256 colors. these methods could work fine for many things (e.g. photography in JPEG), but might be too much where small, clean lines/details are needed. Our white-noise-textured background fell in the second category.
If not JPEG or GIF, I tried to save the background as PNG. The file was BIG.
You Can't (Losslessly) Compress White Noise
The main method I know for lossless compression involves finding repeating patterns, replacing them with a shorter representation and keeping the replacement in a dictionary. For example, in the string aaabaaacaaad
, we have the substring aaa
appearing a few times.
We can shorten it by replacing it with a single character; let's call it 1. We now have a compressed string - 1b1c1d
, and a dictionary - 1:aaa
When displaying the string, we use the dictionary to replace the shorter substring back to the original one, and get aaabaaacaaad
again.
Different formats differ in the way they find what to replace, how many times they return the process, and what of many other optimisations they use. However, these basics are commonly used.
Back to our noisy image - white noise is, by definition, random; it is a noise generated by receiving different values in a range with equal probability. Being random, the chance for repeating patterns is very low, and lossless compression in the way we described is not very effective.
What If We Won't Load This At All?
We'll still have noise, don't worry, but we'll have to deal with it differently:
- Load the page, with a background color or image, but without the noise.
- Use a canvas to generate white noise
- Give the canvas a very low opacity
- Position it above the background
- Mission accomplished!
White noise, being simply a uniform random grey value on each pixel, is very easy to generate. You iterate over the canvas' width and height, and color each pixel in a random shade of grey.
It worked! The total page size was now small enough to fit our hard size limit. However, my state of the art iPhone 4, and all other mobile devices we've tested, took a noticeable time to run this code. This wasn't a good user experience, so we've had to find another solution.
Tiles
The next plan was to use the existing generator to generate a much smaller area of white noise, and use it as a tile, a repeating background. As it has a low opacity and was used as a texture, people wouldn't easily see it repeats, and we'd have a nice, elegant background texture that is quick to generate.
This also meant that I couldn't simply use the canvas above the background, as I'd then have to fill it. Instead, I used an invisible canvas to generate the white noise tile, then used that tile, in the format of a data URI, as a background to a different element.
Thankfully, canvases have long provided this functionality natively.
This is the process we ended up with:
- Load the page, with a background color or image, but without the noise.
- Use an invisible canvas to generate a low-opacity white noise tile.
- Get the canvas image as a data URI, and use that as a second
background-image
for the container element (together with the untextured image), or as a background-image over a color background.
Since the texture was very subtle, we could get away with texture repetition without it being noticed - as long as the tile wasn't very small. A few attempts to balance tile size (repetition vs. loading speed) and how visible the repetition was, and we've had a working feature :)
What Should We Take From This?
- It's good to know how things work, even if you only work with them indirectly. By knowing some basics about image compression and white noise, for example, I could find the source of a non-trivial front-end problem, and solve it.
- Beware of focusing too much on the things you directly need for work. Learning a bit about things outside of your daily line of work, in addition to being interesting, could help you out later.
Top comments (5)
Great story! I'm not familiar with the canvas system so I would have asked the design team to give me a small tile image to position, or to just make do with gray. I'm really glad the solution wasn't to just increase the transfer limit.
Sometimes, compromising the design is the right solution. Fortunately, a combination of a designer unhappy about giving up the texture and enough time to figure this out allowed us to implement this solution in the end :)
I wasn't familiar with the canvas API either, but knowing OF it, and what is white noise, is enough to google and learn what you need as you go.
Even though I might never have this specific use case replicated, I find myself enraptured in reading this and learning how you solved such a specific problem. Great post!
Thanks!
Yeah, this is a VERY specific use case :)
I might use the different things I learned while working on this to solve other problems.
It's nice to see a creative solution come to fruition. 😁