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
- Install XCode from the App Store
- Open XCode, go to
Preferences -> Downloads -> Componentsand 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.