Originally posted to https://medium.com/this-place/translucify-374ec3f621c5
Translucify is a drop-in solution for mismatched background colors between a web page and its images.
- Approximately 230 SLOC (source lines of code)
- No dependencies
A classic way of improving the readability of a table with lots of rows is to set slightly different background colors for alternating rows. This all works nicely when you’re in control of the content that lives inside a row but what happens when the row has to show a thumbnail in one of its cells?
If there are only 3 different images and our prototype hard-codes the links to them then there’s an easy solution: go into Photoshop and delete the backgrounds. But what if we didn’t know where the images came from and if their format even supports transparency? JPEG, for instance, doesn’t save transparency data.
We can’t just go into Photoshop and manually delete the backgrounds ourselves, especially if there are potentially 2000 different images and they are served off a site we lack full access to. It was such an obvious UX issue to us that we felt it was something that had to be addressed immediately.
We thought it such a common problem people would’ve run into that there must already be a solution someone else wrote, right?
“I suppose it is tempting, if the only tool you have is a hammer, to treat everything as if it were a nail.” — Abraham Maslow, The Psychology of Science, 1966
If we could do it client-side, it would be an easy win for UX, rather than chase the image providers down and getting them to change every single image we could possibly use in this way. The only snagging point we could see was Cross-Origin Resource Sharing (CORS) headers. These are settings which control how a web browser uses images that come from a different domain than the page requesting the image (index.html on thisplace.com vs cheese.jpg on wikimedia.org). As long as the domain hosting the image (wikimedia.org) has correctly setup CORS header on images (which is to say it’s enabled for thisplace.com), it shouldn’t be a big deal.
StackOverflow threads were our first legit results in our quest to find an existing solution to this problem. This one was especially relevant to the task of manipulating image data client-side. It’s pretty clear the end result should be a canvas tag that has the same image content as the original only with the background color removed.
The scope of the solution was clear now: create a one off, single purpose micro-library, using the code from the StackOverflow answer as a starting point. The background color has to be deleted, the resulting element should behave like a regular image element as much as possible (in-line element, easy to style with CSS and retain the ability for users to save that image). We’d also want to stick it in a micro-library module (that is CommonJS, AMD and plain
<script> tag compatible) because we don’t mind how it’s used as long as it’s helpful to someone later.
We couldn’t find any existing libraries that worked like this, which is why we wrote our own.
Diamond in the rough
This naive method had a problem though. There could be many false positives inside the image of a shiny item since the code was using a high pass filter on all the pixels.
The solution to this was to add a flood-fill method that works like the magic wand in Photoshop and replaces all the pixels starting from X:0, Y:0. The flood-fill method worked like a charm.
Implementation-wise, the library uses no bleeding-edge APIs or features. The only thing we needed to do was shim Function.bind() (an issue well known with PhantomJS <2) for our screenshot image diff tests.
Going further, we wanted to gauge how well either of these methods performed across browsers and devices. We wrote a jsPerf test case: this actually showed us something we didn’t expect but made perfect sense. The simpler high pass filter method of translucifying images was slower than the flood-fill method by a factor of more than 10!
The takeaway from this exercise was an immediate change to remove the high-pass method from the code since it was both slower and less useful.