The Pixel Density Explosion Wednesday, April 10 2013

Back in mid-2012, we didn't have that many unique pixel density values; just 1, 1.5, 2, and 2.25, plus variations based on zoom size. Since then, we've had an explosion of devices with high-resolution displays (adding 1.75, 2.5, 3, etc) and this continues to grow.

I'm very concerned that the current srcset and picture elements are jumping on the dppx/ pixel density bandwagon without considering that they're introducing exponential complexity for authors. If v is viewport sizes in virtual pixels we optimize for and d is pixel densities we want to support crisply without waste, we must describe v*n number of images. This is untenable.

Photographs don't care about dppx. Photos are crisp if there's a 1-1 mapping between physical and bitmap pixels. If the browser has decent scaling, additional bitmap pixels are acceptable too. It's generally a good thing if your eyes can't make out the individual pixels of a photo.

I also prefer to pinch-zoom in on a semantic photo instead of having the context cropped automatically for me. Perhaps others find it natural to have content changed on browser resize, but I find it unsettling.

Note I used the term photographs, not images. Text and layout really need virtual pixels and the dppx abstraction to ensure readability and usability - as do images that contain text or affect layout (although you should be using SVG).

What this means in terms of markup

This is where we're headed with the current picture spec and the pixel density explosion.

We're only handling the standard Bootstrap breakpoints, assuming a full-screen image, and the 6 most popular pixel densities here. We're not even handling smaller mobile devices well here, nor are we doing a great job of bandwidth optimization. We're just doing a somewhat acceptable job of delivering a crisp image that doesn't waste more than 60% of the bandwidth used.

<picture>
   <source media="(min-width: 941px)" srcset="http://cdn.company.com/site/images/1200_x1.jpg 1x, http://cdn.company.com/site/images/1200_x1_5.jpg 1.5x http://cdn.company.com/site/images/1200_x2.jpg 2x http://cdn.company.com/site/images/1200_x2_25.jpg 2.25x http://cdn.company.com/site/images/1200_x2_5.jpg 2.5x http://cdn.company.com/site/images/1200_x3.jpg 3x">
   <source media="(min-width: 768px) and (max-width: 940px)" srcset="http://cdn.company.com/site/images/940_x1.jpg 1x, http://cdn.company.com/site/images/940_x1_5.jpg 1.5x http://cdn.company.com/site/images/940_x2.jpg 2x http://cdn.company.com/site/images/940_x2_25.jpg 2.25x http://cdn.company.com/site/images/940_x2_5.jpg 2.5x http://cdn.company.com/site/images/940_x3.jpg 3x">
   <source media="(min-width: 480px) and (max-width: 767px)" srcset="http://cdn.company.com/site/images/767_x1.jpg 1x, http://cdn.company.com/site/images/767_x1_5.jpg 1.5x http://cdn.company.com/site/images/767_x2.jpg 2x http://cdn.company.com/site/images/767_x2_25.jpg 2.25x http://cdn.company.com/site/images/767_x2_5.jpg 2.5x http://cdn.company.com/site/images/767_x3.jpg 3x">
   <source srcset="http://cdn.company.com/site/images/480_x1.jpg 1x, http://cdn.company.com/site/images/480_x1_5.jpg 1.5x http://cdn.company.com/site/images/480_x2.jpg 2x http://cdn.company.com/site/images/480_x2_25.jpg 2.25x http://cdn.company.com/site/images/480_x2_5.jpg 2.5x http://cdn.company.com/site/images/480_x3.jpg 3x">
   <img src="http://cdn.company.com/site/images/480_x1.jpg" alt="">
   <p>Accessible text</p>
</picture>

This is not a good direction

So… that's pretty bad for a single image. We're definitely leaving the door open for 'art direction', but is this overhead worth it?

We've described 24 image URLs with the following physical widths:

  • 1200 - 1800 - 2400 - 2700 - 3000 - 3600
  • 940 - 1410 - 1880 - 2115 - 2350 - 2820
  • 767 - 1150 - 1534 - 1726 - 1917 - 2301
  • 480 - 720 - 960 - 1080 - 1200 - 1440

This is a crazy amount of duplication.

Despite providing so many resolutions, we're only supporting 4 breakpoints well, and either wasting or poorly supporting the rest. This is wasteful.

If we de-duplicate those resolutions, we get 9 sizes, and we can even add '640' to provide more granularity.

  • 480 - 640 - 780 - 960 - 1200 - 1440 - 1800 - 2400 - 3000 - 3600

Speaking in device pixels is good

By describing 'true pixels' instead of virtual pixels, the browser can use the same image and declaration for a 480px 2dppx viewport and a 960 1dppx viewport.

This allows us to reduce asset count by 60%, markup by ~75%, and lower our bandwidth waste tolerance from 60% to 20%.

<picture>
  <source src="http://cdn.company.com/site/images/3600.jpg" w="3600" />
  <source src="http://cdn.company.com/site/images/3000.jpg" w="3000" />
  <source src="http://cdn.company.com/site/images/2400.jpg" w="2400" />
  <source src="http://cdn.company.com/site/images/1800.jpg" w="1800" />
  <source src="http://cdn.company.com/site/images/1440.jpg" w="1400" />
  <source src="http://cdn.company.com/site/images/1200.jpg" w="1200" />
  <source src="http://cdn.company.com/site/images/960.jpg" w="960" />
  <source src="http://cdn.company.com/site/images/780.jpg" w="780" />
  <source src="http://cdn.company.com/site/images/640.jpg" w="640" />
  <source src="http://cdn.company.com/site/images/480.jpg" w="480" />
   <img src="http://cdn.company.com/site/images/480.jpg" alt="">
   <p>Accessible text</p>
</picture>

Isn't that simpler? And we're supporting a wider variety of viewport sizes and device pixel densities.

This doesn't preclude media queries or art direction, they have a place

Because pinch-to-zoom is nearly ubiqitous, I feel that it's generally a waste of time to prepare 2 photographs of identical dimensions with different content, but there is a use-case.

I am NOT advocating the removal of the media attribute from source, only that we permit a descriptive syntax as shown above, instead of an essentially imperative/declarative method. Images with text need to be modified for 2dppx and 3dppx displays; they'll be unreadable as-is. Either media="(min-device-pixel-ratio:2dppx)" or srcset can be used, but I favor using media as it simplifies and flattens the evaluation logic for both the browser and the human mind. It's good to evaluation logic in a single dimension.

srcset has the same problem.

Solving the srcset problem is more difficult (syntatically) if we also wish to support virtual viewport sizes AND pixel density selectors. Suggestions are welcome.

Clarification on scope.

Unlike my last proposal regarding the 'picture' element, I'm not trying to address the argument of viewport vs. element context for image URL selection. That's a separate concern I'll write about later.

For now, assume I'm talking about viewport-based evaluation unless I state otherwise.

This article is about device pixel ratio and why the popular use syntax should simply describe bitmap dimensions.

View comments on original page...

Why don't we have CSS 'image-fit' yet? Monday, April 8 2013

When both width and height are specified for an image, the standard behavior is to stretch the image to fit the new aspect ratio.

I'm highly curious if image distortion has ever been a useful behavior pattern.

Why doesn't 'image-fit' already exist?

image-fit: stretch; /* 'stretch-scale' The current default */

image-fit: crop; /* 'crop-scale' Scale down to largest dimension, crop to fit new aspect ratio */
image-fit: crop-downscale; /* Pad instead of upscaling if the edges don't reach borders*/
image-fit: crop-noscale; /* Crop without scaling */

image-fit: pad; /* Scale to fit bounding box, pad other axis*/
image-fit: pad-downscale; /* Expand the canvas instead of upscaling a tiny image */

After 6 years of working with ImageResizer customers, I've identified the above 6 'fit modes' as being 'in real-world use'. Only crop, pad, and pad-downscale are very popular, however.

The above fit modes do not attempt to address alignment or focus points. Ideally, we would standardize an image metadata tag to describe the focus points within the image; however, a CSS-based approach is actually feasbile.

image-fit-align: x-percent, y-percent;  /* Center the given point within the bounding box*/

image-fit-preserve: x-pct,y-pct, x2pct,ypct, ...; /* Never crop off any of these points */

The simply polygon math required to implement all these features only requires about 60 lines of code (ImageResizer implements a superset of these features). I'm willing to assist any browser implementors.

'image-fit' and friends can pave the way for simpler solutions to responsive imaging

A current point of contention between proponents of various responsive imaging solutions is 'element' vs 'viewport' media query evaluation. I think both are important, but element/context-based source selection faces a unique challenge: how do we prevent infinite loops?

The premise everyone assumes is that the only fit behavior possible for img or picture is stretch-scale. If we solve this problem, we can force boundaries on images without destroying aspect ratio.

While we're at it..

It would be nice to tell browsers which filters will work best with our images.

image-scale-filter: bicubic-sharper | bicubic-smoother | bilinear | nearest-neighbor | fant; 
View comments on original page...

Setting up a new rMBP for Ruby development Monday, April 1 2013

I've had the same filesystem for roughly 7 years, copying it across 4 hard drives and 2 laptops, and installing 4 new OS versions along the way without a clean install. Both my Mid 2012 17" MBP and my new 15" rMBP have 512GB SDD drives, but according to BlackMagic, the rMBP is 2-4x faster on both reads and writes.

Needless to say, my copy of OS X is thoroughly unique by this point, and not completely in a good way.

I've decided to document my steps for setting up a new OS X development environment, now that it's much simpler thanks to Homebrew, rbenv, npm, etc.

1. Make OS X capable of building software from source code

  1. Install XCode from the App Store
  2. Open XCode, go to Preferences -> Downloads -> Components and install "Command Line Tools". Now you can build from source.

2. Install Homebrew. Package managers are required!

ruby -e "$(curl -fsSL https://raw.github.com/mxcl/homebrew/go)"
brew doctor

If brew doctor gives you a clean bill of health, you can continue.

3. Install git, awk, wget, and any other command-line tools you need

brew install git awk wget 

Tip: Dotfiles and profile scripts are hidden by default. Make finder display all files by running

defaults write com.apple.Finder AppleShowAllFiles YES

Then alt-clicking the Finder icon and relaunching it. To undo, re-run with "NO" instead of "YES".

4. Install rbenv and ruby

rbenv allows you to have multiple side-by-side installations of Ruby, and lets you set the preferred version via hint files or environment variables.

We'll install rbenv, tell it to run by default on all bash terminals, and then install ruby-build so we can install ruby versions through rbenv (although you're free to install ruby in any manner with rbenv, this is just the easiest).

brew install rbenv
echo 'if which rbenv > /dev/null; then eval "$(rbenv init -)"; fi' >> ~/.bash_profile
brew install ruby-build

Now let's see what versions of ruby are available. As I write this, 2.0.0-p0 and 1.9.3-p392 are the main versions you'll need.

rbenv install -l

rbenv install 1.9.3-p392
rbenv install 2.0.0-p0
rbenv rehash

Now you can install bunder for both versions of ruby

rbenv shell 1.9.3-p392
gem install bundler

rbenv shell 2.0.0-p0
gem install bundler
rbenv rehash

rbenv shell --unset

To set a certain project to use a certain ruby version, change to that directory in Terminal and run

rbenv versions # Show installed and active versions

rbenv local 1.9.3-p327 # Set the ruby verson for this folder by creating a `.ruby-version` file.

Now you can run bundle install on your projects. Just remember to run rbenv rehash after any new package installation which includes binaries. Running rbenv rehash tells it to ensure binstubs are configured for all currently installed ruby versions.

Unlike RVM, rbenv doesn't rewrite your shell commands, so you need to type 'bundle exec ' in front of any gem binaries you call from the command line. I suggest making an alias:

alias b='bundle exec'

5. Install Node and NPM (Node Package Manager)

brew install node
curl https://npmjs.org/install.sh | sh
export NODE_PATH="/usr/local/lib/node"
export PATH="/usr/local/share/npm/bin:$PATH"

Now you can install node packages, like bower (yet another package manager - but for assets):

sudo npm install -g bower

6. Install Python

Homebrew can install Python2 and Python3 side-by-side

brew install python

brew install python3

They include pip and pip3 respectively, so you're set to install all the packages you want; here's a few that are commonly needed:

pip install numpy ipython scipy pil

Some (like wxWidgets, OpenCV, and PngCrush) are platform-level libraries with python bindings. These you install with brew.

brew install --python wxmac --devel

sudo ln -s /usr/local/Cellar/wxmac/2.9.4.0/lib/python2.7/site-packages/wx /Library/Python/2.7/site-packages/wx

brew install opencv
brew install pngcrush

Important: Do not use brew install from an active virtualenv environment; open a fresh terminal session first.

"Part 2: Apps" will arrive… when you see it.

View comments on original page...

Browse archives...

About Nathanael

Nathanael Jones is a software engineer, husband, consultant, and computer linguist with unreasonably high expectations of inanimate objects. He refines .NET, ruby, and javascript libraries full-time at Imazen, but can often be found on stack overflow or participating in W3C community groups.

ImageResizer

If you develop websites, and those websites have images, ImageResizer can make your life much eaiser. Find out more at imageresizing.net.

Recent Tweets

| Loading recent tweets...

Imazen

I run Imazen, a tiny software company that specializes in web-based image processing and other difficult engineering problems. I spend most of my time writing image-processing code in C#, web apps in Ruby, and documentation in Markdown. Check out some of my current projects.